{c.nombre}
++ {c.count > 0 ? ( + + {c.count} material{c.count === 1 ? '' : 'es'} + + ) : ( + Sin materiales + )} +
+diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4b114d1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +node_modules +dist +.astro +.env +.env.* +!.env.example +.git +.github +.gitignore +.vscode +.claude +scratchpad +supabase/migrations/*.sql +AGENTS.md +CLAUDE.md +*.md +!README.md +Dockerfile +.dockerignore +docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..49e2c02 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +PUBLIC_SUPABASE_URL=https://supabase.buglabs.dev +PUBLIC_SUPABASE_ANON_KEY= +SUPABASE_SERVICE_ROLE_KEY= +PUBLIC_APP_URL=http://localhost:4321 diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..f0678ea --- /dev/null +++ b/.env.production.example @@ -0,0 +1,5 @@ +# Copia como .env.production y rellena antes de docker compose up +PUBLIC_SUPABASE_URL=https://supabase.buglabs.dev +PUBLIC_SUPABASE_ANON_KEY= +SUPABASE_SERVICE_ROLE_KEY= +PUBLIC_APP_URL=https://prestamos.buglabs.dev diff --git a/AGENTS.md b/AGENTS.md index c5ff429..9cdd2ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,3 +20,102 @@ Consult these guides before working on related tasks: - [Adding or managing content](https://docs.astro.build/en/guides/content-collections/) - [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/) - [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/) + +## 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, `
Resumen del laboratorio.
+| Alumno | +Material | +Cant. | +Estado | +Solicitado | +
|---|---|---|---|---|
| {r.alumno?.nombre ?? r.alumno?.email ?? '—'} | +{r.material?.nombre ?? '—'} | +{r.cantidad} | +{estadoLabel[r.estado] ?? r.estado} | +{fmtFecha.format(new Date(r.fecha_solicitud))} | +
Aún no hay solicitudes registradas.
+ )} +Organiza los materiales por tipo.
++ No se pudieron cargar las categorías. Recarga la página. +
+Crea la primera para agrupar tus materiales.
+| Nombre | +# Materiales | +Acciones | +
|---|---|---|
| {c.nombre} | ++ {c.count > 0 ? ( + + {c.count} + + ) : ( + 0 + )} + | +
+
+
+ |
+
+ {c.count > 0 ? ( + + {c.count} material{c.count === 1 ? '' : 'es'} + + ) : ( + Sin materiales + )} +
+Materiales del laboratorio.
++ No se pudo cargar el inventario. Recarga la página. +
++ {q || catId != null || estado + ? 'Ningún material coincide con los filtros. Ajusta la búsqueda.' + : 'Aún no hay material registrado. Agrega uno para comenzar.'} +
+| Nombre | +Categoría | +Nº inventario | +Stock | +Estado | +Acciones | +
|---|---|---|---|---|---|
|
+ {m.nombre}
+ {m.descripcion && (
+ {m.descripcion}
+ )}
+ |
+ {m.categoria?.nombre ?? '—'} | +{m.numero_inventario ?? '—'} | ++ {m.cantidad_disponible} / {m.cantidad_total} + | ++ + {badge.label} + + | +
+
+
+ |
+
{m.categoria.nombre}
+ )} +{m.descripcion}
} +Historial filtrable de préstamos y lista de vencidos.
+Sin resultados para los filtros seleccionados.
+Ajusta los filtros o exporta todo el historial.
+No hay préstamos vencidos. Todo al día.
+No hay préstamos activos.
+No hay registros para este filtro.
+No hay solicitudes por revisar.
+Todo al día.
+Material disponible en el Laboratorio de Sistemas.
++ No se pudo cargar el catálogo. Recarga la página o intenta más tarde. +
+Vuelve pronto o contacta al encargado del laboratorio.
++ Ningún material en esta categoría. Prueba con otra. +
+ +{m.descripcion}
+ )} + +Estado de tus solicitudes actuales e historial.
++ No se pudieron cargar tus préstamos. Recarga la página o intenta más tarde. +
+Explora el catálogo y envía tu primera solicitud.
+ Ir al catálogo +Nada activo por ahora.
+ ) : ( ++ {p.material?.numero_inventario ?? '—'} +
++ {p.notas} +
+ )} +Sin historial todavía.
+ ) : ( ++ {p.material?.numero_inventario ?? '—'} +
+Bienvenido{nombre ? ',' : ''}
+Aprobar o rechazar peticiones de material.
+ + +Materiales y categorías del laboratorio.
+ + +Historial y exportación de préstamos.
+ + > + ) : ( + <> + +Ver material disponible y solicitar un préstamo.
+ + +Estado de tus solicitudes actuales e historial.
+ + > + )} +Laboratorio de Sistemas Computacionales · UABC
+Accede con tu cuenta institucional.
+ + + + Continuar con Google + + + {errorMsg && ( ++ Acceso exclusivo con correo institucional @uabc.edu.mx +
+