Files
motor-de-encuestas/sprints/sprint-2/ETAPA_2A_PROMPT.md
2026-06-02 10:27:52 -03:00

88 lines
6.3 KiB
Markdown

# ETAPA 2A — Motor del formulario (render + lógica condicional, sin persistir)
> Prompt versionado (Patrón B.1). Primera pieza de cara al usuario. Claude Code lo ejecuta tras leer
> `CLAUDE.md`, `DATA_MODEL.md`, `SEED_SPEC.md`, `ADR-002`, `sprints/sprint-2/BRIEF.md` y este prompt.
## 1. Objetivo
Construir el motor del formulario: una app Next.js que **lee** la encuesta live desde Supabase con la
`anon` key, renderiza las 34 preguntas con el widget correcto por tipo, ejecuta la lógica condicional,
valida, es mobile-first y accesible (WCAG AA), no pierde lo cargado al refrescar, y al enviar muestra
un **resumen de respuestas**. **NO escribe nada en la base** (eso es 2B).
## 2. Contexto previo
La fundación de datos (Sprint 1) está cerrada y verificada en Cloud: esquema, RLS, seed real de 34
preguntas, k-anonimato. El proyecto Supabase está linkeado. Todavía no existe app frontend — esta
etapa la scaffolding. `responses.consent_record_id` es `NOT NULL`, por eso 2A **no** persiste: la
escritura requiere el consentimiento, que es 2B. El mockup `formulario_mapeo.html` (en el historial
del proyecto) es el contrato visual: replicar su look y flujo, pero leyendo del esquema, no hardcoded.
## 3. Decisiones ya tomadas (no consultar de nuevo)
- Stack: **Next.js (App Router)** + **react-hook-form + zod** + **@supabase/supabase-js** (o
`@supabase/ssr`). Sin librería de gráficos (eso es Sprint 3).
- El cliente usa la **`anon` key** (pública, respeta RLS), desde `NEXT_PUBLIC_SUPABASE_URL` y
`NEXT_PUBLIC_SUPABASE_ANON_KEY` en `.env.local`. **El `service_role` NUNCA va a una variable
`NEXT_PUBLIC_` ni al cliente** (regla 9 de CLAUDE.md).
- `options`: **array** para `single_choice`/`multiple_choice`; **objeto** `{min,max,min_label,max_label}`
para `rating` (F3, G4). Manejar las dos formas.
- Condicionales: cada pregunta dependiente trae `conditional_rules` con `{if_question, op:'eq',
value:'Sí', action:'show'}`. Mostrar solo si el disparador (B1/C1/D1) = "Sí"; con "No"/"Prefiero no
decir" ocultar **y limpiar** las respuestas de ese bloque.
- H1 = `multiple_choice` con `max_select=3` (impedir marcar más de 3).
- 2A **no** escribe `responses`/`answers`/`consent_records`. Al enviar → pantalla de resumen.
## 4. Tareas
1. Scaffolding de la app Next.js (App Router) en el repo. Setear el cliente Supabase con la `anon`
key desde `.env.local`. Crear `.env.example` (sin valores) documentando las dos variables.
2. Leer desde Supabase (anon) la encuesta `status='live'` + sus 34 `questions` ordenadas por
`position`, con sus `options`, `type`, `max_select`, `is_sensitive`, `conditional_rules`.
(Si la encuesta seed no está `live`, dejar documentado que el director debe setearla a `live`, o
leer por id — NO cambiar el estado desde el cliente.)
3. Renderizar cada tipo con su widget: `single_choice`→radios, `multiple_choice`→checkboxes (con tope
`max_select`), `rating`→escala 1-5 (usando el objeto options), `text_long`→textarea.
4. Motor de lógica condicional data-driven: evaluar `conditional_rules` para mostrar/ocultar; al
ocultar un bloque, limpiar sus valores.
5. Validación con zod (tipos correctos; respetar `max_select`; `required` según el campo `required`
de cada pregunta — hoy todas `false`, así que validación liviana).
6. UX: mobile-first fluida, replicando el contrato visual del mockup (secciones, progreso, estados de
opción). Accesibilidad WCAG 2.2 AA: HTML semántico, `<label>` asociado a cada input, navegación y
operación completa por teclado, foco visible, contraste suficiente, y anunciar a lectores de
pantalla la aparición/desaparición de los bloques condicionales (`aria-expanded`/`aria-live`).
7. Resiliencia lite: persistir las respuestas en progreso en `localStorage` (clave por `survey_id`)
para sobrevivir un refresh; limpiar al enviar. No guardar nada identificable.
8. Al enviar: validar y mostrar una **pantalla de resumen** con las respuestas dadas. NO llamar a la
base para escribir.
## 5. Definition of Done
- [ ] `npm run dev` levanta la app y carga la encuesta desde Supabase (anon).
- [ ] Se renderizan las 34 preguntas; los 3 bloques condicionales aparecen/desaparecen con B1/C1/D1.
- [ ] H1 no deja marcar más de 3; rating funciona con el formato objeto.
- [ ] Navegable y operable solo con teclado; pasa una auditoría a11y básica (axe o Lighthouse) sin errores AA.
- [ ] Refresh conserva lo cargado; enviar limpia el almacenamiento y muestra el resumen.
- [ ] `npm run build` limpio; ninguna variable `NEXT_PUBLIC_` contiene el `service_role`.
- [ ] `git diff --name-only HEAD` solo con archivos de la app nueva + `.env.example`.
## 6. Validaciones automatizadas mandatorias (antes de entregar)
- La carga lee **34** preguntas desde la base (logear el conteo), no un número hardcodeado.
- Las 12 condicionales se evalúan desde `conditional_rules` (no con `if` cableados por código).
- `max_select` de H1 se respeta en runtime (test o verificación manual reproducible).
- `npm run build` sin errores.
- Auditoría a11y (axe-core o `lighthouse`) sobre la página del formulario: **0 violaciones de nivel AA**.
- Grep de seguridad: `service_role` NO aparece en ningún archivo del cliente ni en `NEXT_PUBLIC_*`.
- Si CUALQUIERA falla: NO declarar lista la etapa. Reportar y esperar.
## 7. Qué reportar
- **Verificado:** conteo de preguntas cargadas, resultado de la auditoría a11y (con la herramienta y
el puntaje/violaciones), confirmación de que el build pasa, salida del grep de `service_role`.
- **Asumido / Hallazgos / Decisiones por iniciativa** (clasificadas por nivel).
- **`git diff --name-only HEAD`** pegado.
- Indicar cómo levantar la app localmente para que el director la abra (el comando y la URL).
## 8. Qué NO hacer
- NO escribir en `responses`, `answers` ni `consent_records` (es 2B).
- NO construir el flujo de consentimiento ni el dashboard.
- NO hardcodear las preguntas: todo se lee del esquema.
- NO poner el `service_role` en el cliente ni en variables `NEXT_PUBLIC_`.
- NO recolectar ni guardar nada identificable del respondente (sin `respondent_id`, sin fingerprinting,
sin claves de localStorage que identifiquen a la persona). El anonimato es la premisa.
- NO cambiar el estado de la encuesta (`draft`/`live`) desde el cliente.
- Ante conflicto entre una restricción y "que compile/funcione": **detenerse y reportar**.