Bitácora: v1.6 en producción (docente + combobox + sonido + wrap + self-heal fix)
This commit is contained in:
@@ -240,3 +240,16 @@ Fases 3, 4/5 y 6 tocan carpetas de rutas distintas (`src/pages/alumno/*`, `src/p
|
|||||||
- **Aprendizajes registrados**: (a) los agentes fallando por límite de sesión son un modo de degradación normal cuando el trabajo por agente es grande — la mitigación es dividir mejor los tracks (agentes P y H fueron los más grandes, ambos cortaron); alternativa: preparar prompts para que cada agente escriba archivos incrementalmente y el orquestador pueda completar los huecos con menos contexto; (b) Docker Compose lee `.env` **implícitamente** para expansión de `${VAR}` en el YAML pero NO lee `env_file` para expansión — para vars en `build.args`, o bien renombras `.env.production` a `.env`, o exportas al shell antes del compose (elegimos lo segundo, menos disruptivo con la config existente); (c) `Storage RLS` con path por carpeta = uid es el patrón limpio para "cada quien sube la suya" — `(storage.foldername(name))[1] = auth.uid()::text` en el `using`/`with check`, no necesita RPC ni endpoint intermedio; (d) para RPCs con fallback lógico (como `crear_solicitud` autocompletando el tutor), poner la lógica en la BD y no en el endpoint es más robusto porque el mismo comportamiento aplica si algún día se llama la RPC desde otro cliente; (e) `CLAUDE.md` es symlink a `AGENTS.md` (aprendido en la sesión anterior, sigue vigente — editar el path real).
|
- **Aprendizajes registrados**: (a) los agentes fallando por límite de sesión son un modo de degradación normal cuando el trabajo por agente es grande — la mitigación es dividir mejor los tracks (agentes P y H fueron los más grandes, ambos cortaron); alternativa: preparar prompts para que cada agente escriba archivos incrementalmente y el orquestador pueda completar los huecos con menos contexto; (b) Docker Compose lee `.env` **implícitamente** para expansión de `${VAR}` en el YAML pero NO lee `env_file` para expansión — para vars en `build.args`, o bien renombras `.env.production` a `.env`, o exportas al shell antes del compose (elegimos lo segundo, menos disruptivo con la config existente); (c) `Storage RLS` con path por carpeta = uid es el patrón limpio para "cada quien sube la suya" — `(storage.foldername(name))[1] = auth.uid()::text` en el `using`/`with check`, no necesita RPC ni endpoint intermedio; (d) para RPCs con fallback lógico (como `crear_solicitud` autocompletando el tutor), poner la lógica en la BD y no en el endpoint es más robusto porque el mismo comportamiento aplica si algún día se llama la RPC desde otro cliente; (e) `CLAUDE.md` es symlink a `AGENTS.md` (aprendido en la sesión anterior, sigue vigente — editar el path real).
|
||||||
- **Deliberadamente NO tocado**: horarios académicos automáticos (fuera de alcance explícito — usuario eligió textarea manual); notificaciones al maestro cuando se aprueba un vale (backlog); comprimir avatares antes de subir (si pesan mucho se agrega después); PWA y browser notifications (backlog viejo).
|
- **Deliberadamente NO tocado**: horarios académicos automáticos (fuera de alcance explícito — usuario eligió textarea manual); notificaciones al maestro cuando se aprueba un vale (backlog); comprimir avatares antes de subir (si pesan mucho se agrega después); PWA y browser notifications (backlog viejo).
|
||||||
- **Deploy completado**: **sistema en producción con perfil + onboarding + home reformulado + maestros CRUD + upload arreglado + 5 bugs mobile arreglados en https://prestamos.buglabs.dev**.
|
- **Deploy completado**: **sistema en producción con perfil + onboarding + home reformulado + maestros CRUD + upload arreglado + 5 bugs mobile arreglados en https://prestamos.buglabs.dev**.
|
||||||
|
|
||||||
|
- **2026-08-25 — v1.6: rol docente + combobox maestros/tutores + sonido notif + fecha 1 día + wrap admin + fix self-heal profile**. Iteración operativa reportada por usuarios reales del laboratorio, más un bug crítico de login descubierto la misma sesión.
|
||||||
|
- **Bug crítico previo (self-heal)**: usuarios `@uabc.edu.mx` haciendo login vía OAuth entraban con `user` pero **sin fila en `prestamos.profiles`** — la app los trataba como "no logueado". Root cause: coexisten 2 triggers `AFTER INSERT` en `auth.users`: `on_auth_user_created` (del proyecto vecino que usa `public.profiles`) y `prestamos_on_auth_user_created` (nuestro). El del vecino se ejecuta primero por orden alfabético y por razón no diagnosticada el nuestro no dispara consistentemente en producción — verificado que la función `prestamos.handle_new_user()` funciona bien manualmente (insert vía backfill regresó 8 filas de 8 users). Fix aplicado: (a) migración 0005 con policy `profiles_insert_self` (with check `id = auth.uid() and rol = 'alumno'` — impide auto-promoción a admin); (b) middleware.ts con self-heal: si `user` existe y `!profile` y email `@uabc.edu.mx`, hace `upsert` al vuelo con nombre desde `user_metadata.full_name`; (c) backfill manual en prod para los 8 users existentes. Aprendizaje: cuando 2 apps comparten una instancia de Supabase, cualquier trigger `AFTER INSERT` en `auth.users` es riesgoso — mejor no depender del trigger y hacer self-heal server-side desde el middleware. En 0006 se amplió la policy a `rol in ('alumno','docente')`.
|
||||||
|
- **Decisiones acordadas con el usuario antes de tocar código**: (1) lista de maestros = admin la va agregando manualmente desde `/admin/maestros` (no seed inicial masivo — la lista suele ser estable); (2) separar tutores de maestros = **flag `es_tutor` en la misma tabla** — todos los maestros aparecen en el combobox del checkout; solo los marcados con `es_tutor=true` aparecen en el select de tutor del perfil (mismo CRUD, un checkbox extra); (3) docente = **`profiles.matricula` reusada con label dinámico** "Matrícula" para alumno / "Número de empleado" para docente (cero columna nueva, cero migración de datos); (4) errores sha512/CORS de `beacon.min.js` = **el usuario los desactiva en Cloudflare dashboard** (Analytics & Logs → Web Analytics → toggle off para el dominio), no requiere código.
|
||||||
|
- **Migración `0006_docente_maestros_v16.sql`** aplicada limpia (`BEGIN`/`COMMIT` propios, mismo patrón que 0001-0005): `profiles.rol` check ahora acepta `'alumno'|'docente'|'admin'`; `maestros.es_tutor boolean not null default false` con `update ... where nombre='Sin especificar'` para marcarlo como tutor fallback (no romper flows existentes); policy `profiles_insert_self` reescrita ahora permite `rol in ('alumno','docente')`; RPC `crear_solicitud` **cambio de firma** de `(text, text, jsonb)` a `(int, text, jsonb)` — recibe `p_maestro_id` en vez de texto libre, y la lógica es: si rol='docente' usa el propio `profiles.nombre` como `maestro_responsable`; si alumno con `p_maestro_id` valida y guarda ese nombre; si alumno sin `p_maestro_id` cae al `tutor_id` del perfil; si tampoco hay tutor, raise `maestro_responsable_requerido`. El drop de la firma vieja es explícito con `drop function if exists prestamos.crear_solicitud(text, text, jsonb)`.
|
||||||
|
- **Ejecución**: 1 agente Explore para background (falló por límite en la ronda anterior — esta vez no hizo falta explorar), 1 agente en paralelo para el track más grande (perfil + onboarding + `es_tutor` en MaestroForm/endpoints/maestros.astro), y el orquestador hizo el resto directo en el hilo principal (endpoint `/api/solicitudes` con `maestro_id`, `catalogo.astro` con SSR de maestros + `esDocente`, `SolicitudCart.tsx` con `<select>` combobox + guard docente + `maxLength=250` en notas, wrap `break-words` en 4 vistas admin, sonido en `BadgeSolicitudes.tsx`, mover audio a `public/audio/`, fecha default `enDias(1)` en `AccionesSolicitud.tsx`). El agente completó su track limpio, cero conflictos con lo que hice.
|
||||||
|
- **UX del combobox maestros**: el `<option value="">` inicial muestra "— Usar mi tutor ({tutorNombre}) —" (o "— Elige un maestro —" si no tiene tutor). El fallback backend sigue activo: si el alumno no elige nadie, la RPC usa el `tutor_id` del perfil. Si escoge otro, guarda ese nombre. Doble defensa contra "diferencia de escritura entre humanos" (la razón que dio el usuario para pedir el combobox).
|
||||||
|
- **UX del docente**: en el checkout, el bloque de maestro simplemente NO se renderiza (`{!esDocente && ...}`); el `perfilCompleto` es `!!matricula` (sin tutor). En perfil/onboarding, semestre y tutor no aparecen; label matrícula → "Número de empleado"; badge del header muestra "Docente". Registro de docente: el admin promueve manualmente vía SQL o desde Studio (`update prestamos.profiles set rol='docente' where email=...`), igual patrón que admin. No hay UI para auto-registrarse como docente — evita abuso.
|
||||||
|
- **Sonido de notificación**: archivo movido de `src/audio/` (Astro no sirve archivos de `src/` estáticamente) a `public/audio/sonido_notificacion.mp3` — accesible en `/audio/sonido_notificacion.mp3` (verificado 200 `audio/mpeg` post-deploy). `BadgeSolicitudes.tsx` en el handler INSERT: `new Audio('/audio/sonido_notificacion.mp3').play().catch(() => {})` — el catch cubre la política de autoplay (muchos navegadores bloquean sonido sin interacción previa; como el admin ya interactuó al hacer login, en la práctica funciona).
|
||||||
|
- **Fecha default 1 día**: cambio de una línea en `AccionesSolicitud.tsx:44` (`useState(enDias(7))` → `useState(enDias(1))`). El admin puede cambiarla si quiere; el default solo era muy generoso.
|
||||||
|
- **Fix del desbordamiento**: `maxLength={500}` → `250` en el textarea del motivo en `SolicitudCart.tsx`, mismo cap aplicado en el backend (`.slice(0, 250)` en `/api/solicitudes/index.ts` como defensa). Wrap con `break-words` (Tailwind = `overflow-wrap: break-word`) agregado en cada renglón donde aparece `Maestro:` o `Motivo:` en las 3 vistas admin (index/activos/historial, tanto tabla desktop como cards mobile) y en el `<dd>` de `VerDetalles.tsx`. El `notas` de `VerDetalles` ya tenía `whitespace-pre-wrap` — quedó intacto.
|
||||||
|
- **Verificación**: `npm run build` limpio (1.52s). Deploy: 2 commits (migración + código), push con token efímero, `ssh buglabs '... set -a && . .env.production && set +a && docker compose up -d --build'` (mismo patrón que 0004). Container healthy en 23s. Smoke test post-deploy: `/login` → 200; `/audio/sonido_notificacion.mp3` → 200 con `content-type: audio/mpeg` (audio sirve correctamente); BD verifica `profiles_rol_check` incluye docente, 3 maestros existentes con `es_tutor` booleano correcto.
|
||||||
|
- **Deploy completado**: **v1.6 en producción en https://prestamos.buglabs.dev**. El usuario se encarga de: (a) desactivar Cloudflare Web Analytics para el dominio (bug de sha512/CORS); (b) marcar como docente vía SQL a las cuentas que corresponda.
|
||||||
|
|||||||
Reference in New Issue
Block a user