Instrumento v2: 44 preguntas + widget text_short
This commit is contained in:
75
sprints/sprint-1/ETAPA_1D_PROMPT.md
Normal file
75
sprints/sprint-1/ETAPA_1D_PROMPT.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# ETAPA 1D — Actualización del instrumento a v2 (44 preguntas)
|
||||
|
||||
> Implementa la revisión del instrumento hecha por la colega del equipo.
|
||||
> Leé: `CLAUDE.md`, `DATA_MODEL.md`, `SEED_SPEC.md` (v2) y este prompt.
|
||||
|
||||
## 1. Objetivo
|
||||
Actualizar el seed y la app para que reflejen el instrumento v2 de 44 preguntas según `SEED_SPEC.md`.
|
||||
Dos partes: (A) base de datos en Supabase Cloud, (B) widget `text_short` en el formulario Next.js.
|
||||
|
||||
## 2. Contexto previo
|
||||
La Etapa 1C cerró la fundación de datos con 34 preguntas. Esta etapa la actualiza a 44 por decisión
|
||||
del equipo. El esquema de tablas NO cambia — solo cambia el contenido del seed. Las columnas `qi_city`
|
||||
y `qi_country` se agregan en un sprint futuro (no en esta etapa).
|
||||
|
||||
## 3. Decisiones ya tomadas
|
||||
- 44 preguntas con códigos únicos según `SEED_SPEC.md`. Códigos corregidos: 6.4 y 6.5 (las
|
||||
que en el HTML original eran 6.2 y 6.3 duplicados).
|
||||
- A1/A2/A3 son tipo `text_short` (texto libre corto), sin opciones. Se guardan en `answers`.
|
||||
- Pregunta 6.2 = `multiple_choice max_select=3` (NO ranking todavía).
|
||||
- Pregunta 3.4 (salary) = `is_sensitive=true`. Ninguna vista de org_admin la expone.
|
||||
- Condicionales: cond-disc-propia (1.1=Sí → 1.2–1.5), cond-familiar (1.6=Sí → 1.7–1.12),
|
||||
cond-adulto-mayor (1.13=Sí → 1.14–1.16). Reglas en la pregunta dependiente.
|
||||
- Secciones A, 2, 3, 4, 5, 6, 7 son top-level (siempre visibles).
|
||||
- RE-INDEX es solo una nota en los textos de `SEED_SPEC.md`; NO agrega columna al esquema.
|
||||
|
||||
## 4. Tareas
|
||||
|
||||
### 4A — Base de datos
|
||||
1. Reescribir `supabase/seed.sql` con las 44 preguntas de `SEED_SPEC.md`: tipos, opciones,
|
||||
`max_select` (6.2=3), `is_sensitive` (marcar las indicadas ✅), `conditional_rules` en
|
||||
dependientes, `required=false` para todas (el equipo decidió capturar todas las respuestas
|
||||
para validar el concepto, la obligatoriedad real se define luego).
|
||||
2. Aplicar a Cloud: `supabase db push --include-seed` o equivalente.
|
||||
3. Verificar: `SELECT count(*) FROM questions` = **44**; códigos únicos sin duplicados;
|
||||
pregunta `3.4` con `is_sensitive=true`; los 3 bloques condicionales tienen las reglas correctas.
|
||||
|
||||
### 4B — App (formulario Next.js)
|
||||
4. En `components/widgets/`, crear `text-short-field.tsx`: un `<input type="text">` accesible
|
||||
(label asociado, aria-required, aria-invalid) con el mismo estilo visual que los otros widgets.
|
||||
5. En `components/question-renderer.tsx`: registrar `text_short` y renderizarlo con el nuevo widget.
|
||||
6. Verificar que A1/A2/A3 se renderizan como campos de texto, y que el formulario sigue
|
||||
funcionando end-to-end (cargar las 44 preguntas desde Supabase, condicionales B/C/D, envío
|
||||
al resumen).
|
||||
|
||||
## 5. Definition of Done
|
||||
- [ ] `SELECT count(*) FROM questions` = 44 contra Cloud.
|
||||
- [ ] Códigos únicos: `SELECT code, count(*) FROM questions GROUP BY code HAVING count(*) > 1` = vacío.
|
||||
- [ ] `3.4` tiene `is_sensitive=true`.
|
||||
- [ ] Pregunta `A1` con `type='text_short'` y `options=null`.
|
||||
- [ ] `npm run build` limpio.
|
||||
- [ ] El formulario en `http://localhost:3000` muestra A1/A2/A3 como campos de texto.
|
||||
- [ ] B1=Sí despliega 1.2–1.5; C1=Sí despliega 1.7–1.12; D1=Sí despliega 1.14–1.16.
|
||||
- [ ] `git diff --name-only HEAD` solo con `supabase/seed.sql` + archivos de la app.
|
||||
|
||||
## 6. Validaciones automatizadas mandatorias
|
||||
- Conteo de preguntas: 44 ✓
|
||||
- Sin códigos duplicados (query arriba) ✓
|
||||
- `is_sensitive` en exactamente las preguntas marcadas en SEED_SPEC.md ✓
|
||||
- Al menos una conditional_rule en cada una de las 16 preguntas dependientes ✓
|
||||
- `npm run build` sin errores ✓
|
||||
- Si CUALQUIERA falla: NO declarar lista. Reportar y esperar.
|
||||
|
||||
## 7. Qué reportar
|
||||
- Conteo de preguntas y confirmación de código únicos.
|
||||
- Confirmación de que A1/A2 se renderizan correctamente en el formulario.
|
||||
- Salida de `git diff --name-only HEAD`.
|
||||
- Hallazgos, asumidos, decisiones por iniciativa (con nivel).
|
||||
|
||||
## 8. Qué NO hacer
|
||||
- NO agregar columnas `qi_city`/`qi_country` al esquema (es Sprint 3).
|
||||
- NO implementar widget de ranking (está en backlog).
|
||||
- NO tocar migraciones 001–007.
|
||||
- NO cambiar la lógica de RLS ni los policies.
|
||||
- NO exponer `3.4` en ninguna vista o función que sea accesible a org_admin.
|
||||
- Ante conflicto restricción vs build limpio: **detenerse y reportar**.
|
||||
@@ -17,8 +17,9 @@ distintos y conviene auditarlos por separado:
|
||||
|
||||
## Alcance
|
||||
- **Etapa 2A — Motor del formulario:** lee la encuesta live desde Supabase (anon key), renderiza las
|
||||
34 preguntas con el widget correcto por tipo, ejecuta la lógica condicional, valida, mobile-first,
|
||||
WCAG AA, resiliencia lite. **Al enviar muestra un resumen; NO escribe en la base.**
|
||||
**44 preguntas** (instrumento v2, Jun 2026) con el widget correcto por tipo (`single_choice`,
|
||||
`multiple_choice`, `rating`, `text_long`, `text_short`), ejecuta la lógica condicional, valida,
|
||||
mobile-first, WCAG AA, resiliencia lite. **Al enviar muestra un resumen; NO escribe en la base.**
|
||||
- **Etapa 2B — Consentimiento + persistencia:** pantalla de consentimiento granular versionado
|
||||
(ADR-003) con texto honesto sobre anonimato y no-revocación, que crea el `consent_record` y recién
|
||||
ahí escribe `response` + `answers`, con dedup por `ip_hash`.
|
||||
@@ -34,15 +35,17 @@ distintos y conviene auditarlos por separado:
|
||||
- El formulario corre como **`anon`** (sin login). `service_role` NUNCA en el cliente (regla 9).
|
||||
- El respondente nunca se ata a un IdP (regla 10 / ADR-004).
|
||||
- `options`: array para `single_choice`/`multiple_choice`, objeto `{min,max,min_label,max_label}` para `rating`.
|
||||
- Condicionales en la pregunta dependiente; disparador value `'Sí'` (B1/C1/D1).
|
||||
- Condicionales en la pregunta dependiente; disparador value `'Sí'` (triggers: 1.1/1.6/1.13).
|
||||
- Tipos de widget: `single_choice`→radio, `multiple_choice`→checkbox, `rating`→escala 1-5,
|
||||
`text_long`→textarea, `text_short`→input de texto de una línea (A1/A2/A3).
|
||||
- WCAG 2.2 AA es pilar (el sujeto es la discapacidad), no checkbox.
|
||||
- El mockup `formulario_mapeo.html` es el **contrato visual** (look y flujo ya aprobados); 2A lo
|
||||
replica pero data-driven desde el esquema, no hardcodeado.
|
||||
|
||||
## Criterios de éxito (Etapa 2A)
|
||||
- [ ] Renderiza las 34 preguntas leídas desde Supabase (no hardcodeadas).
|
||||
- [ ] Lógica condicional: B1/C1/D1 = "Sí" despliega sus bloques; "No"/"Prefiero no decir" los oculta y limpia.
|
||||
- [ ] H1 limita a 3 selecciones; `rating` (F3/G4) maneja el formato objeto.
|
||||
- [ ] Renderiza las **44 preguntas** leídas desde Supabase (no hardcodeadas).
|
||||
- [ ] Lógica condicional: 1.1/1.6/1.13 = "Sí" despliegan sus bloques; "No"/"Prefiero no decir" ocultan y limpian.
|
||||
- [ ] 6.2 limita a 3 selecciones; `rating` (3.5/4.3) maneja formato objeto; A1/A2/A3 son `text_short`.
|
||||
- [ ] Mobile-first fluido + navegable solo con teclado + WCAG AA base.
|
||||
- [ ] Refresh no pierde lo cargado (resiliencia lite).
|
||||
- [ ] Enviar muestra resumen; no escribe en la base.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
## 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,
|
||||
`anon` key, renderiza las **44 preguntas** (instrumento v2) 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
|
||||
La fundación de datos (Sprint 1) está cerrada y verificada en Cloud: esquema, RLS, seed real de **44
|
||||
preguntas** (instrumento v2, Jun 2026), 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.
|
||||
@@ -23,22 +23,25 @@ del proyecto) es el contrato visual: replicar su look y flujo, pero leyendo del
|
||||
`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.
|
||||
para `rating` (3.5, 4.3); **null** para `text_short`/`text_long`. Manejar las tres formas.
|
||||
- Tipos de widget: `single_choice`→radio, `multiple_choice`→checkbox, `rating`→escala 1-5,
|
||||
`text_long`→textarea, `text_short`→`<input type="text">` de una línea (A1/A2/A3: País, Ciudad, Sede).
|
||||
- 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).
|
||||
value:'Sí', action:'show'}`. Triggers: **1.1** (→1.2–1.5), **1.6** (→1.7–1.12), **1.13** (→1.14–1.16).
|
||||
Con "No"/"Prefiero no decir" ocultar **y limpiar** las respuestas del bloque.
|
||||
- 6.2 = `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
|
||||
2. Leer desde Supabase (anon) la encuesta `status='live'` + sus 44 `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.
|
||||
`max_select`), `rating`→escala 1-5 (usando el objeto options), `text_long`→textarea,
|
||||
`text_short`→input de una línea (A1/A2/A3).
|
||||
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`
|
||||
@@ -54,17 +57,17 @@ del proyecto) es el contrato visual: replicar su look y flujo, pero leyendo del
|
||||
|
||||
## 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.
|
||||
- [ ] Se renderizan las **44 preguntas**; los 3 bloques condicionales aparecen/desaparecen con 1.1/1.6/1.13.
|
||||
- [ ] 6.2 no deja marcar más de 3; rating (3.5/4.3) funciona con el formato objeto; A1/A2/A3 son inputs de texto.
|
||||
- [ ] 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).
|
||||
- La carga lee **44** preguntas desde la base (logear el conteo), no un número hardcodeado.
|
||||
- Las **13 condicionales** se evalúan desde `conditional_rules` (no con `if` cableados por código).
|
||||
- `max_select` de 6.2 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_*`.
|
||||
|
||||
Reference in New Issue
Block a user