feat(engine): motor de resolución de flujo declarativo [4C]
This commit is contained in:
@@ -1,96 +1,143 @@
|
||||
# BRIEF — Sprint 4: Formulario Wizard v3
|
||||
# BRIEF — Sprint 4: Formulario Wizard v3 (esquema declarativo)
|
||||
|
||||
> Versión objetivo: **3.0.0** · Rama de trabajo: `main` · Preservación: `v2.1.0-stable`
|
||||
> Versión objetivo: **3.0.0** · Rama: `main` · Preservación: `v2.1.0-stable`
|
||||
|
||||
---
|
||||
|
||||
## Objetivo en una frase
|
||||
Reescribir el formulario de encuesta como un wizard pregunta-por-pantalla con
|
||||
mini-wizard anidado para vínculos familiares, sobre un nuevo instrumento de 10
|
||||
secciones, habilitando análisis de datos multi-instancia.
|
||||
Construir el formulario de cuidadores como un wizard pregunta-por-pantalla cuyo
|
||||
flujo, instancias y condiciones se definen en un **esquema declarativo** (datos),
|
||||
renderizado por un frontend sin lógica de negocio — funcionando para los dos
|
||||
clientes inminentes, y en la dirección arquitectónica del motor de formularios.
|
||||
|
||||
---
|
||||
|
||||
## Decisión arquitectónica rectora (ADR pendiente de registrar)
|
||||
|
||||
**El motor de formularios es el producto; la encuesta de cuidadores es su primer
|
||||
caso de uso.** Por lo tanto, la lógica del flujo del formulario vive en una capa
|
||||
de **definición declarativa** (datos), no en el presentador ni dispersa en columnas
|
||||
sueltas de la base.
|
||||
|
||||
### Estrategia en dos fases (deuda intencional, registrada — no oculta)
|
||||
|
||||
**Fase 1 (este sprint, para el deadline de 10-20 días):**
|
||||
Se construye el formulario de cuidadores con su flujo definido como esquema
|
||||
declarativo persistido. El frontend es un renderer de ese esquema — sin reglas
|
||||
hardcodeadas. La resolución de "qué pantalla sigue" se calcula desde esquema +
|
||||
estado. NO se construye todavía el intérprete genérico universal.
|
||||
|
||||
**Fase 2 (sprints posteriores, post-deadline):**
|
||||
Se extrae el intérprete a un motor genérico de formularios. Como el formulario de
|
||||
cuidadores ya está definido como esquema declarativo, se vuelve el primer caso de
|
||||
prueba del motor sin reescribir el contrato. Registrado en `BACKLOG.md`.
|
||||
|
||||
**Por qué esta estrategia:** entrega valor en plazo sin generar retrabajo. La
|
||||
lógica en datos (no en presentación) es lo que habilita guardado parcial,
|
||||
reanudación, validación consistente y progreso preciso — la mejor UX — y lo que
|
||||
hace que la Fase 2 sea generalización, no reescritura.
|
||||
|
||||
---
|
||||
|
||||
## Alcance (qué entra)
|
||||
- Nuevo modelo de datos: columna `instance_id` en `answers`, nueva constraint única
|
||||
- Nuevo instrumento v3 (10 secciones, ~38 preguntas base + preguntas por instancia)
|
||||
- Motor de wizard: grafo de pantallas, navegación, estado, instancias dinámicas
|
||||
- Componente de pantalla única (una pregunta por pantalla)
|
||||
- Mini-wizard anidado para vínculos familiares (pregunta 4.1 → instancias)
|
||||
- Autocomplete de barrios desde dataset INE/DGEEC (`geo_paraguay.json`)
|
||||
- Esquema declarativo del formulario de cuidadores (flujo, instancias, condiciones como datos)
|
||||
- Persistencia del esquema (en seed/DB, versionado)
|
||||
- `instance_id` en `answers` (Etapa 4A — ya aplicada)
|
||||
- Motor de resolución de flujo para este formulario (interpreta el esquema)
|
||||
- Frontend wizard como renderer del esquema (sin reglas de negocio)
|
||||
- Mini-wizard de vínculos familiares (instancias declaradas en el esquema)
|
||||
- Autocomplete de barrios desde dataset INE/DGEEC
|
||||
- Subtítulos del "porqué" por sección
|
||||
- Submit actualizado para enviar `instance_id` por answer
|
||||
- Barra de progreso dinámica
|
||||
- RPCs nuevas para preguntas de vínculo con instancias
|
||||
- Submit con `instance_id` por answer
|
||||
- Barra de progreso dinámica (calculada desde el esquema + estado)
|
||||
- RPCs para preguntas de vínculo con instancias
|
||||
- Seed v3 limpio (sin migración de datos v2)
|
||||
|
||||
---
|
||||
|
||||
## Fuera de alcance (explícito)
|
||||
- ❌ Migración de datos v2.1 → v3 (se decide más adelante, cuando v3 esté estable)
|
||||
- ❌ Motor genérico universal de formularios (Fase 2 — BACKLOG)
|
||||
- ❌ Backend de construcción/edición de formularios (sprint posterior)
|
||||
- ❌ Migración de datos v2.1 → v3 (se decide cuando v3 esté estable)
|
||||
- ❌ Tocar la rama `v2.1.0-stable`
|
||||
- ❌ RE Index (sprint posterior)
|
||||
- ❌ Informe descargable (sprint posterior)
|
||||
- ❌ Autenticación de `/admin/responses` (TECH-DEBT-008)
|
||||
- ❌ Backup remoto S3 (TECH-DEBT-006)
|
||||
|
||||
---
|
||||
- ❌ RE Index, informe descargable, auth de /admin/responses, backup S3
|
||||
|
||||
## Decisiones ya tomadas (no renegociar)
|
||||
1. v3 se desarrolla en `main`. v2 queda preservado en `v2.1.0-stable` + tag `v2.1.0`.
|
||||
2. v3 arranca con seed limpio — sin datos migrados.
|
||||
3. `instance_id text DEFAULT 'default'` en `answers`. Constraint:
|
||||
`UNIQUE (response_id, question_id, instance_id)`.
|
||||
4. Wizard pregunta-por-pantalla para TODO el formulario tras el consentimiento.
|
||||
5. Mini-wizard de familiares: una pregunta por pantalla, con badge de contexto
|
||||
"Familiar X de N".
|
||||
6. Numeración desde Sección 1 (no "Sección A").
|
||||
7. Barrio: autocomplete para ciudades con datos INE, texto libre para el resto.
|
||||
8. Texto de consentimiento: el del Excel del instrumento v3 (Sección 1).
|
||||
9. Versionado SemVer + CHANGELOG + tags de Git en cada versión estable.
|
||||
10. 2-3 familiares es el caso típico; soportar N sin límite duro.
|
||||
|
||||
---
|
||||
1. v3 en `main`; v2 preservado en `v2.1.0-stable` + tag `v2.1.0`.
|
||||
2. Lógica de flujo en esquema declarativo (datos), NO en el frontend.
|
||||
3. Frontend wizard = renderer sin reglas de negocio.
|
||||
4. Estrategia dos fases: formulario-como-esquema ahora, motor genérico después.
|
||||
5. v3 arranca con seed limpio, sin datos migrados.
|
||||
6. `instance_id` ya implementado (4A). Instancias declaradas en el esquema.
|
||||
7. Wizard pregunta-por-pantalla para todo el formulario tras consentimiento.
|
||||
8. Mini-wizard de familiares: una pregunta por pantalla, badge "Familiar X de N".
|
||||
9. Numeración desde Sección 1.
|
||||
10. Barrio: autocomplete con datos INE, texto libre para el resto.
|
||||
11. Texto de consentimiento: el del Excel del instrumento v3.
|
||||
12. SemVer + CHANGELOG + tags en cada versión estable.
|
||||
13. Migraciones se aplican vía CLI del VPS (`docker exec -i supabase-db psql`).
|
||||
14. **Motor de flujo (4C): corre en el cliente** como módulo puro y aislado (entrada→salida
|
||||
determinista, testeable con Node). Interpreta el esquema declarativo; no embebe lógica
|
||||
en el componente React.
|
||||
15. **Validación de integridad server-side en el submit (4G): REQUISITO NO NEGOCIABLE.**
|
||||
El servidor valida contra el esquema que: las preguntas existen, los valores son opciones
|
||||
válidas, las instancias corresponden a un generador real, y no se inyectan respuestas a
|
||||
preguntas que el flujo nunca debió mostrar. La validación de cliente es UX, NO seguridad.
|
||||
Para datos sensibles de salud, confiar solo en validación de cliente sería una falla de
|
||||
seguridad. Esto NO se difiere a Fase 2.
|
||||
|
||||
## Criterios de éxito (checklist)
|
||||
- [ ] El formulario funciona end-to-end como wizard, una pregunta por pantalla
|
||||
- [ ] El mini-wizard de familiares genera instancias correctamente (verificable en DB)
|
||||
- [ ] El flujo del formulario está definido como esquema declarativo persistido
|
||||
- [ ] El frontend no contiene reglas de negocio del flujo (verificable por revisión)
|
||||
- [ ] El wizard funciona end-to-end, una pregunta por pantalla
|
||||
- [ ] El mini-wizard de familiares genera instancias según el esquema
|
||||
- [ ] Los datos se guardan con `instance_id` correcto por familiar
|
||||
- [ ] El dashboard sigue funcionando (las preguntas no-instancia usan `instance_id='default'`)
|
||||
- [ ] Las nuevas RPCs agregan datos de vínculo por instancia
|
||||
- [ ] El autocomplete de barrios funciona para las ciudades principales
|
||||
- [ ] La validación visual del wizard coincide con el mockup aprobado
|
||||
- [ ] `v2.1.0-stable` permanece intacta y restaurable
|
||||
- [ ] El dashboard sigue funcionando (preguntas no-instancia con `instance_id='default'`)
|
||||
- [ ] El autocomplete de barrios funciona para ciudades principales
|
||||
- [ ] La validación visual coincide con el mockup aprobado
|
||||
- [ ] `v2.1.0-stable` permanece intacta
|
||||
- [ ] La deuda de Fase 2 está registrada en BACKLOG
|
||||
|
||||
---
|
||||
|
||||
## Etapas del Sprint 4
|
||||
|
||||
| Etapa | Nombre | Entregable | Dependencia |
|
||||
| Etapa | Nombre | Entregable | Estado |
|
||||
|---|---|---|---|
|
||||
| **4.0** | Preservación v2 | Tag v2.1.0, rama stable, dump, CHANGELOG | — |
|
||||
| **4A** | Modelo de datos | Migración `instance_id` + constraint | 4.0 |
|
||||
| **4B** | Seed v3 | Instrumento v3 completo en seed.sql + geo JSON | 4A |
|
||||
| **4C** | Motor del wizard | Grafo de pantallas, estado, navegación (lógica) | 4B |
|
||||
| **4D** | Componente de pantalla | UI una-pregunta-por-pantalla + secciones | 4C |
|
||||
| **4E** | Mini-wizard instancias | Vínculos 4.1 + pantallas por familiar | 4D |
|
||||
| **4.0** | Preservación v2 | Tag, rama stable, dump, CHANGELOG | ✅ Completada |
|
||||
| **4A** | Persistencia instance_id | Migración + DATA_MODEL | ✅ Completada |
|
||||
| **4B** | Esquema declarativo + seed v3 | Esquema del formulario como datos + geo JSON | — |
|
||||
| **4C** | Motor de resolución de flujo | Intérprete del esquema (este formulario) | 4B |
|
||||
| **4D** | Frontend renderer | Wizard que renderiza el esquema, una preg/pantalla | 4C |
|
||||
| **4E** | Instancias de vínculo | Mini-wizard de familiares desde el esquema | 4D |
|
||||
| **4F** | Autocomplete barrios | Campo barrio con dataset INE | 4D |
|
||||
| **4G** | Submit con instance_id | Guardado correcto multi-instancia | 4E |
|
||||
| **4H** | RPCs de vínculo | Agregados de datos por instancia en dashboard | 4G |
|
||||
| **4I** | Validación e2e + tag v3.0.0 | QA visual completo + release | todas |
|
||||
|
||||
---
|
||||
| **4G** | Submit con instance_id | Guardado multi-instancia | 4E |
|
||||
| **4H** | RPCs de vínculo | Agregados por instancia en dashboard | 4G |
|
||||
| **4I** | Validación e2e + tag v3.0.0 | QA visual + release + ADR + BACKLOG Fase 2 | todas |
|
||||
|
||||
## Orden de ejecución
|
||||
4.0 → 4A → 4B → 4C → 4D → (4E ∥ 4F) → 4G → 4H → 4I
|
||||
|
||||
Las etapas 4E y 4F pueden ir en paralelo si hay capacidad; si no, 4E primero
|
||||
(es la más crítica), 4F después.
|
||||
4B → 4C → 4D → (4E ∥ 4F) → 4G → 4H → 4I
|
||||
|
||||
---
|
||||
|
||||
## Notas de versionado por etapa
|
||||
- Cada etapa cierra con un commit versionado descriptivo.
|
||||
- Las versiones `3.0.0-alpha.N` se pueden usar para hitos intermedios si se
|
||||
despliega a un entorno de prueba.
|
||||
- El tag `v3.0.0` se crea solo en la etapa 4I, cuando todo está validado.
|
||||
## Definición del esquema declarativo (guía para 4B)
|
||||
|
||||
El esquema describe el formulario como datos. Estructura conceptual:
|
||||
|
||||
```
|
||||
formulario:
|
||||
version, secciones[]
|
||||
seccion:
|
||||
numero, nombre, descripcion (el "porqué")
|
||||
preguntas[]
|
||||
pregunta:
|
||||
codigo, tipo, enunciado, hint, opciones[], required, sensitive
|
||||
flujo:
|
||||
condiciones_visibilidad[] (cuándo se muestra — evaluado por el motor)
|
||||
genera_instancias? (si esta pregunta crea instancias, ej. 4.1)
|
||||
pertenece_a_instancia_de? (de qué generador depende, ej. 4.2/4.3 → 4.1)
|
||||
validaciones[]
|
||||
```
|
||||
|
||||
La forma técnica exacta de persistir esto (columnas en `questions` +
|
||||
`conditional_rules` JSONB extendido, o tabla de definición de flujo separada)
|
||||
la decide la Etapa 4B según el esquema actual. Principio: declarativo, versionado,
|
||||
interpretable por el motor sin conocer el caso de uso.
|
||||
124
sprints/sprint-4/ETAPA_4C_PROMPT.md
Normal file
124
sprints/sprint-4/ETAPA_4C_PROMPT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# ETAPA 4C — Motor de resolución de flujo (módulo puro, cliente)
|
||||
|
||||
## 1. Objetivo
|
||||
Construir el motor que interpreta el esquema declarativo del formulario y resuelve
|
||||
la secuencia de pantallas según el estado de respuestas. Es un módulo puro y aislado
|
||||
en `lib/`, sin dependencias de React ni del DOM — entrada→salida determinista,
|
||||
testeable con Node. El componente (Etapa 4D) lo consume; el motor no conoce al componente.
|
||||
|
||||
## 2. Contexto previo
|
||||
- ADR-005: la lógica de flujo vive en el esquema (datos). El motor la interpreta.
|
||||
- El esquema v3 está en la DB: questions con `type`, `conditional_rules`,
|
||||
`instance_generator`, `instance_of`, `hint`, más filas `type='section'`.
|
||||
- Decisión confirmada: el motor corre en el CLIENTE (interpretación de flujo, no validación
|
||||
de seguridad — esa va server-side en 4G).
|
||||
- Decisión confirmada: las secciones (`type='section'`) son PANTALLAS propias en el grafo.
|
||||
- La lógica de v2 (`lib/conditional.ts`, `survey-form.tsx`) es INCOMPATIBLE y se reemplaza:
|
||||
- v2 evalúa AND (`rules.every`); v3 necesita OR entre condiciones de ocultamiento.
|
||||
- v2 solo conoce `eq`/`eq_any` y acción `show`; v3 necesita `contains`, `eq_sole`, `not_in`, acción `hide`.
|
||||
- v2 infiere secciones por regex de prefijo; v3 las tiene como datos.
|
||||
- El comportamiento esperado está validado en el mockup `wizard_formulario_mapeo_v3.html`
|
||||
(función `buildVisible` y navegación) — usar como referencia de comportamiento, NO de arquitectura.
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar de nuevo
|
||||
- Motor puro en `lib/` (proponé el nombre: ej. `lib/form-engine.ts`), sin React/DOM.
|
||||
- Vocabulario de operadores: `eq`, `eq_any`, `not_in` (escalares), `contains`, `eq_sole` (array).
|
||||
- Acciones: `show`, `hide`.
|
||||
- Combinación: múltiples reglas en el array de una pregunta se evalúan en OR.
|
||||
(Una pregunta se oculta si CUALQUIER regla `hide` aplica; se muestra si CUALQUIER regla `show` aplica.
|
||||
Definí la semántica precisa de show+hide combinados y documentala.)
|
||||
- Secciones = pantallas propias en el grafo.
|
||||
- Instancias: 4.1 (`instance_generator=true`) genera N instancias; las preguntas con
|
||||
`instance_of='4.1'` se repiten una vez por instancia activa, en orden por familiar
|
||||
(familiar1: 4.2→4.3, familiar2: 4.2→4.3, ...) como en el mockup.
|
||||
- Estado de respuestas con clave compuesta `question_code:instance_id` (instancias) o
|
||||
`question_code` con `instance_id='default'` (normales).
|
||||
|
||||
## 4. Tareas
|
||||
|
||||
### 4.1 Tipos del motor (`lib/types.ts` ampliado o nuevo)
|
||||
Definir/ampliar:
|
||||
- `ConditionalRule`: operadores y acciones completos del vocabulario v3.
|
||||
- `FormSchema`: la representación del formulario que el motor consume (secciones, preguntas,
|
||||
reglas, instanciación) — derivada de las filas de `questions`.
|
||||
- `WizardState`: respuestas (con instancias), instancia activa, pantalla actual.
|
||||
- `Screen`: una pantalla resoluble (sección-intro | pregunta | pregunta-de-instancia | fin).
|
||||
|
||||
### 4.2 Evaluador de reglas
|
||||
Reescribir la evaluación (reemplaza `lib/conditional.ts`):
|
||||
- Implementar los 5 operadores y 2 acciones.
|
||||
- `contains` / `eq_sole` operan sobre respuestas array (multiple_choice).
|
||||
- Semántica OR documentada y correcta para el caso `r_hide_ning_sn`.
|
||||
- Función pura: `(rule, answers) => boolean` y `(question, answers) => 'show'|'hide'`.
|
||||
|
||||
### 4.3 Resolución del grafo de pantallas
|
||||
Función pura que dado el esquema + estado devuelve la lista ordenada de pantallas visibles:
|
||||
- Expande instancias (4.2/4.3 por cada familiar activo declarado en 4.1).
|
||||
- Aplica visibilidad (oculta secciones/preguntas según reglas).
|
||||
- Incluye las pantallas de sección.
|
||||
- Equivale conceptualmente al `buildVisible()` del mockup, pero como módulo puro.
|
||||
|
||||
### 4.4 Navegación
|
||||
Funciones puras `next(state, schema)` y `back(state, schema)` que devuelven el nuevo
|
||||
estado (pantalla actual actualizada), resolviendo saltos. Identificar la pantalla actual
|
||||
por ID estable, no por índice (lección del mockup: el índice se desincroniza cuando
|
||||
cambian las instancias).
|
||||
|
||||
### 4.5 Carga del esquema
|
||||
Actualizar la query (`app/page.tsx`) para traer las 3 columnas nuevas
|
||||
(`hint`, `instance_generator`, `instance_of`). Transformar las filas de `questions`
|
||||
en el `FormSchema` que consume el motor.
|
||||
|
||||
### 4.6 Tests del motor (Node, sin navegador)
|
||||
Tests puros que verifiquen:
|
||||
- Recorrido completo (todos los vínculos) genera las pantallas esperadas.
|
||||
- "Solo niño sin discapacidad" (eq_sole) → recorrido liviano correcto (5.3, 8.4, 9.1, 9.4).
|
||||
- "Ninguna de las anteriores" (contains) → salta a sección 10.
|
||||
- 3.1='No' oculta 3.2-3.5.
|
||||
- 3 familiares → 6 pantallas de instancia en orden por familiar.
|
||||
- Navegación next/back coherente cuando cambian las instancias.
|
||||
|
||||
## 5. Entregables (DoD)
|
||||
- [ ] Motor puro en `lib/` sin dependencias de React/DOM.
|
||||
- [ ] Los 5 operadores y 2 acciones implementados y testeados.
|
||||
- [ ] Resolución de grafo con instancias y secciones-pantalla.
|
||||
- [ ] Navegación por ID estable, no por índice.
|
||||
- [ ] Query actualizada con las 3 columnas nuevas.
|
||||
- [ ] Tests Node que cubren los recorridos del punto 4.6, todos en verde.
|
||||
- [ ] NO se toca el componente todavía (es 4D) — el motor se entrega standalone + tests.
|
||||
|
||||
## 6. Qué reportar
|
||||
```
|
||||
Verificado:
|
||||
- Motor en [archivo], puro (sin React/DOM) ✓
|
||||
- Operadores implementados: eq, eq_any, not_in, contains, eq_sole ✓
|
||||
- Acciones: show, hide ✓ — semántica de combinación: [descripción]
|
||||
- Tests: N/N en verde
|
||||
- recorrido completo ✓
|
||||
- solo niño (eq_sole) → 5.3/8.4/9.1/9.4 ✓
|
||||
- ninguna (contains) → salta S10 ✓
|
||||
- 3.1=No oculta 3.2-3.5 ✓
|
||||
- 3 familiares → 6 pantallas en orden ✓
|
||||
- next/back coherente ✓
|
||||
|
||||
Asumido:
|
||||
- [supuestos de diseño del motor]
|
||||
```
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No poner lógica de flujo en un componente React (el motor es puro, lib/).
|
||||
- ❌ No tocar el componente de formulario todavía (es 4D).
|
||||
- ❌ No implementar validación de seguridad acá (es 4G, server-side).
|
||||
- ❌ No usar índices numéricos para la pantalla actual — usar ID estable.
|
||||
- ❌ No tocar v2.1.0-stable ni el seed.
|
||||
|
||||
## 8. Versionado
|
||||
Commit sugerido:
|
||||
```
|
||||
feat(engine): motor de resolución de flujo declarativo [4C]
|
||||
|
||||
Módulo puro que interpreta el esquema v3 y resuelve pantallas.
|
||||
5 operadores (eq, eq_any, not_in, contains, eq_sole), acciones show/hide.
|
||||
Instancias de vínculo y secciones-pantalla. Navegación por ID estable.
|
||||
Tests Node cubren los 3 recorridos. Reemplaza lib/conditional.ts de v2.
|
||||
```
|
||||
Reference in New Issue
Block a user