Files
labre-web/AGENTS.md
T
LakG 1121ab7199 Sistema de préstamos LabRe UABC — implementación inicial
Sistema web para gestión de préstamos de material del Laboratorio de
Sistemas Computacionales de la UABC.

- Backend: Supabase self-hosted, schema aislado `prestamos` con RLS,
  triggers de stock y audit log (supabase/migrations/0001_init.sql).
- Auth: Google OAuth restringido a @uabc.edu.mx, verificado en
  middleware y como segunda línea en trigger de DB.
- Frontend: Astro 7 (SSR con adapter Node) + React islands + Tailwind
  v4 con paleta UABC (primary #00723F, secondary #DD971A) bajo regla
  60/30/10.
- Interfaz alumno mobile-first: catálogo con filtro por categorías,
  solicitud de préstamos, historial personal.
- Interfaz admin desktop-first: panel con KPIs, bandeja de solicitudes
  (aprobar/rechazar/devolver), CRUD de inventario y categorías,
  reportes filtrables con export CSV nativo.
- Modales con `<dialog>` nativo, cero librerías de UI adicionales.
- Deploy: Dockerfile multi-stage node:22-alpine + docker-compose para
  publicar bajo prestamos.buglabs.dev vía Cloudflare Tunnel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-14 19:37:49 -07:00

18 KiB
Raw Blame History

Development

When starting the dev server, use background mode:

astro dev --background

Manage the background server with astro dev stop, astro dev status, and astro dev logs.

Documentation

Full documentation: https://docs.astro.build

Consult these guides before working on related tasks:

Proceso de trabajo de este proyecto

Al final de cada prompt del usuario, actualizar la sección Bitácora de este archivo con lo que se hizo, decisiones tomadas y qué sigue. No crear archivos de plan aparte: este CLAUDE.md es la única fuente de verdad del progreso.

Antes de ejecutar cambios de infraestructura (SSH a buglabs, docker, Cloudflare tunnel, DB) pedir confirmación al usuario si el cambio es destructivo o afecta a otros proyectos que viven en el mismo servidor (genqbar, gitea, etc).

Cuando el trabajo se pueda dividir en partes que no toquen los mismos archivos, lanzar agentes en paralelo (Agent tool) en vez de hacerlo secuencial.


Proyecto: Sistema de Préstamos — Laboratorio de Sistemas Computacionales UABC

Sistema web para gestionar préstamos de material del laboratorio. Dos roles: alumno (solicita material) y admin (gestiona solicitudes, inventario y reportes). Login exclusivo con Google restringido a correo institucional @uabc.edu.mx.

Decisiones de arquitectura

Decisión Elegido Motivo
Backend Supabase self-hosted existente en buglabs (supabase.buglabs.dev) Ya está corriendo (Postgres 15, Auth, Kong, Studio) — no se despliega uno nuevo. Compartido con otro proyecto (genqbar).
Aislamiento de datos Schema Postgres propio: prestamos public ya lo usa genqbar (tablas profiles, edificios, eventos, etc). Evita colisión de nombres.
Auth Supabase Auth + proveedor Google OAuth (a configurar, hoy no está activo en la instancia) Auth es compartido entre apps del mismo Supabase; el proveedor Google se habilita una vez a nivel instancia.
Restricción de dominio @uabc.edu.mx A nivel aplicación (middleware de Astro tras login), no a nivel GoTrue GoTrue es compartido con otras apps del servidor; no se puede bloquear el signup global sin afectar a genqbar.
Alta de admin inicial Manual vía Supabase Studio, después del primer login Decisión del usuario — no se hardcodea ningún correo admin.
Frontend interactivo Astro + React (islands) Ya es un proyecto Astro; React da el ecosistema más grande para tablas/formularios/export.
Hosting de la app Servidor propio (buglabs), adapter @astrojs/node Mismo homelab que Supabase; se agrega contenedor + ruta en el túnel de Cloudflare existente.
Subdominio prestamos.buglabs.dev Se añade al mismo túnel Cloudflare (3de17b3c-...) junto a supabase/git/status/genqbar.
Exportar reportes CSV nativo (sin librería) para v1 Cero dependencias nuevas; se sube a PDF/Excel solo si se pide explícitamente.

Modelo de datos propuesto (schema prestamos)

  • profiles — id (=auth.users.id), email, nombre, matricula, rol (alumno|admin), created_at
  • categorias — id, nombre
  • materiales — id, nombre, categoria_id, descripcion, cantidad_total, cantidad_disponible, numero_inventario, estado (disponible|mantenimiento|baja)
  • prestamos — id, alumno_id, material_id, cantidad, estado (pendiente|aprobado|rechazado|activo|devuelto|vencido), fecha_solicitud, fecha_aprobacion, fecha_devolucion_estimada, fecha_devolucion_real, aprobado_por, notas

RLS: alumno solo ve/edita sus propios préstamos; admin ve y gestiona todo. Enforced con auth.uid() contra profiles.id y policies por rol.

Fases de implementación

  1. Backend Supabase — schema prestamos, tablas, RLS, trigger handle_new_user, seed de categorías. (bloqueante, va primero)
  2. Scaffolding Astro — integración React, cliente Supabase, middleware de auth + verificación de dominio/rol, layout base.
  3. Interfaz alumno — catálogo de materiales, solicitar préstamo, ver mis préstamos y su estado.
  4. Interfaz admin — solicitudes — bandeja de solicitudes (aprobar/rechazar), marcar devoluciones.
  5. Interfaz admin — inventario — alta/baja/edición de materiales y categorías.
  6. Reportes — filtros (fecha, material, alumno, estado), export CSV, vista de vencidos.
  7. Deploy — Dockerfile + compose para el adapter Node, ruta prestamos.buglabs.dev en el túnel de Cloudflare de buglabs.
  8. QA end-to-end — flujo real de login + alumno + admin en producción.

Fases 3, 4/5 y 6 tocan carpetas de rutas distintas (src/pages/alumno/*, src/pages/admin/*) y pueden correr como agentes en paralelo una vez completa la fase 2. Fase 7 (infra) también es independiente del código de UI y puede prepararse en paralelo.

Bitácora

  • 2026-08-13 — Plan inicial definido. Se hizo reconocimiento por SSH del homelab buglabs: Supabase self-hosted ya corre ahí (compartido con proyecto "genqbar" en schema public), túnel Cloudflare ya gestiona varios subdominios de buglabs.dev, Docker/Compose disponibles. Decisiones de arquitectura tomadas junto con el usuario (ver tabla arriba). Pendiente: confirmación del usuario para arrancar Fase 1 (backend Supabase).

  • 2026-08-14 — Plan refinado y aprobado. Ajustes: (1) schema public.profiles NO se reutiliza — el sistema de préstamos usa prestamos.profiles propio y aislado; (2) sí se agrega prestamos.audit_log con trigger para trazabilidad de cambios de estado; (3) v1 sin reglas duras de límite/duración (admin decide caso por caso). Se corrigió la identificación del vecino que usa el schema public: no es genqbar (genqbar es un estático sin auth), es otra app de check-in por QR corriendo en el 8080. Google OAuth se confirmó ya habilitado en la instancia de Supabase (GOOGLE_ENABLED=true, con CLIENT_ID/SECRET presentes en ~/supabase/docker/.env), no requiere alta nueva. Se añadieron normas de diseño al plan: paleta UABC (primario #00723F verde, secundario #DD971A dorado, terciario #F4F7F5, neutral #2D3748) bajo regla 60/30/10, admin=desktop-first y alumno=mobile-first pero ambos cross-device, minimalista con animaciones ligeras, Inter Variable como tipografía, WCAG AA no negociable. Plan guardado en ~/.claude/plans/inicia-con-la-faze-modular-reef.md.

  • 2026-08-14 — Fase 1 completada (Backend Supabase).

    • Escrita migración supabase/migrations/0001_init.sql con: schema prestamos; tablas profiles, categorias, materiales, prestamos, audit_log; función helper prestamos.is_admin(); trigger prestamos_on_auth_user_created en auth.users que rechaza emails no @uabc.edu.mx (segunda línea de defensa) y crea el perfil; triggers prestamos_sync_stock (ajusta cantidad_disponible según transiciones de estado) y prestamos_log_estado (registra cambios en audit_log); 11 policies RLS distribuidas en las 5 tablas usando is_admin(); seed de 6 categorías.
    • Aplicada por psql sobre la instancia de buglabs. Verificado: 5 tablas creadas, 11 policies activas, 3 triggers propios registrados, 6 categorías seed presentes.
    • Editado ~/supabase/docker/.env en buglabs (backup previo con timestamp): PGRST_DB_SCHEMAS incluye ahora prestamos, ADDITIONAL_REDIRECT_URLS incluye https://prestamos.buglabs.dev/**.
    • Recreados contenedores supabase-rest y supabase-auth con docker compose up -d (un simple restart no relee .env; ese fue un aprendizaje del proceso). Downtime real: ~5s.
    • Corrección aplicada tras primer curl de verificación: faltaba grant a service_role. Se añadió grant ... to service_role + alter default privileges (tanto en la DB como en el archivo de migración, para que sea idempotente si se re-aplica). Verificado con curl que GET /rest/v1/categorias con Accept-Profile: prestamos retorna las 6 categorías.
    • Estado: backend listo para consumir desde la app. Próximo paso: Fase 2 (scaffolding Astro + React + Tailwind + adapter Node + cliente Supabase SSR + middleware de auth con verificación de dominio).
  • 2026-08-14 — Fase 2 completada (Scaffolding Astro).

    • Instaladas integraciones vía npx astro add react node tailwind --yes. Añadido a package.json: @astrojs/react, @astrojs/node, tailwindcss (v4, sin config JS — configura en CSS con @theme), react, react-dom. Adaptador Node en modo standalone.
    • Añadidas deps runtime: @supabase/supabase-js, @supabase/ssr, @fontsource-variable/inter.
    • astro.config.mjs: se explicitó output: 'server' (sin esto, Astro intentaba prerenderizar rutas SSR y el cliente Supabase reventaba). Integraciones: React + Tailwind v4 (vía @tailwindcss/vite).
    • tsconfig.json: añadido baseUrl: '.' + paths: { "@/*": ["src/*"] } para import alias.
    • src/styles/global.css: paleta UABC como tokens Tailwind v4 (@theme con --color-primary #00723F, --color-secondary #DD971A, --color-surface #F4F7F5, --color-ink #2D3748, --color-danger #C53030), Inter Variable como --font-sans, componentes .btn, .btn-primary, .btn-secondary, .btn-ghost, .card, .input, .label. Tap targets ≥44px. touch-action: manipulation y -webkit-tap-highlight-color: transparent en botones. text-wrap: balance/pretty en tipografía.
    • src/lib/supabase.ts: helpers serverClient(cookies) y browserClient(). Ambos apuntan a db: { schema: 'prestamos' } para que las queries no necesiten prefijo. Cookies con httpOnly, sameSite: 'lax', secure solo en prod. Usa el patrón get/set/remove (el getAll/setAll moderno de @supabase/ssr no funcionó con AstroCookies de Astro 7 — el método .getAll() no existe ahí).
    • src/env.d.ts: tipos para App.Locals (supabase, user, profile) y ImportMetaEnv (PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, PUBLIC_APP_URL).
    • src/middleware.ts: instancia serverClient, obtiene user, verifica dominio @uabc.edu.mx (rechaza + redirect /login?error=dominio), carga profile desde prestamos.profiles, protege rutas privadas (redirect a /login si no hay user; 403 en /admin/* si no es admin), redirige a / si un user logueado visita /login.
    • src/pages/login.astro: pantalla completa aparte, tarjeta centrada con logo, <h1>Sistema de Préstamos</h1>, <a> Google (verde primario) que arma authorize URL de Supabase, alerta accesible si ?error=dominio|oauth, nota de dominio institucional al pie. Animación entrada solo detrás de motion-safe:.
    • src/pages/api/auth/callback.ts: intercambia code por sesión, verifica dominio por segunda vez y redirige a / (o a /login?error=… según el caso).
    • src/pages/api/auth/signout.ts: POST → signOut → redirect /login.
    • src/layouts/Layout.astro: HTML base (lang="es", meta viewport, meta theme-color=#00723F, color-scheme: light), incluye skip-to-content link con focus:not-sr-only. title prop.
    • src/layouts/AppLayout.astro: sidebar bg-primary con logo enlazado a /, nav responsive (sidebar en desktop, topbar en mobile), items admin vs alumno según rol, botón signout desktop full + icon-only en mobile con aria-label.
    • src/pages/index.astro: dashboard con tarjetas según rol (alumno: catálogo + mis préstamos; admin: solicitudes + inventario + reportes). Placeholders navegables — sus destinos se implementarán en Fases 36.
    • .env local creado (obtenidas ANON y SERVICE_ROLE keys por SSH de buglabs). .env.example versionado con placeholders. .env* ya estaba en .gitignore.
    • Borrado src/components/Welcome.astro y src/assets/* del starter, Layout.astro reescrito para nuestros propósitos.
    • Auditoría de UI con skill web-design-guidelines (checklist Vercel Labs): resueltos 6 hallazgos accionables — transition:all reemplazado por transiciones específicas por propiedad; role="button" sobrante en <a> de Google removido; theme-color y color-scheme metas añadidos; touch-action: manipulation en interactivos; logo mobile con aria-label sobre link y letra aria-hidden; text-wrap: balance/pretty en tipografía.
    • Verificación: npm run build sin errores. npm run dev (background) → GET / sin sesión redirige /login (200), /login renderiza copy correcto, ?error=dominio muestra la alerta esperada.
    • Aprendizajes registrados: (a) Tailwind v4 requiere @reference "tailwindcss" dentro de <style> scoped de .astro para usar @apply; (b) output: 'server' es obligatorio en astro.config.mjs incluso con adaptador Node — no se infiere; (c) AstroCookies.getAll() no existe en Astro 7, usar get(name).
    • Estado: base lista. Fases 36 pueden arrancar en paralelo con agentes ahora que hay: middleware con sesión + profile en Astro.locals, cliente Supabase apuntando al schema prestamos, layout con nav responsive, tokens de diseño Tailwind y clases utilitarias .btn/.card/.input.
  • 2026-08-14 — Fases 3, 4, 5, 6, 7 completadas en paralelo (5 agentes).

    • Se lanzaron 5 subagentes simultáneos en un solo turno con contratos aislados por carpeta (sin colisión de archivos). Todos terminaron el core aunque tres (Fases 4, 5, 6) marcaron failed al final por límite de sesión durante refinamientos post-implementación; el orquestador consolidó y validó que todo compila.
    • Fase 3 (Alumno) — 5 archivos: src/pages/alumno/{catalogo,mis-prestamos}.astro, src/pages/api/prestamos/index.ts, src/components/alumno/{SolicitarModal,FiltroCategorias}.tsx. Mobile-first. Catálogo agrupa por categoría en SSR, modal <dialog> nativo para solicitar, filtro por chips que sincroniza ?cat= en URL. Mis-préstamos con secciones "Activos" (pendiente/aprobado/activo) y "Historial" en <details>. Endpoint POST valida stock antes de insertar.
    • Fase 4 (Admin solicitudes) — 11 archivos: src/pages/admin/index.astro (panel con KPIs), src/pages/admin/solicitudes/{index,activos,historial}.astro, src/components/admin/solicitudes/{AccionesSolicitud,MarcarDevuelto,VerDetalles}.tsx, src/pages/api/admin/prestamos/[id]/{aprobar,rechazar,devolver,detalles}.ts. Desktop-first tabla / mobile cards. Flujo simplificado: pendiente → aprobado (que también significa activo) → devuelto. El estado activo del enum queda reservado. Los endpoints usan .eq('estado', 'pendiente') como guard anti doble-click; los triggers de DB manejan stock y audit_log.
    • Fase 5 (Admin inventario) — 8 archivos: src/pages/admin/inventario/{index,categorias}.astro, src/components/admin/inventario/{MaterialForm,EliminarMaterial,CategoriaForm,EliminarCategoria}.tsx, src/pages/api/admin/{materiales,categorias}/[index,\[id\]].ts. Filtros SSR vía query params. Edición de cantidad_total valida que no quede debajo de lo prestado. Manejo de FK violations (23503) con mensaje útil.
    • Fase 6 (Reportes) — 6 archivos: src/pages/admin/reportes.astro (tabs Historial/Vencidos con filtros por rango de fecha/estado/material/alumno), src/components/admin/reportes/{Autocomplete,AutocompleteMaterial,AutocompleteAlumno}.tsx (patrón combobox con teclado y aria-*, se extrajo helper compartido Autocomplete.tsx), src/pages/api/admin/reportes/{materiales,alumnos,export}.ts. Export CSV nativo (sin librería) con BOM UTF-8, escape de ," y \n, fechas ISO. Marca // ponytail: LIMIT 500, paginar cuando pase de eso.
    • Fase 7 (Deploy) — 5 archivos: Dockerfile (multi-stage node:22-alpine, 3 stages, healthcheck vía node -e http.get('/login'), USER node), .dockerignore, docker-compose.yml (puerto 127.0.0.1:8082:4321, log rotation 10MB×3), .env.production.example, deploy/README.md con pasos de primer deploy + update + rollback + snippet Cloudflare tunnel. Sin nginx delante, sin pm2. Tamaño de imagen esperado ~180-220 MB.
    • Correcciones post-agentes: (a) botón "Cerrar sesión" del AppLayout usaba transition (all) — cambiado a transition-colors. (b) AutocompleteMaterial y AutocompleteAlumno originalmente tenían <span class="label"> externo sin asociación al input; el agente 6 detectó la regresión y consolidó en Autocomplete.tsx con <label htmlFor> propio.
    • Verificación end-to-end en dev: npm run build pasa limpio. Smoke test sin sesión sobre 14 rutas (páginas + API): /login → 200; todas las demás → 302 a /login. Middleware protege correctamente /alumno/*, /admin/* y /api/*.
    • Total: 35 archivos nuevos (25 páginas/endpoints + 10 componentes React + 5 archivos de deploy). Cero librerías nuevas más allá de lo que quedó en Fase 2. Todos los modales son <dialog> HTML nativo.
    • Bugs/hallazgos secundarios detectados por los agentes (registrados para atender después, no bloqueantes): (i) el middleware ejecuta getUser() + fetch de perfil en cada request incluyendo /api/*, sin cache — bajo carga alta convendría memoizar por request. (ii) Si Astro.locals.user es null, el error de .id en el endpoint podría reventar; los endpoints actuales lo cubren con early return pero conviene revisar en la fase de QA.
    • Estado: código completo. Falta solo Fase 8 (deploy real al servidor + QA end-to-end con login real). Pendiente confirmación del usuario para arrancar el deploy.