diff --git a/sprints/sprint-4/ETAPA_4G_PROMPT.md b/sprints/sprint-4/ETAPA_4G_PROMPT.md new file mode 100644 index 0000000..989a3c1 --- /dev/null +++ b/sprints/sprint-4/ETAPA_4G_PROMPT.md @@ -0,0 +1,121 @@ +# ETAPA 4G — Submit con instance_id + validación de integridad server-side + +## 1. Objetivo +Escribir el submit de v3: persiste las respuestas (incluyendo instancias) y VALIDA la +integridad en el servidor contra el esquema antes de insertar. La validación server-side +es REQUISITO NO NEGOCIABLE (BRIEF decisión 15). El cliente no es confiable; datos de salud. + +## 2. Contexto previo +- El submit de v2 (`app/actions/submit-survey.ts`) NO está conectado y está desactualizado: + - `WITH CHECK (true)` en RLS de answers → acepta cualquier insert (sin validación real). + - payload plano `Record` — sin noción de `instance_id`. + - códigos qi_* apuntan a A1/A4/A5 (v2), no existen en v3 (son 2.1/2.4/2.5). + - 3 INSERTs sueltos sin transacción → si answers falla, responses/consent quedan huérfanos. +- El motor `lib/form-engine.ts` es PURO → se puede correr SERVER-SIDE para validar. + NO reescribir la lógica de flujo: reusar el motor. +- `answers.instance_id` existe (4A), constraint UNIQUE(response_id, question_id, instance_id). +- El estado del wizard ya tiene claves `code:instanceId` (4C/4D). +- Se usa anon key respetando RLS (regla 9 de CLAUDE.md) — NUNCA service_role. + +## 3. DECISIONES YA TOMADAS — no consultar de nuevo +- Validación de integridad SERVER-SIDE, no negociable. +- Reusar `lib/form-engine.ts` en el servidor para validar coherencia de flujo — no duplicar lógica. +- El submit rechaza el envío ENTERO si algo no valida — no inserta datos parciales ni inyectados. +- El payload del cliente pasa a ser consciente de instancia: `{code, instanceId, value}[]` + (o equivalente que preserve la clave compuesta). +- Transaccionalidad: las inserciones (consent_records → responses → answers) deben ser atómicas. + Evaluar función RPC transaccional (SECURITY DEFINER con validaciones) vs. inserts en el + Server Action. Proponé el enfoque más robusto dado que anon no puede hacer múltiples + inserts atómicos fácilmente — una RPC transaccional es probablemente lo correcto. +- Códigos qi_* actualizados a v3 (2.1 zona, 2.4 edad, 2.5 sector — verificar contra el seed). + +## 4. Validaciones server-side mandatorias +Por cada submit recibido, el servidor valida CONTRA EL ESQUEMA de la DB (no contra el cliente): +1. **Survey válido y activo:** el `survey_id` existe y está en estado que admite respuestas. +2. **Preguntas legítimas:** cada `code` recibido existe y pertenece a ESE survey. +3. **Valores válidos:** cada valor es una opción válida de su pregunta (para single/multiple_choice), + respeta `max_select`, tipo correcto. +4. **Visibilidad coherente:** usando el motor (`resolveScreens`/`resolveVisibility`) con las + respuestas recibidas, cada respuesta enviada corresponde a una pregunta que DEBÍA estar + visible. Rechazar respuestas a preguntas que el flujo nunca debió mostrar (ej. responder + 3.2 cuando 3.1='No' las oculta). +5. **Instancias legítimas:** cada `instance_id` (familiar_N) corresponde a una instancia + realmente generada por su `instance_generator` (4.1) según la respuesta real recibida para 4.1. + Rechazar instancias inventadas. +6. **Consentimiento requerido presente:** el consentimiento operativo (requerido) está. +7. **Requeridas presentes:** las preguntas required visibles tienen respuesta. + +Si CUALQUIERA falla: rechazar el submit completo, no insertar nada, devolver error claro. + +## 5. Tareas +### 5.1 Definir el contrato del payload v3 +Shape consciente de instancia. El wizard ya tiene `state.answers` con claves `code:instanceId`. +Definir el payload que el cliente envía y el validador consume. + +### 5.2 Validador server-side (reusando el motor) +Módulo/función que recibe el payload + carga el esquema desde la DB + corre las 7 validaciones +del punto 4, reusando `lib/form-engine.ts`. Puro y testeable donde se pueda. + +### 5.3 Persistencia transaccional +Implementar la inserción atómica (consent_records → responses → answers con instance_id). +Proponer e implementar RPC transaccional o mecanismo equivalente. Si una RPC SECURITY DEFINER +es el camino, documentar por qué y qué valida internamente. + +### 5.4 Conectar el wizard al submit +El botón "Enviar (próximamente)" pasa a enviar de verdad. Manejar estados éxito/error +(los stage que 4D dejó sin implementar). Mensajes de error accionables, nunca trace crudo. + +### 5.5 Tests +- Validación: submit con respuesta a pregunta oculta → rechazado. +- Validación: valor fuera de opciones → rechazado. +- Validación: instancia inventada → rechazada. +- Validación: sin consentimiento requerido → rechazado. +- Happy path: submit completo válido (con instancias) → persiste correctamente con instance_id. +- Atomicidad: fallo a mitad → no quedan datos parciales. + +## 6. Entregables (DoD) +- [ ] Validación server-side completa (7 validaciones) reusando el motor. +- [ ] Submit rechaza envío entero si algo no valida; sin datos parciales. +- [ ] Persistencia transaccional (sin huérfanos ante fallo). +- [ ] instance_id persistido correctamente por instancia. +- [ ] Códigos qi_* actualizados a v3. +- [ ] Wizard conectado al submit, estados éxito/error funcionando. +- [ ] Tests de las validaciones + happy path + atomicidad. +- [ ] anon key, nunca service_role (salvo dentro de RPC SECURITY DEFINER justificada). +- [ ] **Validación del director** antes de aplicar a producción. + +## 7. Qué reportar +``` +Verificado: +- Validación server-side: 7/7 implementadas, reusando lib/form-engine.ts ✓ +- Casos rechazados probados: pregunta oculta, valor inválido, instancia falsa, sin consentimiento +- Persistencia transaccional vía [RPC / mecanismo] ✓ — atomicidad probada +- instance_id persistido: [ejemplo real de filas answers con familiar_1/familiar_2] +- Códigos qi_* actualizados a v3 ✓ +- Wizard conectado, estados éxito/error ✓ +- Tests: N/N +Asumido / decisiones: +- [enfoque transaccional y por qué] +Hallazgos: +- [lo que aparezca] +``` +Reporte con datos concretos (C.4): mostrá filas reales de answers con instance_id tras un +submit de prueba con 2 familiares. + +## 8. Qué NO hacer +- ❌ No confiar en validación de cliente para integridad (es UX, no seguridad). +- ❌ No duplicar la lógica de flujo — reusar el motor server-side. +- ❌ No insertar datos parciales si la validación falla. +- ❌ No usar service_role fuera de una RPC SECURITY DEFINER justificada y documentada. +- ❌ No aplicar a producción sin validación del director. +- ❌ No tocar v2.1.0-stable, el seed, ni el motor (salvo para CONSUMIRLO en validación). + +## 9. Versionado +``` +feat(submit): submit v3 con validación de integridad server-side [4G] + +Validación server-side (no negociable) reusando el motor: rechaza respuestas +a preguntas ocultas, valores inválidos, instancias falsas, sin consentimiento. +Persistencia transaccional con instance_id por instancia. Conecta el wizard. +Cierra la falla de WITH CHECK(true): el cliente ya no es fuente de verdad. +``` \ No newline at end of file