ETAPA_CALIDAD: Nota de discrepancia: las 5 clases de P1 usan var(--color-primary), no var(--color-re-teal) como decía el prompt — ambas resuelven al mismo hex #43BBC8. Hice el reemplazo por nombre de clase (igual intención: texto teal → carbón), no por el literal del grep.
This commit is contained in:
169
docs/estado/ESTADO_PROYECTO_26JUN2026.md
Normal file
169
docs/estado/ESTADO_PROYECTO_26JUN2026.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# ESTADO DEL PROYECTO — Datos País sobre el Cuidado
|
||||
## Documento de continuidad — 26 junio 2026
|
||||
|
||||
> Director (Product Owner): Marcos · Claude (Projects) = arquitecto/copiloto · Claude Code = implementación
|
||||
> Metodología: Maria Mersan · Contacto Fundación: Valeria Arce
|
||||
|
||||
---
|
||||
|
||||
## 1. Versionado y ramas
|
||||
- **Producción:** **3.0.0** — wizard v3 + dashboard tres miradas desplegado · tag `v3.0.0` · commit `fcf93ea`
|
||||
- **Preservación:** `v2.1.0-stable` + tag `v2.1.0`
|
||||
- **3 surveys en `live`** — reactivados al desplegar v3 (26 jun 2026)
|
||||
- SemVer + CHANGELOG + tags. ADRs en `docs/adr/`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Sprint 4 — Wizard v3 — COMPLETO Y EN PRODUCCIÓN
|
||||
|
||||
| Etapa | Estado |
|
||||
|---|---|
|
||||
| 4.0 Preservación v2 | ✅ |
|
||||
| 4A instance_id | ✅ |
|
||||
| 4B esquema declarativo + seed v3 | ✅ |
|
||||
| 4C motor de flujo (`lib/form-engine.ts`, puro, 20+ tests) | ✅ |
|
||||
| 4D wizard renderer (layout estable, header/footer fijos) | ✅ |
|
||||
| 4E instancias familiares (mini-wizard emergente del motor) | ✅ |
|
||||
| 4F autocomplete barrios + filtro ciudad por departamento | ✅ |
|
||||
| 4G submit + validación server-side (RPC `rpc_submit_response`) | ✅ |
|
||||
| 4I validación e2e + tag v3.0.0 + deploy + surveys live | ✅ |
|
||||
|
||||
### Decisiones arquitectónicas clave (no reabrir)
|
||||
- **ADR-005:** lógica de flujo en datos (esquema), nunca en frontend.
|
||||
- Motor puro en cliente; reusado server-side para validación de submit.
|
||||
- Único camino de escritura: RPC `rpc_submit_response` (SECURITY DEFINER).
|
||||
- `instance_id` por answer — instancias familiares persistidas correctamente.
|
||||
- Validación server-side: 7 checks (preguntas visibles, valores válidos, instancias legítimas,
|
||||
consentimiento, requeridas presentes). Rechaza el submit completo si algo falla.
|
||||
|
||||
### Autocomplete geo (4F)
|
||||
- `public/data/geo_paraguay_full.json` — 8152 barrios, jerarquía depto → ciudad → barrios.
|
||||
- Pregunta 2.3 (barrio): autocomplete con `position: fixed` + `getBoundingClientRect()`.
|
||||
- Pregunta 2.2 (ciudad): filtra por departamento (2.1) usando el mismo JSON. Limpia valor
|
||||
al cambiar departamento. Texto libre siempre permitido como fallback.
|
||||
|
||||
---
|
||||
|
||||
## 3. Dashboard v3 — Mirada Empresa en producción
|
||||
|
||||
**Decisión vigente:** NO migrar RPCs v2. Dashboard nuevo de tres miradas
|
||||
(OIT 5R, CEPAL, PNCUPA Paraguay).
|
||||
|
||||
### Estado
|
||||
- **Mirada Empresa:** ✅ COMPLETA EN PRODUCCIÓN
|
||||
- 11 RPCs (`rpc_me_*`) + helper `_dash_es_cuidador` verificados contra 50 respuestas reales.
|
||||
- 9 widgets funcionando: Talento en riesgo, Prevalencia, Punto ciego, Señales de impacto
|
||||
(presentismo 41.7%, ausentismo 36.1%, riesgo 38.9%, carrera congelada 47.2%),
|
||||
Apoyos demandados (ordenados por demanda), Segmentación (nivel y modalidad),
|
||||
Disposición a red de apoyo.
|
||||
- Tema claro marca Re: fondo `#E7E6E5`, cards `#FFFFFF`, texto `#494F4F`.
|
||||
- Migraciones aplicadas: `20260625000001` (fix `_dash_responses`) +
|
||||
`20260625000002` (11 RPCs) + `20260626000001` (fix bug `cnt` Opción B).
|
||||
- **Mirada País y Mirada Humana:** propuesta + mockup listos, implementación DIFERIDA
|
||||
a sprint de dashboard post-deadline.
|
||||
|
||||
### Bug conocido abierto
|
||||
- `rpc_me_carrera_congelada`: "Ninguna de las anteriores" aparece en `top_opciones`
|
||||
a pesar del filtro en el subquery. El filtro opera sobre el array completo, no sobre
|
||||
los elementos individuales del `jsonb_array_elements_text`. No bloqueante para el
|
||||
release — registrado para próximo sprint.
|
||||
|
||||
### Documentos (en `docs/dashboard/`)
|
||||
- `PROPUESTA_DASHBOARD_v3.md` — las 3 miradas, ~30 métricas, marcos citados
|
||||
- `dashboard_tres_miradas_mockup.html` — mockup funcional 3 pestañas
|
||||
- `SPEC_MIRADA_EMPRESA.md` — contrato técnico 10 métricas + estrella
|
||||
|
||||
### Decisiones del dashboard (no reabrir)
|
||||
- CUIDADOR_ACTIVO incluye "hijo menor sin discapacidad" (criterio UNFPA, validado por Maria jun 2026).
|
||||
- Mirada Empresa NO expone datos sensibles (3.x, 4.2, 4.3).
|
||||
- k-anonimato k≥5 sobre `count(DISTINCT response_id)` — también en métricas por instancia.
|
||||
- 2 gaps de datos para sprint dashboard: horas cuidado no remunerado (País),
|
||||
escolarización niñez (Humana).
|
||||
|
||||
---
|
||||
|
||||
## 4. Datos de prueba en VPS
|
||||
- **51 respuestas** en survey Demo (`30000000-0000-0000-0000-000000000001`) — 50 del seed
|
||||
determinista (seed=42) + 1 del e2e de 4I.
|
||||
- Survey Demo en `live`. Datos NO limpiados — el director los necesita para mostrar al equipo.
|
||||
- Script de limpieza cuando corresponda:
|
||||
```bash
|
||||
docker exec supabase-db psql -U postgres -d postgres -c "
|
||||
TRUNCATE answers, responses, consent_records CASCADE;
|
||||
UPDATE surveys SET status='draft' WHERE id='30000000-0000-0000-0000-000000000001';"
|
||||
```
|
||||
- ⚠️ El seed corrió dos veces en sesión anterior (detectado y corregido). Agregar guard
|
||||
anti-duplicación al `seed_prueba_50.sql` — anotado en BACKLOG.
|
||||
|
||||
---
|
||||
|
||||
## 5. Deuda técnica y seguridad
|
||||
|
||||
| ID | Descripción | Prioridad |
|
||||
|---|---|---|
|
||||
| BUG-001 | "Ninguna de las anteriores" aparece en `rpc_me_carrera_congelada` | Próximo sprint dashboard |
|
||||
| TECH-DEBT-006 | Backup remoto (S3 o VPS alternativo o Google Drive) | Antes de datos reales no desechables |
|
||||
| TECH-DEBT-009 | Rotar secrets post-CVE | Antes de datos reales no desechables |
|
||||
| TECH-DEBT-010 | E9 lee modalidad por JOIN a answers (2.7) en vez de `qi_modalidad` | Baja |
|
||||
| SEC-002 | Sprint compliance (audit log, hashing, cifrado en reposo) | Sprint dedicado |
|
||||
| SEC-003 | TRUNCATE evade inmutabilidad ADR-003 | Antes de datos reales no desechables |
|
||||
| SEC-004 | RPC bypass REST (coherencia de flujo en Server Action) | Sprint futuro |
|
||||
| BACKLOG-001 | `seed_prueba_50.sql` idempotente (guard anti-duplicación) | Baja |
|
||||
| BACKLOG-002 | Fase 2: motor genérico de formularios | Post-deadline |
|
||||
|
||||
**Riesgo aceptado explícitamente (26 jun 2026):** TECH-DEBT-006, TECH-DEBT-009 y SEC-003
|
||||
aplican cuando haya datos reales no desechables. Los datos actuales son de empresas voluntarias
|
||||
y grupos de control — desechables si es necesario.
|
||||
|
||||
---
|
||||
|
||||
## 6. Infraestructura
|
||||
|
||||
- Next.js 15.3.8 · Supabase self-hosted · Dokploy v0.29.4 · VPS inq-mart-2 (172.20.5.11)
|
||||
- App: `economia-cuidado.inqualityhq.com` · API: `db.inqualityhq.com` (Kong + SSL)
|
||||
- Repo: `git.inquality.com.py/marcos/motor-de-encuestas` (rama `main`)
|
||||
- Repo local: `/Users/marcos/InQ/Motor de encuestas`
|
||||
- Migraciones SIEMPRE vía CLI del VPS:
|
||||
`docker exec -i supabase-db psql -U postgres -d postgres < archivo.sql`
|
||||
- Deploy: Dokploy → Rebuild + "Clean cache"
|
||||
- Anon key Kong (diagnóstico):
|
||||
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzAwMDAwMDAwLCJleHAiOjIwMDAwMDAwMDB9.6D79NWu1KfeOzEZXT6Amf-NmG0T9uk8SK9xNd32oLaY`
|
||||
|
||||
---
|
||||
|
||||
## 7. Contexto comercial
|
||||
- 2 clientes inminentes. Mirada Empresa es la que le habla al cliente que paga.
|
||||
- InQuality y Fundación Solidaridad en joint venture.
|
||||
- Datos actuales: empresas voluntarias y grupos de control (desechables).
|
||||
- Datos reales no desechables: avisará el director — en ese momento activar deuda de seguridad.
|
||||
|
||||
---
|
||||
|
||||
## 8. Próximos sprints sugeridos
|
||||
|
||||
1. **Sprint dashboard** — Miradas País y Humana, fix BUG-001 carrera congelada,
|
||||
preguntas faltantes (horas no remuneradas, escolarización), decisión `qi_modalidad`.
|
||||
2. **Sprint compliance** — SEC-002 (audit log, hashing, cifrado), SEC-003, TECH-DEBT-006/009.
|
||||
3. **Sprint motor genérico** — BACKLOG-002 Fase 2, extracción del intérprete.
|
||||
|
||||
---
|
||||
|
||||
## 9. Prompt para nueva sesión
|
||||
|
||||
```
|
||||
Sos el arquitecto/copiloto técnico de "Datos País sobre el Cuidado", motor de formularios
|
||||
soberano (Paraguay). Yo soy el product owner (Marcos).
|
||||
Claude Code (VS Code, contexto separado) hace la implementación; vos planificás,
|
||||
especificás y auditás.
|
||||
|
||||
Estado completo: docs/estado/ESTADO_PROYECTO_26JUN2026.md
|
||||
|
||||
Producción: v3.0.0 desplegada · 3 surveys en live · tag v3.0.0 (commit fcf93ea)
|
||||
Formulario v3: COMPLETO (motor, wizard, instancias, submit server-side, autocomplete geo)
|
||||
Dashboard: Mirada Empresa funcional en producción · País/Humana diferidas
|
||||
Datos en VPS: 51 respuestas en survey Demo (no limpiar — para mostrar al equipo)
|
||||
|
||||
Bug abierto: "Ninguna de las anteriores" en rpc_me_carrera_congelada (no bloqueante)
|
||||
|
||||
Próximo paso: [COMPLETAR]
|
||||
```
|
||||
113
sprints/sprint-4/ETAPA_4I_PROMPT.md
Normal file
113
sprints/sprint-4/ETAPA_4I_PROMPT.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# ETAPA 4I — Validación e2e + Release v3.0.0
|
||||
|
||||
## 1. Objetivo
|
||||
Validar el wizard v3 end-to-end y hacer el release a producción. Los 3 surveys
|
||||
pasan de `draft` a `live`. Esta etapa es irreversible en varios pasos — seguir
|
||||
el orden estricto de fases y detenerse si algo falla.
|
||||
|
||||
## 2. Estado de entrada
|
||||
- Último commit: `3e7109a0` (4F ciudad-select)
|
||||
- Commits no desplegados desde último deploy:
|
||||
`8dea04c` fix cnt RPCs · `dbcc60a` tema claro · `3ec5da0` autocomplete barrios ·
|
||||
`3e7109a0` ciudad-select
|
||||
- VPS: seed de 50 respuestas activo (Demo en `live`) — NO limpiar, el director
|
||||
quiere mostrar el dashboard al equipo con esos datos
|
||||
- Los 3 surveys están en `draft`
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar
|
||||
- Deploy vía Dokploy (rebuild + clean cache)
|
||||
- Migraciones SIEMPRE vía CLI del VPS — ninguna migración pendiente en esta etapa
|
||||
- Los 3 surveys se reactivan a `live` solo DESPUÉS de verificar producción
|
||||
- Tag `v3.0.0` solo DESPUÉS de reactivar surveys
|
||||
- No hay migración de datos v2 → v3 (arranque limpio)
|
||||
|
||||
## 4. Fases — orden estricto, detenerse si algo falla
|
||||
|
||||
### FASE 1 — Validación e2e local (antes de tocar producción)
|
||||
Correr el wizard completo localmente y verificar:
|
||||
- [ ] Consentimiento → flujo completo → submit exitoso
|
||||
- [ ] Una respuesta con al menos 1 familiar (instancia) persiste con instance_id
|
||||
- [ ] El motor no muestra preguntas que el flujo oculta
|
||||
- [ ] El autocomplete de barrio funciona (dropdown visible, no tapado)
|
||||
- [ ] El filtro ciudad-departamento funciona (2.1 → 2.2 filtra, cambiar 2.1 limpia 2.2)
|
||||
- [ ] Submit devuelve confirmación, no error
|
||||
|
||||
Si CUALQUIER item falla: DETENER. Reportar el fallo y esperar instrucción.
|
||||
|
||||
### FASE 2 — Deploy en Dokploy
|
||||
- Trigger rebuild en Dokploy con "Clean cache"
|
||||
- Verificar que el BUILD_ID nuevo aparece en los logs
|
||||
- Verificar que la app responde en `economia-cuidado.inqualityhq.com`
|
||||
- Verificar que el dashboard sigue mostrando los 50 datos sembrados
|
||||
- Verificar que el wizard abre y llega a la pregunta 2.3 sin errores de consola
|
||||
|
||||
Si CUALQUIER item falla: DETENER. Reportar el fallo y esperar instrucción.
|
||||
|
||||
### FASE 3 — Reactivar surveys + Tag
|
||||
Solo ejecutar si Fase 1 y Fase 2 están completas y verificadas.
|
||||
|
||||
Reactivar los 3 surveys en el VPS:
|
||||
```sql
|
||||
UPDATE surveys SET status = 'live'
|
||||
WHERE id IN (
|
||||
SELECT id FROM surveys WHERE status = 'draft'
|
||||
);
|
||||
```
|
||||
|
||||
Verificar que quedaron en `live`:
|
||||
```sql
|
||||
SELECT id, name, status FROM surveys ORDER BY name;
|
||||
```
|
||||
|
||||
Tag de release en el repo local y push:
|
||||
```bash
|
||||
git tag -a v3.0.0 -m "Release v3.0.0 — wizard declarativo + dashboard tres miradas"
|
||||
git push origin v3.0.0
|
||||
```
|
||||
|
||||
Actualizar CHANGELOG.md con las etapas 4C→4I completadas.
|
||||
|
||||
## 5. DoD completo
|
||||
- [ ] `git log --name-only -1` antes de empezar — confirmar commit de entrada
|
||||
- [ ] Fase 1 completa sin fallos
|
||||
- [ ] Fase 2 completa — app en producción respondiendo
|
||||
- [ ] Surveys en `live` — verificado con SELECT
|
||||
- [ ] Tag `v3.0.0` pusheado a Gitea
|
||||
- [ ] CHANGELOG.md actualizado
|
||||
- [ ] `git log --name-only -1` del commit de CHANGELOG
|
||||
|
||||
## 6. Qué reportar
|
||||
Por fase, en orden:
|
||||
FASE 1 — e2e local:
|
||||
|
||||
Submit con familiar: [response_id y instance_id del registro insertado]
|
||||
Items del checklist: ✓/✗ cada uno
|
||||
|
||||
FASE 2 — Deploy:
|
||||
|
||||
BUILD_ID nuevo: [valor]
|
||||
App responde: ✓/✗
|
||||
Dashboard con datos: ✓/✗
|
||||
Wizard sin errores de consola: ✓/✗
|
||||
|
||||
FASE 3 — Release:
|
||||
|
||||
Surveys en live: [output del SELECT]
|
||||
Tag v3.0.0: [output de git tag]
|
||||
CHANGELOG: [secciones agregadas]
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No reactivar surveys antes de verificar producción (Fase 2 primero)
|
||||
- ❌ No taggear antes de reactivar surveys (Fase 3 es la última)
|
||||
- ❌ No limpiar el seed de 50 respuestas — el director los necesita para mostrar al equipo
|
||||
- ❌ No aplicar migraciones — no hay ninguna pendiente en esta etapa
|
||||
- ❌ No tocar `v2.1.0-stable`
|
||||
- ❌ No continuar si algo falla — detenerse y reportar
|
||||
|
||||
## 8. Versionado
|
||||
chore(release): CHANGELOG v3.0.0 — wizard declarativo + dashboard tres miradas
|
||||
Etapas 4C→4I completadas. Motor de flujo puro, wizard renderer,
|
||||
|
||||
instancias familiares, submit con validación server-side, dashboard
|
||||
|
||||
tres miradas (Mirada Empresa funcional), autocomplete barrios/ciudad.
|
||||
148
sprints/sprint-4/ETAPA_AUTH_PROMPT.md
Normal file
148
sprints/sprint-4/ETAPA_AUTH_PROMPT.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# ETAPA AUTH — Supabase Auth + protección /admin
|
||||
|
||||
## 1. Objetivo
|
||||
Implementar autenticación email/password con Supabase Auth y proteger
|
||||
la ruta /admin/*. El dashboard y las vistas de admin quedan detrás de login.
|
||||
Sin esta etapa no se avanza a vista admin mejorada ni vista detallada.
|
||||
|
||||
## 2. Contexto
|
||||
- `current_app_role()` y `current_org_id()` ya leen del JWT — el esquema está listo.
|
||||
- No hay middleware.ts ni sistema de auth hoy — /admin/responses está abierto.
|
||||
- Organizations existentes: Empresa Demo (…0001), InQuality (…0002),
|
||||
Fundación Solidaridad (…0003).
|
||||
- No hay usuarios en auth.users — hay que crear el primero como parte de esta etapa.
|
||||
- La app usa anon key para el formulario público — eso NO cambia.
|
||||
- /admin/* usa service_role hoy — eso SÍ cambia: pasa a usar sesión del usuario.
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar
|
||||
- Supabase Auth nativo, email/password. Sin OAuth por ahora.
|
||||
- Dos roles: `platform_admin` (acceso total) y `org_admin` (acceso a su org).
|
||||
- Roles van como custom claims en el JWT: `app_role` y `org_id`.
|
||||
- Middleware Next.js protege /admin/* — redirige a /login si no hay sesión válida.
|
||||
- Página de login en /login — simple, solo email/password, sin registro público.
|
||||
- El formulario público (/encuesta/*) NO se toca — sigue con anon key.
|
||||
- Las RPCs del dashboard usan current_app_role() y current_org_id() del JWT —
|
||||
cuando el usuario esté autenticado, RLS funciona automáticamente.
|
||||
|
||||
## 4. Tareas
|
||||
|
||||
### 4.1 Diagnóstico previo — mecanismo de custom claims
|
||||
ANTES de implementar, reportar qué mecanismo de JWT customization está disponible
|
||||
en esta versión de Supabase self-hosted. Puede ser:
|
||||
- Hook SQL en `auth.hooks`
|
||||
- Override de función `auth.jwt()`
|
||||
- Edge Function hook
|
||||
No implementar nada hasta confirmar el mecanismo y reportarlo.
|
||||
|
||||
### 4.2 Migración — tabla user_roles + custom claims hook
|
||||
Migración `20260627000001_auth_user_roles.sql`:
|
||||
|
||||
```sql
|
||||
-- Tabla de roles
|
||||
CREATE TABLE public.user_roles (
|
||||
user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
app_role text NOT NULL CHECK (app_role IN ('platform_admin','org_admin')),
|
||||
org_id uuid REFERENCES public.organizations(id)
|
||||
);
|
||||
|
||||
-- RLS: solo el propio usuario puede ver su rol (y platform_admin ve todos)
|
||||
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
|
||||
Implementar el hook de custom claims según el mecanismo disponible confirmado en 4.1.
|
||||
El JWT resultante debe incluir:
|
||||
```json
|
||||
{ "app_role": "platform_admin", "org_id": "10000000-0000-0000-0000-000000000002" }
|
||||
```
|
||||
|
||||
### 4.3 Script de setup — primer usuario admin
|
||||
Script operativo (NO migración) en `scripts/setup_first_admin.sql`:
|
||||
- Crea el usuario en auth.users vía INSERT directo o Supabase admin API
|
||||
- Inserta en user_roles con app_role='platform_admin' y org_id de InQuality
|
||||
- NO hardcodear credenciales — usar variables `:email` y `:password` con instrucciones
|
||||
de cómo ejecutarlo:
|
||||
```bash
|
||||
docker exec -i supabase-db psql -U postgres -d postgres \
|
||||
-v email='admin@ejemplo.com' -v password='CAMBIAR' \
|
||||
< scripts/setup_first_admin.sql
|
||||
```
|
||||
- Documentar en el script cómo cambiar la contraseña después del primer login
|
||||
|
||||
### 4.4 Middleware Next.js
|
||||
`middleware.ts` en la raíz del proyecto:
|
||||
- Protege `/admin/*` y `/dashboard/*`
|
||||
- Si no hay sesión válida → redirect a `/login`
|
||||
- Si hay sesión → deja pasar
|
||||
- `/`, `/encuesta/*`, `/login` — sin restricción
|
||||
|
||||
### 4.5 Página de login
|
||||
`app/login/page.tsx` — Client Component:
|
||||
- Formulario email/password
|
||||
- Llama `supabase.auth.signInWithPassword()`
|
||||
- En éxito: redirect a `/admin/responses`
|
||||
- En error: mensaje claro ("Email o contraseña incorrectos"), nunca trace crudo
|
||||
- Sin registro público
|
||||
- Usar clases Tailwind existentes — sin nuevo sistema de diseño
|
||||
|
||||
### 4.6 Actualizar /admin/responses
|
||||
- Reemplazar `getSupabaseAdmin()` (service_role) por cliente de sesión del usuario
|
||||
- RLS de Postgres filtra según app_role y org_id del JWT automáticamente
|
||||
- Agregar botón "Cerrar sesión" (`supabase.auth.signOut()` → redirect a `/login`)
|
||||
|
||||
## 5. DoD antes de reportar
|
||||
- [ ] `git log --name-only -1` — listar todos los archivos tocados
|
||||
- [ ] Diagnóstico de mecanismo de custom claims reportado (4.1) antes de implementar
|
||||
- [ ] Migración `20260627000001` aplicada en VPS — verificar con:
|
||||
```bash
|
||||
docker exec -i supabase-db psql -U postgres -d postgres -c "\d user_roles"
|
||||
```
|
||||
- [ ] Script `scripts/setup_first_admin.sql` existe y está documentado
|
||||
- [ ] Hook JWT verificado — el token incluye `app_role` (cómo verificarlo: reportar el método)
|
||||
- [ ] `/login` funciona — email/password → sesión activa → redirect a /admin/responses
|
||||
- [ ] `/admin/responses` sin sesión → redirect a `/login`
|
||||
- [ ] `/admin/responses` con sesión → carga datos correctamente
|
||||
- [ ] Formulario público sin cambios (verificar que /encuesta/* sigue funcionando)
|
||||
- [ ] Build sin errores: `npm run build`
|
||||
- [ ] Commit + push a Gitea
|
||||
|
||||
## 6. Qué reportar
|
||||
```
|
||||
Diagnóstico previo (4.1):
|
||||
- Mecanismo de custom claims disponible: [cuál]
|
||||
- Alternativas descartadas: [por qué]
|
||||
|
||||
Verificado:
|
||||
- Migración user_roles aplicada: ✓ — output de \d user_roles
|
||||
- Hook JWT: app_role presente en token ✓/✗ — método de verificación usado
|
||||
- /login funciona: ✓/✗
|
||||
- /admin sin sesión → /login: ✓/✗
|
||||
- /admin con sesión → datos: ✓/✗
|
||||
- Formulario público sin cambios: ✓/✗
|
||||
- Build: ✓/✗
|
||||
- git log --name-only -1: [lista completa de archivos]
|
||||
|
||||
Asumido / decisiones:
|
||||
- [mecanismo de hook y razonamiento]
|
||||
|
||||
Hallazgos:
|
||||
- [lo que aparezca]
|
||||
```
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No tocar el formulario público ni la anon key
|
||||
- ❌ No hardcodear credenciales en ningún archivo commiteado
|
||||
- ❌ No implementar registro público de usuarios
|
||||
- ❌ No usar service_role en rutas que ya tienen sesión de usuario
|
||||
- ❌ No avanzar a vista admin mejorada — eso es la etapa siguiente
|
||||
- ❌ No hacer deploy — solo commit y push a Gitea
|
||||
- ❌ No implementar el hook sin reportar primero el diagnóstico de 4.1
|
||||
|
||||
## 8. Versionado
|
||||
```
|
||||
feat(auth): Supabase Auth email/password + middleware + login [auth]
|
||||
|
||||
Protege /admin/* con Supabase Auth. Tabla user_roles con custom claims
|
||||
(app_role, org_id) en JWT. Middleware Next.js redirige a /login sin sesión.
|
||||
Página /login con email/password. /admin/responses migrado de service_role
|
||||
a sesión de usuario autenticado.
|
||||
```
|
||||
124
sprints/sprint-5/ETAPA_AUDIT_LOG_PROMPT.md
Normal file
124
sprints/sprint-5/ETAPA_AUDIT_LOG_PROMPT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# ETAPA AUDIT LOG — Registro de auditoría append-only (Sprint 5, SEC-002)
|
||||
|
||||
## 1. Objetivo
|
||||
Implementar un registro de auditoría append-only en Postgres que loggee eventos
|
||||
de configuración crítica: cambios en `user_roles` (altas/bajas/modificaciones de
|
||||
acceso) y cambios de `status` en `surveys` (activación/desactivación de encuestas).
|
||||
Opcionalmente, si la implementación es simple y sin fricción, agregar también un
|
||||
trigger DDL para loggear migraciones aplicadas.
|
||||
|
||||
## 2. Contexto
|
||||
- Migración de referencia para patrones: `20260627000001_auth_user_roles.sql`
|
||||
(patrón SECURITY DEFINER + GRANT puntual a reutilizar).
|
||||
- Helpers RLS existentes a reusar: `public.current_app_role()` y
|
||||
`public.current_org_id()` — no reinventar.
|
||||
- Gap conocido y aceptado (SEC-003): esto NO protege contra TRUNCATE/DROP ejecutado
|
||||
por superusuario directo en el VPS. Documentar en comentario de la migración, no resolver.
|
||||
- Acceso a datos sensibles: NO loggear en esta etapa — hoy no existe código que
|
||||
lea columnas `is_sensitive=true`. Deferido a cuando ese código exista.
|
||||
- Naming de migración: después de `20260627000001` — usar timestamp del día de
|
||||
ejecución, formato `YYYYMMDDHHMMSS`.
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar
|
||||
- Tabla nueva: `public.audit_log`, append-only real.
|
||||
- Ningún rol (`anon`, `authenticated`, `service_role` vía API) tiene INSERT/UPDATE/DELETE
|
||||
directo en `audit_log`. Solo escriben funciones SECURITY DEFINER de los triggers.
|
||||
- Segunda barrera: trigger `BEFORE UPDATE OR DELETE ON audit_log` que aborta con
|
||||
excepción — defensa en profundidad.
|
||||
- Lectura de audit_log: RLS, solo `platform_admin` via `current_app_role()`.
|
||||
- Patrón de función trigger: SECURITY DEFINER, propiedad de `postgres`,
|
||||
GRANT EXECUTE solo al rol que lo necesita (no a PUBLIC).
|
||||
- Columnas de audit_log:
|
||||
```sql
|
||||
id bigserial PRIMARY KEY,
|
||||
occurred_at timestamptz NOT NULL DEFAULT now(),
|
||||
actor_role text,
|
||||
actor_user_id uuid,
|
||||
action text NOT NULL,
|
||||
target_table text NOT NULL,
|
||||
target_id text,
|
||||
detail jsonb
|
||||
```
|
||||
- Triggers obligatorios (Prioridad 1):
|
||||
- AFTER INSERT/UPDATE/DELETE en `user_roles`
|
||||
- AFTER UPDATE en `surveys` cuando cambia `status`
|
||||
- Trigger DDL (Prioridad 2 — opcional): solo implementar si es simple y sin fricción.
|
||||
Si requiere extensión nueva o configuración de infraestructura, NO incluir y reportarlo.
|
||||
|
||||
## 4. Tareas
|
||||
|
||||
### 4.1 Diagnóstico previo — DDL trigger
|
||||
Antes de implementar, verificar si el DDL trigger (`ddl_command_end`) está disponible
|
||||
sin extensiones adicionales en esta versión de Postgres self-hosted:
|
||||
```bash
|
||||
docker exec -i supabase-db psql -U postgres -d postgres -c "
|
||||
SELECT event_object_schema, trigger_name
|
||||
FROM information_schema.triggers
|
||||
WHERE trigger_name LIKE '%ddl%' LIMIT 5;"
|
||||
```
|
||||
Reportar si es viable sin fricción. Si requiere configuración extra, documentarlo
|
||||
y omitirlo de esta etapa.
|
||||
|
||||
### 4.2 Migración principal
|
||||
Archivo: `supabase/migrations/YYYYMMDDHHMMSS_audit_log.sql`
|
||||
|
||||
Contenido en orden:
|
||||
1. Crear tabla `audit_log` con columnas definidas arriba.
|
||||
2. RLS en `audit_log`: solo `platform_admin` puede SELECT.
|
||||
3. Trigger `BEFORE UPDATE OR DELETE ON audit_log` → función que lanza excepción
|
||||
(append-only enforcement).
|
||||
4. Función `log_user_roles_change()` SECURITY DEFINER → INSERT en audit_log.
|
||||
5. Trigger AFTER INSERT/UPDATE/DELETE en `user_roles` → llama a la función.
|
||||
6. Función `log_survey_status_change()` SECURITY DEFINER → INSERT en audit_log
|
||||
solo cuando `NEW.status IS DISTINCT FROM OLD.status`.
|
||||
7. Trigger AFTER UPDATE en `surveys` → llama a la función.
|
||||
8. Si DDL trigger es viable (4.1): función + trigger DDL.
|
||||
9. Comentario al final documentando el gap SEC-003.
|
||||
|
||||
### 4.3 Verificación en VPS
|
||||
NO aplicar la migración — solo generar el archivo y reportar el SQL completo para
|
||||
revisión del director. El director aplica manualmente después de aprobar.
|
||||
|
||||
## 5. DoD antes de reportar
|
||||
- [ ] `git log --name-only -1` — solo el archivo de migración
|
||||
- [ ] SQL completo de la migración en el reporte (no resumido — el director
|
||||
necesita leerlo antes de aplicar)
|
||||
- [ ] Diagnóstico DDL trigger reportado (4.1)
|
||||
- [ ] Build sin errores: `npm run build`
|
||||
- [ ] Commit + push a Gitea
|
||||
- [ ] NO aplicado en VPS — esperar aprobación del director
|
||||
|
||||
## 6. Qué reportar
|
||||
```
|
||||
Diagnóstico DDL trigger (4.1):
|
||||
- ¿Viable sin extensiones? ✓/✗
|
||||
- Si no: razón y alternativa
|
||||
|
||||
SQL completo de la migración:
|
||||
[pegar el SQL íntegro — no resumido]
|
||||
|
||||
Verificado:
|
||||
- Build: ✓/✗
|
||||
- git log --name-only -1: [lista]
|
||||
- Commit: [hash]
|
||||
|
||||
NO aplicado en VPS — pendiente aprobación del director.
|
||||
```
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No aplicar la migración en el VPS sin aprobación explícita del director
|
||||
- ❌ No implementar logging de accesos a datos sensibles (deferido)
|
||||
- ❌ No agregar extensiones nuevas a Postgres
|
||||
- ❌ No tocar el formulario público, las RPCs de dashboard, ni el middleware de auth
|
||||
- ❌ No usar GRANT EXECUTE a PUBLIC — solo al rol específico que necesita cada función
|
||||
- ❌ No omitir el comentario del gap SEC-003 en la migración
|
||||
|
||||
## 8. Versionado
|
||||
```
|
||||
feat(compliance): audit_log append-only — user_roles y surveys.status [SEC-002]
|
||||
|
||||
Tabla audit_log con RLS (solo platform_admin), trigger BEFORE UPDATE/DELETE
|
||||
que aborta (append-only enforcement), triggers AFTER en user_roles y
|
||||
surveys.status. Patrón SECURITY DEFINER reutilizado de auth_user_roles.
|
||||
Gap SEC-003 (TRUNCATE por superusuario) documentado y aceptado.
|
||||
```
|
||||
123
sprints/sprint-5/ETAPA_CALIDAD_PROMPT.md
Normal file
123
sprints/sprint-5/ETAPA_CALIDAD_PROMPT.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# ETAPA CALIDAD — Contraste, mobile, conectividad, touch (Sprint 5)
|
||||
|
||||
## 1. Objetivo
|
||||
Resolver los 4 problemas de calidad identificados en la auditoría del 27 jun 2026,
|
||||
en orden de impacto. Un solo commit al terminar todo.
|
||||
|
||||
## 2. Contexto
|
||||
Diagnóstico base: auditoría de solidez/usabilidad (27 jun 2026, sin modificaciones).
|
||||
Todos los problemas tienen archivo y línea exacta identificados.
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar
|
||||
- **Contraste teal:** Opción B — `#43BBC8` queda SOLO para decoración (fondos,
|
||||
bordes, barras, elementos gráficos). Cuando se usa como COLOR DE TEXTO, reemplazar
|
||||
por `#494F4F` (carbón de marca). No crear variante oscura del teal.
|
||||
- **Mobile bottombar:** flex-wrap en el contenedor, no width fijo en los botones.
|
||||
- **Submit sin try/catch:** agregar manejo de error de red que muestre el estado
|
||||
'error' propio del wizard, no la pantalla cruda de React.
|
||||
- **Autocomplete touch:** agregar `onTouchEnd` como fallback junto al `onMouseDown`
|
||||
existente — no reemplazarlo.
|
||||
- Sin librerías nuevas en ningún fix.
|
||||
|
||||
## 4. Tareas en orden
|
||||
|
||||
### P1 — Contraste teal como texto (CRÍTICO — sistemático)
|
||||
En `app/globals.css`, reemplazar `color: var(--color-re-teal)` por
|
||||
`color: #494F4F` en estas clases específicas:
|
||||
- `.btn-secondary` (botón "Anterior") — línea ~380
|
||||
- `.wizard-section-number` ("Sección X de Y") — línea ~569
|
||||
- `.summary-code` — línea ~431
|
||||
- `.barrio-autocomplete-item--active` (texto del ítem activo) — línea ~319
|
||||
- `.consent-required-badge` — línea ~490
|
||||
|
||||
NO tocar usos de `var(--color-re-teal)` como `background-color`, `border-color`,
|
||||
`fill`, o en las clases `.db-*` del dashboard — esos son usos decorativos correctos.
|
||||
|
||||
Verificar después del cambio que no quedó ningún `color: var(--color-re-teal)` en
|
||||
clases del wizard (grep en globals.css sección wizard):
|
||||
```bash
|
||||
grep -n "color: var(--color-re-teal)" app/globals.css
|
||||
```
|
||||
Los únicos resultados aceptables son en sección `.db-*` del dashboard.
|
||||
|
||||
### P2 — Submit sin try/catch (red se cae al enviar)
|
||||
En `components/wizard-form.tsx`, función `handleSubmit` (líneas ~212-226):
|
||||
Envolver el `await submitSurvey(...)` en try/catch. Si el catch recibe un error
|
||||
de red (fetch failed, network error), setear el estado del wizard a 'error' con
|
||||
mensaje accionable: "No pudimos enviar tu respuesta. Verificá tu conexión e
|
||||
intentá de nuevo." El botón de reintento ya existe en el estado 'error'
|
||||
(wizard-form.tsx:136-152) — solo hay que llegar a ese estado correctamente.
|
||||
No crear `app/error.tsx` — el estado de error propio del wizard es suficiente.
|
||||
|
||||
### P3 — Botones bottombar en mobile ≤480px
|
||||
En `app/globals.css`, sección `.wizard-bottombar` (~línea 653):
|
||||
Agregar `flex-wrap: wrap` al contenedor `.wizard-bottombar`.
|
||||
En el breakpoint `@media (max-width: 480px)`:
|
||||
- `.wizard-bottombar` → `flex-direction: column-reverse` (Siguiente arriba, Anterior abajo)
|
||||
- `.btn` dentro del bottombar → `width: 100%`
|
||||
- El span hint entre los botones → `order: 3` para que quede al final
|
||||
`column-reverse` porque el botón primario (Siguiente/Enviar) debe estar arriba
|
||||
en mobile — el pulgar llega más fácil al centro que a la base.
|
||||
|
||||
### P4 — Autocomplete touch frágil
|
||||
En `components/widgets/barrio-autocomplete-field.tsx` y
|
||||
`components/widgets/ciudad-select-field.tsx`:
|
||||
En el handler de selección de ítem del dropdown, agregar `onTouchEnd` junto
|
||||
al `onMouseDown` existente (no reemplazarlo). El `onTouchEnd` debe llamar
|
||||
`e.preventDefault()` antes de ejecutar la selección para evitar el blur
|
||||
sintético que cierra la lista antes de que se procese la selección.
|
||||
|
||||
## 5. DoD antes de reportar
|
||||
- [ ] `git log --name-only -1` — listar todos los archivos tocados
|
||||
- [ ] grep de verificación P1:
|
||||
```bash
|
||||
grep -n "color: var(--color-re-teal)" app/globals.css
|
||||
```
|
||||
Solo resultados en sección `.db-*`
|
||||
- [ ] Build sin errores: `npm run build`
|
||||
- [ ] P2 verificado: en `handleSubmit`, el catch existe y setea estado 'error'
|
||||
con mensaje accionable (pegar el bloque try/catch completo en el reporte)
|
||||
- [ ] P3 verificado: describir el CSS resultante del bottombar en mobile
|
||||
- [ ] P4 verificado: describir el handler resultante de selección en autocomplete
|
||||
- [ ] Commit: `fix(quality): contraste teal, submit red, bottombar mobile, autocomplete touch`
|
||||
- [ ] Push a Gitea
|
||||
|
||||
## 6. Qué reportar
|
||||
```
|
||||
P1 — Contraste:
|
||||
- Clases modificadas: [lista con línea antes → después]
|
||||
- grep verificación: [output]
|
||||
|
||||
P2 — Submit try/catch:
|
||||
- Bloque handleSubmit completo después del fix: [código]
|
||||
|
||||
P3 — Bottombar mobile:
|
||||
- CSS resultante de .wizard-bottombar y el breakpoint: [código]
|
||||
|
||||
P4 — Autocomplete touch:
|
||||
- Handler de selección resultante (onMouseDown + onTouchEnd): [código]
|
||||
|
||||
Verificado:
|
||||
- Build: ✓/✗
|
||||
- git log --name-only -1: [lista]
|
||||
- Commit: [hash]
|
||||
```
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No tocar `var(--color-re-teal)` en usos decorativos (background, border, fill)
|
||||
- ❌ No tocar clases `.db-*` del dashboard
|
||||
- ❌ No crear `app/error.tsx`
|
||||
- ❌ No reemplazar `onMouseDown` — agregar `onTouchEnd` junto a él
|
||||
- ❌ No agregar librerías nuevas
|
||||
- ❌ No hacer deploy — solo commit y push a Gitea
|
||||
- ❌ No mezclar con los fixes rápidos de la etapa anterior
|
||||
|
||||
## 8. Versionado
|
||||
```
|
||||
fix(quality): contraste teal, submit red, bottombar mobile, autocomplete touch
|
||||
|
||||
P1: color de texto teal → #494F4F en 5 clases del wizard (WCAG AA).
|
||||
P2: try/catch en handleSubmit — error de red muestra estado error del wizard.
|
||||
P3: flex-wrap + column-reverse en bottombar ≤480px.
|
||||
P4: onTouchEnd fallback en autocomplete barrio y ciudad.
|
||||
```
|
||||
47
sprints/sprint-5/ETAPA_FIXES_RAPIDOS_PROMPT.md
Normal file
47
sprints/sprint-5/ETAPA_FIXES_RAPIDOS_PROMPT.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# ETAPA — Fixes rápidos: toggle password + card Talento en riesgo
|
||||
|
||||
## 1. Objetivo
|
||||
Dos fixes visuales independientes y quirúrgicos. Scope estricto.
|
||||
|
||||
## 2. DECISIONES YA TOMADAS — no consultar
|
||||
- Toggle de contraseña: solo en el campo password del login, sin librería nueva.
|
||||
- Card Talento en riesgo: eliminar el gradiente, quedar igual que los demás cards
|
||||
(background blanco, sin distinción especial de fondo).
|
||||
|
||||
## 3. Tareas
|
||||
|
||||
### Fix A — Toggle "ver contraseña" en /login
|
||||
En `app/login/page.tsx`:
|
||||
- Agregar estado `showPassword` (useState).
|
||||
- El input password alterna entre `type="password"` y `type="text"`.
|
||||
- Botón inline dentro del campo (position absolute o flex) con ícono SVG
|
||||
simple de ojo (inline, sin librería). Accesible: `aria-label="Mostrar contraseña"` /
|
||||
`aria-label="Ocultar contraseña"` según estado. `type="button"` para no submitear.
|
||||
- Estilo consistente con el formulario existente.
|
||||
|
||||
### Fix B — Card Talento en riesgo sin gradiente
|
||||
En `app/globals.css`, clase `.db-widget-star`:
|
||||
- Eliminar la propiedad `background` (el gradiente actual).
|
||||
- El card hereda `background: #FFFFFF` de `.db-widget` — mismo que los demás.
|
||||
- NO tocar `border-color: var(--color-re-teal)` — mantener el borde distintivo.
|
||||
- NO tocar `.db-widget-star .db-widget-title` — mantener el título en teal.
|
||||
|
||||
## 4. DoD antes de reportar
|
||||
- [ ] `git log --name-only -1` — solo `app/login/page.tsx` y `app/globals.css`
|
||||
- [ ] Toggle password funciona: click muestra/oculta, aria-label cambia
|
||||
- [ ] Card Talento en riesgo: fondo blanco igual que los demás cards
|
||||
- [ ] Build sin errores: `npm run build`
|
||||
- [ ] Commit: `fix(ui): toggle password en login + fondo blanco card talento`
|
||||
- [ ] Push a Gitea
|
||||
|
||||
## 5. Qué reportar
|
||||
- `git log --name-only -1`
|
||||
- Línea exacta eliminada en `.db-widget-star`
|
||||
- Build: ✓/✗
|
||||
|
||||
## 6. Qué NO hacer
|
||||
- ❌ No tocar otros campos del formulario de login
|
||||
- ❌ No tocar otros widgets del dashboard
|
||||
- ❌ No agregar librerías de iconos
|
||||
- ❌ No modificar el borde teal ni el título teal del card estrella
|
||||
- ❌ No hacer deploy — solo commit y push
|
||||
106
sprints/sprint-5/INSUMOS_AUDIT_LOG.md
Normal file
106
sprints/sprint-5/INSUMOS_AUDIT_LOG.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# INSUMOS — Etapa Audit Log append-only (Sprint compliance, SEC-002)
|
||||
|
||||
> Material de contexto para que Claude (Projects, arquitecto) redacte el ETAPA_PROMPT.md
|
||||
> de esta etapa. No es el prompt en sí — es lo que el arquitecto necesita para escribirlo
|
||||
> bien, sin tener que releer todo el repo.
|
||||
|
||||
---
|
||||
|
||||
## 1. Por qué esta etapa, y por qué este alcance
|
||||
|
||||
`BACKLOG.md` (SEC-002) y `COMPLIANCE.md` §5.1 piden, dentro del sprint de compliance,
|
||||
un "registro de auditoría append-only (log inmutable de operaciones sensibles)". El
|
||||
director priorizó esta pieza primero, antes de hashing de integridad, trazabilidad de
|
||||
cambios o cifrado en reposo (esas quedan para etapas futuras del mismo sprint).
|
||||
|
||||
COMPLIANCE.md §5.1 pide loggear 3 cosas: accesos a datos sensibles, cambios de
|
||||
configuración, y ejecución de migraciones. Evalué las 3 y propongo **acotar el alcance
|
||||
de esta etapa a 2 de las 3**:
|
||||
|
||||
1. **Migraciones (DDL)** — factible con un event trigger nativo de Postgres
|
||||
(`ddl_command_end`), sin tocar el flujo de deploy.
|
||||
2. **Cambios de configuración** — triggers `AFTER INSERT/UPDATE/DELETE` en
|
||||
`user_roles` (altas/bajas/cambios de `app_role`/`org_id`) y en `surveys` cuando
|
||||
cambia `status` (activación/desactivación de encuestas).
|
||||
3. **Acceso a datos sensibles — propongo DEFERIR explícitamente.** Verificado en
|
||||
COMPLIANCE.md §3.2: hoy ningún camino de código (ni `/admin/responses` ni las RPCs
|
||||
`rpc_me_*`) lee columnas `is_sensitive=true`. Instrumentar "acceso" sin que exista
|
||||
un acceso real sería simular cobertura. Cuando se construya el primer camino que sí
|
||||
lea sensibles, ESE PR debe agregar su propio log de acceso junto con el código que
|
||||
lo necesita — no antes.
|
||||
|
||||
El arquitecto puede confirmar o ajustar este recorte; lo presento como punto de partida,
|
||||
no como cerrado (esto todavía no se aprobó con el director, se está armando el prompt).
|
||||
|
||||
---
|
||||
|
||||
## 2. Decisiones de diseño que ya tienen forma (para que el prompt las fije)
|
||||
|
||||
- **Tabla nueva:** `public.audit_log` — append-only real:
|
||||
- Ningún rol (`anon`, `authenticated`, ni `service_role` vía API) tiene
|
||||
`INSERT`/`UPDATE`/`DELETE` directo. Solo escriben funciones `SECURITY DEFINER`
|
||||
de los triggers, propiedad de `postgres` (mismo patrón que
|
||||
`custom_access_token_hook`, ver `supabase/migrations/20260627000001_auth_user_roles.sql`).
|
||||
- Trigger `BEFORE UPDATE OR DELETE ON audit_log` que aborta con excepción — segunda
|
||||
barrera, defensa en profundidad (no a prueba de un operador con `psql -U postgres`
|
||||
en el VPS — ver gap conocido abajo).
|
||||
- Lectura: RLS, solo `platform_admin` (reusar `current_app_role()`, ver
|
||||
`supabase/migrations/20260531000003_rls_policies.sql:17`).
|
||||
- **Columnas sugeridas (a validar por el arquitecto):** `id bigserial pk`,
|
||||
`occurred_at timestamptz default now()`, `actor_role text`, `actor_user_id uuid`,
|
||||
`action text`, `target_table text`, `target_id text`, `detail jsonb`.
|
||||
- **Gap conocido a documentar, no a resolver en esta etapa:** esto no protege contra
|
||||
`TRUNCATE`/`DROP` ejecutado por un superusuario directo en el VPS — mismo gap ya
|
||||
registrado en `SEC-003` (BACKLOG.md). Append-only a nivel de aplicación/RLS, no a
|
||||
prueba de acceso root a la base.
|
||||
|
||||
---
|
||||
|
||||
## 3. Convenciones del repo que el prompt debe respetar
|
||||
|
||||
- **Migraciones:** `supabase/migrations/`, naming `YYYYMMDDHHMMSS_descripcion.sql`.
|
||||
Última migración aplicada: `20260627000001_auth_user_roles.sql`. La próxima debe
|
||||
fecharse después de esa.
|
||||
- **Patrón SECURITY DEFINER + GRANT EXECUTE puntual:** ver
|
||||
`custom_access_token_hook` en `20260627000001_auth_user_roles.sql` — función
|
||||
`SECURITY DEFINER STABLE`, `GRANT EXECUTE ... TO supabase_auth_admin` (rol
|
||||
específico, no `PUBLIC`). Mismo patrón a replicar para las funciones de trigger
|
||||
de `audit_log`.
|
||||
- **Helpers RLS existentes a reusar, no reinventar:** `public.current_app_role()`
|
||||
y `public.current_org_id()` (`20260531000003_rls_policies.sql:17-30`).
|
||||
- **Verificación obligatoria antes de aplicar en VPS:** mostrar el SQL final completo
|
||||
y esperar OK explícito del director antes de correrlo en producción (ya es práctica
|
||||
establecida en este proyecto — ver incidente VPS del 27 jun, recuperación documentada
|
||||
en `docs/estado/ESTADO_PROYECTO_JUNIO_2026.md` §2.1).
|
||||
- **Formato esperado del archivo de etapa:** seguir la estructura de
|
||||
`sprints/sprint-4/ETAPA_AUTH_PROMPT.md` o `ETAPA_4G_PROMPT.md` — Objetivo,
|
||||
Decisiones ya tomadas (no consultar), Tareas, DoD, Qué reportar (datos concretos,
|
||||
no "funcionó"), Qué NO hacer, Versionado (mensaje de commit sugerido con tag
|
||||
`[compliance]` o similar).
|
||||
|
||||
---
|
||||
|
||||
## 4. Qué NO debería pedir el prompt (fuera de alcance de esta etapa)
|
||||
|
||||
- Hashing de integridad SHA-256 (siguiente pieza del mismo sprint, etapa separada).
|
||||
- Cifrado en reposo (es trabajo de VPS/infraestructura, no de migración SQL).
|
||||
- Trazabilidad histórica completa de cambios de datos (triggers de historial / WAL) —
|
||||
etapa separada también.
|
||||
- Logging de acceso a datos sensibles (ver §1, punto 3 — deferido a cuando exista
|
||||
código que lea sensibles).
|
||||
- pgaudit o cualquier extensión nueva — no está en el Supabase self-hosted actual,
|
||||
agregar una extensión es Nivel 3 aparte (afecta infraestructura/dependencias).
|
||||
|
||||
---
|
||||
|
||||
## 5. Referencias
|
||||
|
||||
- `COMPLIANCE.md` §5 (roadmap auditabilidad), §5.2 (por qué no blockchain), §3.2
|
||||
(qué es sensible y cómo se protege hoy).
|
||||
- `BACKLOG.md` → SEC-002, SEC-003 (gap de TRUNCATE), SEC-004 (bypass REST, contexto
|
||||
de otro riesgo residual ya aceptado, mismo espíritu de "documentar en vez de
|
||||
sobre-construir").
|
||||
- `TECH_DEBT.md` → TECH-DEBT-008 (resuelto 27 jun, ejemplo de cómo se cierra un ítem).
|
||||
- `docs/estado/ESTADO_PROYECTO_JUNIO_2026.md` — estado general post-release v3.0.0
|
||||
+ incidente VPS, para que el arquitecto tenga el panorama completo antes de escribir
|
||||
el prompt.
|
||||
Reference in New Issue
Block a user