feat(engine): motor de resolución de flujo declarativo [4C]

This commit is contained in:
markosbenitez
2026-06-23 07:21:35 -03:00
parent ed524af16b
commit eba93db452
2 changed files with 236 additions and 65 deletions

View File

@@ -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.

View 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.
```