diff --git a/OBSERVATIONS.md b/OBSERVATIONS.md
new file mode 100644
index 0000000..6b068a9
--- /dev/null
+++ b/OBSERVATIONS.md
@@ -0,0 +1,89 @@
+# OBSERVATIONS.md — Observaciones y breaking changes
+
+> Registro de cambios al instrumento que rompen compatibilidad con datos existentes
+> o que requieren atención del director antes/después de re-seedear. No bloquean el
+> trabajo del agente — se documentan para que el director decida qué hacer con los
+> datos ya capturados.
+
+---
+
+## Etapa 3I2 — Revisión del instrumento v2.1 (feedback equipo médico)
+
+### 1. Eliminación de "Prefiero no decir" (Cambio 1)
+Afecta: A5, A6, 1.1, 1.5, 1.6, 1.13, 2.5, 3.1, 3.4.
+
+Si existen respuestas guardadas con el valor `"Prefiero no decir"` en alguna de estas
+preguntas, esos valores quedan **huérfanos**: ya no aparecen como opción en el
+formulario ni en `seed.sql`, pero permanecen en `responses.answers` tal cual fueron
+guardados. No se borran ni se migran automáticamente.
+
+**Impacto en triggers (1.1, 1.6, 1.13):** estas preguntas ahora tienen solo 2 opciones
+(Sí/No). La lógica condicional (incluyendo el nuevo salto-triple, Cambio 4a) solo
+reacciona a `"Sí"`. Una respuesta histórica `"Prefiero no decir"` en un trigger se
+trataría como "no Sí" a efectos de la lógica condicional si se re-evaluara, pero no
+hay re-evaluación retroactiva de respuestas ya enviadas.
+
+### 2. 2.1 cambia de `single_choice` a `multiple_choice` (Cambio 5)
+Respuestas existentes para 2.1 están guardadas como un string único (`"Yo soy el/la
+principal"`, etc.). El formulario nuevo espera un array de strings para preguntas
+`multiple_choice`. Las respuestas históricas de 2.1 quedan en formato de string,
+inconsistente con el nuevo esquema esperado para esa pregunta.
+
+Ver `TECH_DEBT.md` → TECH-DEBT-006 (deuda de ranking pendiente sobre esta misma
+pregunta).
+
+### 3. Eliminación de la pregunta 2.4 (Cambio 6)
+"¿Contás con apoyo de otras personas para el cuidado?" se elimina del seed por ser
+similar/redundante con 2.1 (feedback del equipo médico).
+
+El `DELETE FROM public.questions WHERE survey_id = ...` al inicio de `seed.sql` borra
+la fila de la pregunta 2.4 (id `40000000-0000-0000-0000-000000000027`). Si existen
+filas en `responses` cuyo `answers` referencian `question_id` = 2.4 (por
+`question_id`, no por código embebido en JSON — depende del esquema de `responses`),
+esas referencias quedan apuntando a un `question_id` que ya no existe en `questions`
+tras el re-seed.
+
+**No re-seedear la base de producción sin que el director confirme que no hay
+respuestas reales que dependan de 2.4**, o decidir conservar la fila de 2.4 marcada
+como `archived`/inactiva en lugar de borrarla (cambio de diseño — Nivel 3, no
+ejecutado en esta etapa).
+
+### 4. Cambio de texto en opción de 3.3 (Cambio 8)
+"Es difícil sostenerlo con mis ingresos" → "No puedo sostenerlo con mis ingresos".
+
+Respuestas existentes que eligieron la opción anterior quedan guardadas con el texto
+literal `"Es difícil sostenerlo con mis ingresos"`, que ya no coincide con ninguna
+opción del seed actual. Cualquier agregación por valor exacto de opción debe
+contemplar ambos strings como equivalentes para no perder esos datos en reportes.
+
+### 5. División de la primera franja salarial en 3.4 (Cambio 9)
+"Sueldo mínimo – Gs. 5.000.000" se reemplaza por dos opciones nuevas: "Sueldo mínimo"
+y "Gs. 3.000.000 a 5.000.000".
+
+Respuestas existentes con el valor `"Sueldo mínimo – Gs. 5.000.000"` quedan con ese
+valor, que ya no existe como opción. No hay forma automática de reasignar esas
+respuestas a una de las dos franjas nuevas (la franja exacta del respondente no se
+puede inferir del valor agregado anterior).
+
+### 6. Cambio de texto en última opción de 6.1 (Cambio 10)
+"No, nunca se ha hablado del tema" → "No, no se puede hablar del tema".
+
+Respuestas existentes con el valor anterior quedan con ese texto literal, que ya no
+coincide con ninguna opción del seed actual.
+
+---
+
+## Resumen para el director
+
+- Ningún cambio de esta etapa borra datos directamente, salvo el `DELETE` estándar de
+ `questions` por `survey_id` (mecanismo de idempotencia ya existente en `seed.sql`,
+ no nuevo de esta etapa) — pero ese `DELETE` ahora también elimina la fila de 2.4.
+- Los cambios 1, 8, 9 y 10 dejan **valores de texto huérfanos** en respuestas
+ históricas (ya no matchean ninguna opción vigente). No afecta el funcionamiento del
+ formulario hacia adelante, pero sí cualquier reporte/agregación que dependa de
+ matchear el string exacto de la opción.
+- El cambio 5 (2.1) deja respuestas históricas en un formato (string) distinto al que
+ el formulario nuevo produce (array). Si se construye un reporte sobre 2.1, debe
+ manejar ambos formatos o normalizarse en una migración de datos aparte.
+- **No se ejecutó ningún re-seed sobre la base real** — esto aplica solo cuando el
+ director apruebe re-seedear con `seed.sql` v2.1.
diff --git a/SEED_SPEC.md b/SEED_SPEC.md
index 8b48b28..67387dd 100644
--- a/SEED_SPEC.md
+++ b/SEED_SPEC.md
@@ -1,23 +1,31 @@
-# SEED_SPEC.md — Contrato del instrumento "Mapeo de Cuidadores" v2
+# SEED_SPEC.md — Contrato del instrumento "Mapeo de Cuidadores" v2.1
-> Versión 2 — incorpora revisión de la colega (Jun 2026). Reemplaza la v1 (34 preguntas).
-> 44 preguntas · 8 secciones · Códigos únicos corregidos.
+> Versión 2.1 — incorpora feedback del equipo médico, Etapa 3I2 (Jun 2026). Reemplaza la v2.
+> 44 preguntas (se elimina 2.4, se agrega 6.6) · 8 secciones · Códigos únicos corregidos.
> Claude Code reescribe `seed.sql` a partir de ESTE contrato.
## Convenciones
- Tipos: radio→`single_choice`, checkbox→`multiple_choice`, escala 1-5→`rating`,
texto libre largo→`text_long`, texto corto→`text_short`.
- **`conditional_rules` en la pregunta DEPENDIENTE** (cada pregunta declara su propia condición).
-- Los 3 triggers (1.1, 1.6, 1.13) son `single_choice` de 3 opciones; solo **"Sí"** dispara el bloque.
+- Los 3 triggers (1.1, 1.6, 1.13) son `single_choice` de 2 opciones (Sí · No); solo **"Sí"** dispara
+ el bloque correspondiente.
- `is_sensitive=true` en preguntas de salud/discapacidad/diagnóstico (marcadas ✅).
- `3.4` (salary) — `is_sensitive=true` + analyst-only (ADR-001 addendum): nunca en vistas org_admin.
- RE-INDEX: las preguntas marcadas son candidatas a índice compuesto (metodología TBD Sprint 3).
+- En preguntas `multiple_choice` el formulario muestra el subtítulo "Podés marcar más de una opción".
+- "Prefiero no decir" eliminada de todas las preguntas (Cambio 1, Etapa 3I2).
-## Estructura condicional (3 bloques)
+## Estructura condicional (4 bloques)
- **cond-disc-propia** — 1.1='Sí' → muestra 1.2, 1.3, 1.4, 1.5
- **cond-familiar** — 1.6='Sí' → muestra 1.7, 1.8, 1.9, 1.10, 1.11, 1.12
- **cond-adulto-mayor** — 1.13='Sí' → muestra 1.14, 1.15, 1.16
-- Secciones A, 2, 3, 4, 5, 6, cierre: **top-level, siempre visibles.**
+- **salto-triple** — si 1.1='No' Y 1.6='No' Y 1.13='No' (es decir, ninguno de los tres dio
+ 'Sí'), se ocultan completas las secciones 2, 3, 4, 5 y las preguntas 6.1, 6.2, 6.3. El
+ formulario salta directo a 6.4. Implementado como `conditional_rules`
+ `{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}` en cada
+ pregunta del bloque (equivalente: se muestra si AL MENOS uno de los tres es 'Sí').
+- Sección A, 6.4-6.6 y Sección 7 (cierre): **top-level, siempre visibles.**
---
@@ -28,49 +36,68 @@
| A2 | single_choice | ¿En qué ciudad vivís?* | 219 ciudades (GeoSelectWidget: dependiente de A1) | qi_city — Sprint 3 |
| A3 | text_short | ¿En qué barrio vivís? | — (texto libre, opcional) | |
| A4 | single_choice | ¿En qué área o gerencia trabajás?* | Directorio · Gerencia · Operaciones/Producción · Comercial/Ventas · Administración/Finanzas · Recursos Humanos/Cultura · Tecnología/Sistemas · Logística/Distribución · Otra área | qi_sector |
-| A5 | single_choice | ¿Cuántos años tenés? | Menos de 25 años · 25 a 34 años · 35 a 44 años · 45 a 54 años · 55 años o más · Prefiero no decir | qi_age_bucket |
-| A6 | single_choice | ¿Con qué género te identificás? | Femenino · Masculino · No binario/Otro · Prefiero no decir | |
+| A5 | single_choice | ¿Cuántos años tenés? | Menos de 25 años · 25 a 34 años · 35 a 44 años · 45 a 54 años · 55 años o más | qi_age_bucket |
+| A6 | single_choice | ¿Con qué género te identificás? | Femenino · Masculino · No binario/Otro | |
| A7 | single_choice | ¿Cuál es tu modalidad de trabajo actual? | Presencial · Remoto (home office) · Híbrido | |
## Sección 1 — Prevalencia: cuidado y discapacidad *(sensible)*
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|---|---|---|---|---|---|
-| 1.1 | single_choice | trigger | ¿Vos mismo/a vivís con alguna discapacidad o condición de salud que afecte tu funcionamiento cotidiano?* | Sí · No · Prefiero no decir | ✅ |
+| 1.1 | single_choice | trigger | ¿Vos mismo/a vivís con alguna discapacidad o condición de salud que afecte tu funcionamiento cotidiano?* | Sí · No | ✅ |
| 1.2 | multiple_choice | 1.1=Sí | ¿Qué tipo de discapacidad o condición tenés? | Discapacidad física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial/salud mental · Condición crónica que genera limitaciones · Otra | ✅ |
-| 1.3 | multiple_choice | 1.1=Sí | ¿Usás algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · No utilizo | ✅ |
+| 1.3 | multiple_choice | 1.1=Sí | ¿Usás algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utilizo | ✅ |
| 1.4 | single_choice | 1.1=Sí | ¿Contás con certificado oficial de discapacidad? | Sí · No, pero estoy en trámite · No | ✅ |
-| 1.5 | single_choice | 1.1=Sí | ¿Tu condición afecta tu desempeño o comodidad en el trabajo? | Sí, de forma significativa · Sí, pero en aspectos puntuales · No, está bien gestionada · Prefiero no decir | ✅ |
-| 1.6 | single_choice | trigger | ¿Tenés algún familiar cercano (hijo/a, pareja, hermano/a, padre/madre) que viva con discapacidad?* | Sí · No · Prefiero no decir | ✅ |
+| 1.5 | single_choice | 1.1=Sí | ¿Tu condición afecta tu desempeño o comodidad en el trabajo? | Sí, de forma significativa · Sí, pero en aspectos puntuales · No, está bien gestionada | ✅ |
+| 1.6 | single_choice | trigger | ¿Tenés algún familiar cercano (hijo/a, pareja, hermano/a, padre/madre) que viva con discapacidad?* | Sí · No | ✅ |
| 1.7 | multiple_choice | 1.6=Sí | ¿Cuál es tu vínculo con esa persona? | Hijo/a · Cónyuge/pareja · Padre o madre · Hermano/a · Otro familiar | ✅ |
| 1.8 | multiple_choice | 1.6=Sí | ¿Qué tipo de discapacidad tiene? | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial/salud mental · Condición crónica severa · Otra | ✅ |
| 1.9 | multiple_choice | 1.6=Sí | ¿Qué tipo de apoyo o acompañamiento necesita principalmente? | Movilidad física · Comunicación · Apoyo escolar/educativo · Inclusión laboral · Terapias/rehabilitación · Apoyo emocional/psicosocial | ✅ |
-| 1.10 | multiple_choice | 1.6=Sí | ¿Usa algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · No utiliza | ✅ |
+| 1.10 | multiple_choice | 1.6=Sí | ¿Usa algún elemento de apoyo para la movilidad o autonomía? | Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utiliza | ✅ |
| 1.11 | single_choice | 1.6=Sí | Si es niño/a o adolescente en edad escolar: ¿está escolarizado/a y enfrenta barreras de aprendizaje? | Escolarizado/a, sin barreras importantes · Escolarizado/a, enfrenta barreras o faltan ajustes · No está escolarizado/a · No aplica | ✅ |
| 1.12 | single_choice | 1.6=Sí | ¿La persona con discapacidad se encuentra trabajando actualmente? | Sí, está trabajando · No, y está buscando empleo · No, y no está buscando · No aplica | ✅ |
-| 1.13 | single_choice | trigger | ¿Tenés algún adulto mayor (60+ años) a tu cuidado o bajo tu responsabilidad?* | Sí · No · Prefiero no decir | |
+| 1.13 | single_choice | trigger | ¿Tenés algún adulto mayor (60+ años) a tu cuidado o bajo tu responsabilidad?* | Sí · No | |
| 1.14 | single_choice | 1.13=Sí | ¿Qué nivel de dependencia tiene? (la más representativa) | Independiente, pero necesita supervisión · Dependencia leve · Dependencia moderada · Dependencia severa | ✅ |
| 1.15 | multiple_choice | 1.13=Sí | ¿Tiene una condición de salud que genere mayor necesidad de cuidado? | Demencia/Alzheimer · ACV/Parálisis · Enfermedad crónica grave · Movilidad reducida · Deterioro cognitivo · Sin condición específica | ✅ |
| 1.16 | single_choice | 1.13=Sí | ¿Vive con vos o en otra vivienda? | Vive en mi hogar · Vive en otra vivienda · Está en residencia/institución | |
## Sección 2 — Intensidad y rol del cuidado (top-level)
+> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
+
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
-| 2.1 | single_choice | ¿Quién es el/la responsable principal del cuidado? | Yo soy el/la principal · Comparto con otro familiar · Hay cuidador/a pagado/a · Está en institución/centro de día |
+| 2.1 | multiple_choice ⚠️ TECH-DEBT | ¿Quién es el/la responsable principal del cuidado? | Yo soy el/la principal · Comparto con otro familiar · Hay cuidador/a pagado/a · Está en institución/centro de día |
| 2.2 | single_choice | ¿Cuántas horas por día dedicás, en promedio, al cuidado directo? | Menos de 1h · Entre 1 y 3h · Entre 3 y 6h · Más de 6h · Varía mucho |
| 2.3 | multiple_choice | ¿En qué momentos del día cuidás principalmente? | Antes del trabajo · Durante horario laboral (llamadas/emergencias) · Al llegar del trabajo · Durante la noche · Los fines de semana · Es continuo sin horario fijo |
-| 2.4 | single_choice | ¿Contás con apoyo de otras personas para el cuidado? | Sí, apoyo familiar suficiente · Algo de apoyo, no suficiente · No, soy el/la único/a responsable · Tengo apoyo contratado |
-| 2.5 | single_choice | Como cuidador/a primario, ¿presentás signos de agotamiento o estrés crónico? | Sí, frecuentemente · A veces · No, lo llevo bien · Prefiero no decir |
+| 2.5 | single_choice | Como cuidador/a primario, ¿presentás signos de agotamiento o estrés crónico? | Sí, frecuentemente · A veces · No, lo llevo bien |
+
+> ⚠️ TECH-DEBT (2.1): pasó de `single_choice` a `multiple_choice` — selección múltiple sin orden
+> de prioridad. Deuda: implementar ranking (Principal cuidador soy yo > secundario instituto >
+> principal pagado > secundario familiar). Ver `TECH_DEBT.md`.
+>
+> **2.4 eliminada** (Cambio 6, Etapa 3I2): "¿Contás con apoyo de otras personas para el cuidado?"
+> — feedback del equipo médico: pregunta similar/redundante con 2.1. Ver `OBSERVATIONS.md`.
## Sección 3 — Impacto económico del cuidado (top-level)
+> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
+> 3.4: is_sensitive=true + analyst-only (no exponer a org_admin).
+
| Code | Tipo | Pregunta | Opciones | Notas |
|---|---|---|---|---|
-| 3.1 | single_choice | ¿Cuánto estimás que gastás por mes en el cuidado? | Sin gastos significativos · Menos de Gs. 500.000 · Gs. 500.000–1.500.000 · Gs. 1.500.000–3.000.000 · Gs. 3.000.000–6.000.000 · Más de Gs. 6.000.000 · Prefiero no decir | |
+| 3.1 | single_choice | ¿Cuánto estimás que gastás por mes en el cuidado? | Menos de Gs. 500.000 · Gs. 500.000–1.500.000 · Gs. 1.500.000–3.000.000 · Gs. 3.000.000–6.000.000 · Más de Gs. 6.000.000 | |
| 3.2 | multiple_choice | ¿Qué servicios o apoyos utilizás actualmente? | Terapias especializadas · Cuidador/a particular · Centro de día/institución · Transporte adaptado · Medicación alto costo · Equipamiento médico/tecnología asistiva · No utilizo servicios formales | |
-| 3.3 | multiple_choice | ¿Cómo sostenés los gastos y responsabilidades del cuidado? | Con mi empleo de tiempo completo · Con mi empleo de tiempo parcial · Con trabajo independiente/emprendimiento · Me ayudan mis familiares · Es difícil sostenerlo con mis ingresos | |
-| 3.4 | single_choice | ¿En qué franja salarial mensual te ubicás? *(CONFIDENCIAL — analyst-only)* | Sueldo mínimo – Gs. 5.000.000 · Gs. 5.000.001–8.000.000 · Gs. 8.000.001–12.000.000 · Gs. 12.000.001–15.000.000 · Gs. 15.000.001–18.000.000 · Gs. 18.000.001–25.000.000 · Gs. 25.000.001–50.000.000 · Gs. 50.000.001 o más · Prefiero no decir | is_sensitive=true; never in org_admin views |
+| 3.3 | multiple_choice | ¿Cómo sostenés los gastos y responsabilidades del cuidado? | Con mi empleo de tiempo completo · Con mi empleo de tiempo parcial · Con trabajo independiente/emprendimiento · Me ayudan mis familiares · No puedo sostenerlo con mis ingresos | |
+| 3.4 | single_choice | ¿En qué franja salarial mensual te ubicás? *(CONFIDENCIAL — analyst-only)* | Sueldo mínimo · Gs. 3.000.000 a 5.000.000 · Gs. 5.000.001–8.000.000 · Gs. 8.000.001–12.000.000 · Gs. 12.000.001–15.000.000 · Gs. 15.000.001–18.000.000 · Gs. 18.000.001–25.000.000 · Gs. 25.000.001–50.000.000 · Gs. 50.000.001 o más | is_sensitive=true; never in org_admin views |
| 3.5 | rating | ¿Los gastos de cuidado impactan en tu situación económica personal? | 1=Sin impacto · 2=Leve · 3=Moderado · 4=Alto · 5=Muy alto | RE-INDEX |
+> **Cambio 7** (3.1): se elimina "Sin gastos significativos" además de "Prefiero no decir".
+> **Cambio 8** (3.3): "Es difícil sostenerlo con mis ingresos" → "No puedo sostenerlo con mis
+> ingresos". Breaking change para datos existentes — ver `OBSERVATIONS.md`.
+> **Cambio 9** (3.4): "Sueldo mínimo – Gs. 5.000.000" se divide en "Sueldo mínimo" y
+> "Gs. 3.000.000 a 5.000.000". Breaking change para datos existentes — ver `OBSERVATIONS.md`.
+
## Sección 4 — Tiempo y descanso (top-level)
+> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
+
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| 4.1 | single_choice | ¿Cómo describirías tu nivel de descanso habitual? | Descanso bien, me siento recuperado/a · Descanso de forma irregular, pero lo manejo · Me cuesta descansar, me siento cansado/a con frecuencia · Descanso muy poco, el agotamiento afecta mi día a día | RE-INDEX |
@@ -78,19 +105,25 @@
| 4.3 | rating | En una escala del 1 al 5, ¿cómo evaluás tu bienestar general en este momento? | 1=Muy mal · 2=Mal · 3=Regular · 4=Bien · 5=Muy bien | RE-INDEX |
## Sección 5 — Impacto en el trabajo (top-level)
+> Cond.: **salto-triple** — toda la sección se oculta si 1.1='No' Y 1.6='No' Y 1.13='No'.
+
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| 5.1 | single_choice | En los últimos 3 meses, ¿con qué frecuencia sentiste que el cuidado afectó tu concentración o rendimiento? | Nunca o casi nunca · Algunas veces al mes · Varias veces por semana · Casi todos los días | RE-INDEX |
| 5.2 | single_choice | En el último año, ¿faltaste al trabajo por situaciones relacionadas al cuidado? | Nunca · Pocas veces (1–3 veces al año) · Varias veces (4–10 veces al año) · Con frecuencia (más de 10 veces al año) | |
## Sección 6 — Conciencia organizacional (top-level)
+> Cond. (6.1-6.3): **salto-triple** — se ocultan si 1.1='No' Y 1.6='No' Y 1.13='No'.
+> 6.4-6.6: siempre visibles (destino del salto).
+
| Code | Tipo | Pregunta | Opciones | Notas |
|---|---|---|---|---|
-| 6.1 | single_choice | ¿Sentís que tu empresa conoce o comprende la realidad del cuidado y la discapacidad de su gente?* | Sí, me siento acompañado/a · Parcialmente, hay cierta comprensión · No, es algo bastante invisible · No, nunca se ha hablado del tema | RE-INDEX |
+| 6.1 | single_choice | ¿Sentís que tu empresa conoce o comprende la realidad del cuidado y la discapacidad de su gente?* | Sí, me siento acompañado/a · Parcialmente, hay cierta comprensión · No, es algo bastante invisible · No, no se puede hablar del tema | RE-INDEX. Cambio 10: última opción reescrita (era "No, nunca se ha hablado del tema"). Breaking change — ver `OBSERVATIONS.md`. |
| 6.2 | multiple_choice (**max_select=3**) | ¿Qué apoyos de la empresa te ayudarían más? Elegí hasta 3 | Flexibilidad horaria o trabajo remoto · Apoyo económico o convenios/descuentos · Acceso a terapias o salud · Contención/apoyo emocional o psicológico · Capacitación y autocuidado · Redes de apoyo/grupos de pares · Información sobre recursos y servicios disponibles | ⚠️ Ranking → multiple_choice para v1. Ranking widget al backlog. |
| 6.3 | single_choice | ¿Estarías dispuesto/a a participar en un grupo de apoyo para cuidadores dentro de la empresa? | Sí, definitivamente · Tal vez, si hay más personas en la misma situación · No por ahora · Prefiero mantenerlo en privado | |
| 6.4 | single_choice | ¿Conocés a algún compañero/a de trabajo que esté al cuidado de una persona con discapacidad o dependiente? | Sí, conozco a varias personas · Sí, conozco a una persona · No conozco a nadie · No estoy seguro/a | Código corregido (era 6.2 duplicado) |
| 6.5 | single_choice | ¿Considerás que la empresa debería ofrecer apoyos a quienes cuidan a una persona dependiente? | Sí, definitivamente · Tal vez, dependería del caso · No lo considero necesario · No lo sé | Código corregido (era 6.3 duplicado) |
+| 6.6 | single_choice | ¿Estarías dispuesto/a a ser voluntario/a en iniciativas y/o redes de apoyo a personas con discapacidad y/o cuidadores? | Sí · No · Tal vez | Nueva (Cambio 4b, Etapa 3I2) |
## Sección 7 — Para cerrar (top-level)
| Code | Tipo | Pregunta | Notas |
diff --git a/TECH_DEBT.md b/TECH_DEBT.md
index bc8eaf8..f7e57cf 100644
--- a/TECH_DEBT.md
+++ b/TECH_DEBT.md
@@ -58,3 +58,17 @@
- **Para implementar:** migration futura que agregue extracción de A1/A2 en el insert
y extienda la whitelist de `agg_segment`.
- **Trigger:** inicio de Sprint 3 (dashboard avanzado con filtros de ciudad).
+
+---
+
+## [TECH-DEBT-006] Ranking widget para 2.1
+
+- **Estado:** Etapa 3I2 (Cambio 5) cambió 2.1 ("¿Quién es el/la responsable principal
+ del cuidado?") de `single_choice` a `multiple_choice`. Permite selección múltiple
+ pero sin orden de prioridad — pierde la noción de "responsable principal" cuando
+ el respondente marca más de una opción.
+- **Deuda:** implementar widget de ranking para 2.1 con el orden:
+ Principal cuidador soy yo > secundario instituto > principal pagado > secundario
+ familiar (mismo patrón que TECH-DEBT-001 para 6.2).
+- **Trigger:** decisión del director sobre el tipo de 2.1 en próxima versión del
+ instrumento.
diff --git a/app/admin/responses/page.tsx b/app/admin/responses/page.tsx
index 6f64b09..19734d1 100644
--- a/app/admin/responses/page.tsx
+++ b/app/admin/responses/page.tsx
@@ -1,20 +1,44 @@
import { createClient } from '@supabase/supabase-js'
-// Server Component sin auth — herramienta interna de diagnóstico (Etapa 3V).
+// Server Component sin auth — herramienta interna de diagnóstico (Etapa 3V-B).
// Usa SUPABASE_SERVICE_ROLE_KEY: vive solo acá, server-side, nunca al cliente.
export const dynamic = 'force-dynamic'
-const MAX_ROWS = 200
+const MAX_SUBMISSIONS = 500
+const PROMPT_MAX_LEN = 60
+
+interface AllowedQuestion {
+ id: string
+ code: string
+ prompt: string
+ type: string
+ position: number
+}
+
+interface AnswerRow {
+ value: unknown
+ question_id: string
+}
interface ResponseRow {
id: string
submitted_at: string
qi_zone: string | null
qi_sector: string | null
- qi_age_bucket: string | null
- ip_hash: string | null
- surveys: { title: string } | { title: string }[] | null
- answers: { count: number }[]
+ answers: AnswerRow[]
+}
+
+interface FlatRow {
+ key: string
+ submissionNum: number
+ isFirstInGroup: boolean
+ submitted_at: string
+ qi_zone: string | null
+ qi_sector: string | null
+ code: string
+ prompt: string
+ type: string
+ value: unknown
}
function getSupabaseAdmin() {
@@ -29,45 +53,125 @@ function formatDate(iso: string): string {
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}
-function dash(value: string | null): string {
- return value ?? '—'
+function truncate(text: string, max = PROMPT_MAX_LEN): string {
+ return text.length > max ? `${text.slice(0, max)}...` : text
+}
+
+// Spec 4.3 — null/undefined → null (renderizado como badge "sin dato")
+function renderValue(value: unknown, type: string): string | null {
+ if (value === null || value === undefined) return null
+
+ switch (type) {
+ case 'single_choice':
+ case 'text_short':
+ return String(value).replace(/^"|"$/g, '')
+ case 'multiple_choice':
+ if (Array.isArray(value)) return value.join(', ')
+ return String(value)
+ case 'rating':
+ return String(value)
+ default:
+ return String(value)
+ }
}
export default async function AdminResponsesPage() {
const supabase = getSupabaseAdmin()
- const { data, error } = await supabase
- .from('responses')
- .select(
- `
- id,
- submitted_at,
- qi_zone,
- qi_sector,
- qi_age_bucket,
- ip_hash,
- surveys ( title ),
- answers ( count )
- `
- )
- .order('submitted_at', { ascending: false })
- .limit(MAX_ROWS)
+ // 1. Preguntas permitidas — text_long e is_sensitive=true se excluyen ACÁ,
+ // antes de tocar la tabla answers. Sus valores nunca se consultan.
+ const { data: questionsData, error: questionsError } = await supabase
+ .from('questions')
+ .select('id, code, prompt, type, position')
+ .eq('is_sensitive', false)
+ .neq('type', 'text_long')
+ .order('position', { ascending: true })
- if (error) {
+ if (questionsError) {
return (
Control de respuestas
No fue posible conectarse a la base de datos.
-
{error.message}
+
{questionsError.message}
)
}
- const { count: totalCount } = await supabase
+ const allowedQuestions = (questionsData ?? []) as AllowedQuestion[]
+ const allowedIds = allowedQuestions.map((q) => q.id)
+ const questionMap = new Map(allowedQuestions.map((q) => [q.id, q]))
+
+ // 2. Submissions con sus respuestas — answers!inner + .in() filtra el ARRAY
+ // embebido a solo question_id permitidos (consulta, no post-filtro).
+ const { data: responsesData, error: responsesError } =
+ allowedIds.length > 0
+ ? await supabase
+ .from('responses')
+ .select(
+ `
+ id,
+ submitted_at,
+ qi_zone,
+ qi_sector,
+ answers!inner ( value, question_id )
+ `
+ )
+ .in('answers.question_id', allowedIds)
+ .order('submitted_at', { ascending: false })
+ .limit(MAX_SUBMISSIONS)
+ : { data: [] as ResponseRow[], error: null }
+
+ if (responsesError) {
+ return (
+
+
Control de respuestas
+
No fue posible conectarse a la base de datos.
+
{responsesError.message}
+
+ )
+ }
+
+ const responses = (responsesData ?? []) as unknown as ResponseRow[]
+
+ // 3. Contadores totales (independientes del límite de 500 submissions)
+ const { count: totalSubmissions } = await supabase
.from('responses')
.select('*', { count: 'exact', head: true })
- const rows = (data ?? []) as unknown as ResponseRow[]
+ const { count: totalAnswers } =
+ allowedIds.length > 0
+ ? await supabase
+ .from('answers')
+ .select('*', { count: 'exact', head: true })
+ .in('question_id', allowedIds)
+ : { count: 0 }
+
+ // 4. Aplanar: una fila por answer, ordenadas por position dentro de cada submission
+ const rows: FlatRow[] = []
+ responses.forEach((response, idx) => {
+ const answers = response.answers
+ .filter((a) => questionMap.has(a.question_id))
+ .sort(
+ (a, b) =>
+ questionMap.get(a.question_id)!.position - questionMap.get(b.question_id)!.position
+ )
+
+ answers.forEach((a, ai) => {
+ const q = questionMap.get(a.question_id)!
+ rows.push({
+ key: `${response.id}-${a.question_id}`,
+ submissionNum: idx + 1,
+ isFirstInGroup: ai === 0,
+ submitted_at: response.submitted_at,
+ qi_zone: response.qi_zone,
+ qi_sector: response.qi_sector,
+ code: q.code,
+ prompt: q.prompt,
+ type: q.type,
+ value: a.value,
+ })
+ })
+ })
return (
@@ -75,7 +179,7 @@ export default async function AdminResponsesPage() {
Control de respuestas
- {totalCount ?? 0} respuestas en total · últimas {rows.length} ·
+ {totalSubmissions ?? 0} submissions · {totalAnswers ?? 0} respuestas individuales ·
actualizado {formatDate(new Date().toISOString())}
@@ -88,29 +192,35 @@ export default async function AdminResponsesPage() {
| # |
Fecha |
- Encuesta |
- Departamento |
+ Código |
+ Pregunta |
+ Tipo |
+ Valor |
+ Departamento |
Sector |
- Rango etario |
- Respuestas |
- IP hash |
- {rows.map((row, i) => {
- const survey = Array.isArray(row.surveys) ? row.surveys[0] : row.surveys
- const answerCount = row.answers?.[0]?.count ?? 0
+ {rows.map((row) => {
+ const groupClass =
+ row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd'
+ const trClass = row.isFirstInGroup ? `${groupClass} admin-group-start` : groupClass
+ const rendered = renderValue(row.value, row.type)
return (
-
- | {i + 1} |
- {formatDate(row.submitted_at)} |
- {survey?.title ?? '—'} |
- {dash(row.qi_zone)} |
- {dash(row.qi_sector)} |
- {dash(row.qi_age_bucket)} |
- {answerCount} |
-
- {row.ip_hash ? `${row.ip_hash.slice(0, 8)}...` : '—'}
+ |
+ | {row.submissionNum} |
+ {row.isFirstInGroup ? formatDate(row.submitted_at) : ''} |
+ {row.code} |
+ {truncate(row.prompt)} |
+ {row.type} |
+
+ {rendered === null ? sin dato : rendered}
+ |
+
+ {row.isFirstInGroup ? (row.qi_zone ?? '—') : ''}
+ |
+
+ {row.isFirstInGroup ? (row.qi_sector ?? '—') : ''}
|
)
diff --git a/app/globals.css b/app/globals.css
index 2f7cafb..ff1eb46 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -765,11 +765,22 @@ body {
position: sticky; top: 0; background: var(--color-re-dark); color: #fff;
font-weight: 600; text-align: left; padding: 10px 12px; white-space: nowrap;
}
-.admin-table td { padding: 10px 12px; border-bottom: 1px solid var(--color-border); color: var(--color-text); }
-.admin-table tbody tr:nth-child(even) { background: var(--color-bg); }
-.admin-table tbody tr:hover { background: var(--color-primary-light); }
+.admin-table td { padding: 8px 12px; border-bottom: 1px solid var(--color-border); color: var(--color-text); vertical-align: top; }
.admin-table tbody tr:last-child td { border-bottom: none; }
-.admin-iphash { font-family: 'Courier New', monospace; color: var(--color-text-muted); }
+.admin-table tbody tr.admin-group-even { background: var(--color-bg); }
+.admin-table tbody tr.admin-group-start td { border-top: 2px solid var(--color-re-dark); }
+.admin-table tbody tr:hover { background: var(--color-primary-light); }
+.admin-prompt { color: var(--color-text); }
+.admin-type-badge {
+ display: inline-block; padding: 1px 8px; border-radius: 10px;
+ background: var(--color-primary-light); color: var(--color-primary-hover);
+ font-size: 0.7rem; font-weight: 600; white-space: nowrap;
+}
+.admin-nodata {
+ display: inline-block; padding: 1px 8px; border-radius: 10px;
+ background: var(--color-re-light); color: var(--color-re-dark);
+ opacity: 0.6; font-size: 0.7rem; font-style: italic;
+}
.admin-empty { padding: 24px; text-align: center; color: var(--color-text-muted); }
.admin-error { color: var(--color-error); font-weight: 600; }
.admin-error-detail {
diff --git a/components/survey-form.tsx b/components/survey-form.tsx
index 50563b9..d670522 100644
--- a/components/survey-form.tsx
+++ b/components/survey-form.tsx
@@ -2,7 +2,7 @@
import { useCallback, useEffect, useRef, useState, useTransition } from 'react'
import type { FormAnswers, Question, Survey } from '@/lib/types'
import { clearDraft, loadDraft, saveDraft } from '@/lib/storage'
-import { getHiddenCodes, isQuestionVisible } from '@/lib/conditional'
+import { getHiddenCodes, isQuestionVisible, ruleQuestionCodes } from '@/lib/conditional'
import { submitSurvey } from '@/app/actions/submit-survey'
import { ConsentScreen } from './consent-screen'
import { ProgressBar } from './progress-bar'
@@ -71,7 +71,7 @@ export function SurveyForm({ survey, questions }: SurveyFormProps) {
} else {
const appeared = questions.some(
(q) =>
- q.conditional_rules?.some((r) => r.if_question === code) &&
+ q.conditional_rules?.some((r) => ruleQuestionCodes(r).includes(code)) &&
isQuestionVisible(q, updated) &&
!isQuestionVisible(q, prev)
)
diff --git a/components/widgets/checkbox-group.tsx b/components/widgets/checkbox-group.tsx
index 9c3c7f7..461b08a 100644
--- a/components/widgets/checkbox-group.tsx
+++ b/components/widgets/checkbox-group.tsx
@@ -40,11 +40,13 @@ export function CheckboxGroup({
{required && *}
{required && (obligatorio)}
- {maxSelect !== null && (
+ {maxSelect !== null ? (
Elegí hasta {maxSelect} opciones
{value.length > 0 && ` · ${value.length}/${maxSelect} seleccionadas`}
+ ) : (
+ Podés marcar más de una opción
)}
{options.map((opt) => {
diff --git a/lib/conditional.ts b/lib/conditional.ts
index 22f961f..0353900 100644
--- a/lib/conditional.ts
+++ b/lib/conditional.ts
@@ -11,9 +11,17 @@ export function isQuestionVisible(
return rules.every((rule) => evaluateRule(rule, answers))
}
+/** Normalizes if_question to an array of codes regardless of rule shape. */
+export function ruleQuestionCodes(rule: ConditionalRule): string[] {
+ return Array.isArray(rule.if_question) ? rule.if_question : [rule.if_question]
+}
+
function evaluateRule(rule: ConditionalRule, answers: FormAnswers): boolean {
if (rule.op === 'eq') {
- return answers[rule.if_question] === rule.value
+ return answers[rule.if_question as string] === rule.value
+ }
+ if (rule.op === 'eq_any') {
+ return ruleQuestionCodes(rule).some((code) => answers[code] === rule.value)
}
return false
}
@@ -32,8 +40,8 @@ export function getHiddenCodes(
return questions
.filter((q) => {
if (!q.conditional_rules?.length) return false
- const dependsOnTrigger = q.conditional_rules.some(
- (r) => r.if_question === triggerCode
+ const dependsOnTrigger = q.conditional_rules.some((r) =>
+ ruleQuestionCodes(r).includes(triggerCode)
)
if (!dependsOnTrigger) return false
return !isQuestionVisible(q, projected)
@@ -46,7 +54,7 @@ export function getTriggerCodes(questions: Question[]): Set
{
const triggers = new Set()
for (const q of questions) {
for (const rule of q.conditional_rules ?? []) {
- triggers.add(rule.if_question)
+ for (const code of ruleQuestionCodes(rule)) triggers.add(code)
}
}
return triggers
diff --git a/lib/types.ts b/lib/types.ts
index 6d9fcc2..bcd1a51 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -19,8 +19,8 @@ export interface RatingOptions {
}
export interface ConditionalRule {
- if_question: string
- op: 'eq'
+ if_question: string | string[]
+ op: 'eq' | 'eq_any'
value: string
action: 'show'
}
diff --git a/migrate-claude/ESTADO_PROYECTO_JUNIO_2026.md b/migrate-claude/ESTADO_PROYECTO_JUNIO_2026.md
new file mode 100644
index 0000000..4d80148
--- /dev/null
+++ b/migrate-claude/ESTADO_PROYECTO_JUNIO_2026.md
@@ -0,0 +1,178 @@
+# ESTADO DEL PROYECTO — Datos País sobre el Cuidado
+## Documento de continuidad — junio 2026
+
+> Generado al cierre de una sesión extendida de dirección técnica.
+> Propósito: minimizar pérdida de contexto al abrir una nueva conversación.
+> Director: Marcos (Roy) · Metodología: Maria Mersan · Contacto Fundación: Valeria Arce
+
+---
+
+## 1. Qué es el proyecto
+
+Plataforma soberana de encuestas sobre economía del cuidado en Paraguay.
+- **Producto:** formulario anónimo + dashboard analítico con k-anonimato.
+- **Marca:** RE (Responsabilidad y Empatía Empresarial) · Fundación Solidaridad · InQuality.
+- **URL producción:** https://economia-cuidado.inqualityhq.com
+- **Survey demo:** https://economia-cuidado.inqualityhq.com/?s=30000000-0000-0000-0000-000000000001
+
+---
+
+## 2. Infraestructura actual — OPERATIVA ✅
+
+### Stack
+- **App:** Next.js 15.3.3 + TypeScript + Tailwind CSS
+- **DB:** Supabase self-hosted en Dokploy (stack oficial clonado)
+- **Deploy:** Dokploy v0.29.4 en VPS inq-mart-2 (IP VPN: 172.20.5.11)
+- **Repos:**
+ - App: `git.inquality.com.py/marcos/motor-de-encuestas` (migrado desde GitHub InqGit)
+ - Supabase stack: `git.inquality.com.py/marcos/supabase-motor-fomularios`
+- **Dominio API Supabase:** `db.inqualityhq.com` (Kong expuesto con SSL via Traefik/Let's Encrypt)
+
+### Supabase self-hosted — detalles técnicos
+- Stack corriendo desde repo Gitea (modo Git, NO docker raw — docker raw no tiene ./volumes/db/)
+- Kong conectado a `dokploy-network` (persistente en docker-compose.yml del repo)
+- Fix aplicado: `LUA_AUTH_EXPR` en `kong-entrypoint.sh` con prefijo `Bearer ` para PostgREST JWT propagation
+- Variables de entorno en Dokploy: ANON_KEY, SERVICE_ROLE_KEY, SUPABASE_ANON_KEY, SUPABASE_SERVICE_KEY (nombres que usa kong.yml)
+- SMTP_PORT=587 requerido aunque SMTP esté desactivado (GoTrue lo exige como int)
+- GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false (GoTrue lo exige como bool explícito)
+- Backups: script GFS en /usr/local/bin/supabase-backup.sh, cron 3AM root, retención 3 días diarios + domingos 14 días
+- Studio: solo accesible via SSH tunnel `ssh -L 3001:localhost:3001 administrator@172.20.5.11`
+
+### Variables de entorno de la app Next.js (Build Arguments en Dokploy)
+- NEXT_PUBLIC_SUPABASE_URL=https://db.inqualityhq.com
+- NEXT_PUBLIC_SUPABASE_ANON_KEY=
+- SUPABASE_SERVICE_ROLE_KEY= (Environment Setting, no Build Arg)
+
+---
+
+## 3. Estado del código — Sprint 2 COMPLETADO, Sprint 3 COMPLETADO
+
+### Migraciones aplicadas (12 en total, en orden)
+1. 20260531000001_create_tables.sql — 7 tablas
+2. 20260531000002_enable_rls.sql
+3. 20260531000003_rls_policies.sql — PARCIAL (ver TECH-DEBT-007)
+4. 20260531000004_constraints_triggers.sql
+5. 20260531000005_agg_segment.sql
+6. 20260531000006_grants.sql
+7. 20260602111851_revoke_consent_select.sql
+8. 20260604200932_fix_answers_insert_policy.sql
+9. 20260605093948_dashboard_rpc.sql
+10. 20260605093949_rls_deny_sensitive.sql
+11. 20260605140000_011_truncate_testing_data.sql
+12. 20260605140001_012_dashboard_rpc_v2.sql — 11 RPCs v2 del dashboard
+
+### Políticas RLS activas (7)
+- answers_insert, deny_sensitive_answers
+- consent_records_insert, consent_versions_select_all
+- questions_select_live, responses_insert
+- surveys_select_live_anon
+
+### Políticas RLS que fallaron (TECH-DEBT-007)
+- org_select_own, surveys_select_auth, questions_select_auth
+- Causa: auth.jwt() deprecado en GoTrue v2.189+ — no es bloqueante para el piloto
+
+### Dashboard — widgets y códigos reales del instrumento
+| Widget | Códigos reales | Subtítulo |
+|---|---|---|
+| K1 Cuidadores activos | 1.6 + 1.13 | % plantilla con responsabilidades de cuidado |
+| K2 Frecuencia ausentismo | 5.2 | % con 4+ ausencias al año |
+| K3 Bienestar | 4.3 (rating 1-5) | autorreporte bienestar general |
+| W1 Prevalencia por depto | A1 (Departamento) | % cuidadores activos por departamento |
+| W2 Perfil población | 1.1+1.6+1.13 | composición no excluyente |
+| W3 Madurez organizacional | 6.1(×0.7)+6.5(×0.3) | score 0-100 |
+| W4 Horas×Ausentismo | 2.2×5.2 | correlación intensidad/ausentismo |
+| W5 Pirámide demográfica | A5×A6 | etario por género (k≥5) |
+| W6 Apoyos demandados | 6.2 | top 8 multi-select |
+| W7 Intensidad vs Bienestar | 2.2×4.3 | bins k≥5 |
+| W8 Saturación cuidador | 4.3+5.1+5.2 | indicador compuesto |
+
+---
+
+## 4. Deuda técnica registrada
+
+| ID | Descripción | Bloqueante |
+|---|---|---|
+| TECH-DEBT-006 | Backup remoto S3 (Backblaze B2 o Wasabi ~$5/mes) | **SÍ — antes del primer dato real** |
+| TECH-DEBT-007 | auth.jwt() deprecado en GoTrue v2.189+ — current_app_role() y current_org_id() no funcionales | No (service_role bypasa RLS) |
+| CVE-2025-66478 | Next.js 15.3.3 con vulnerabilidad de seguridad crítica | **SÍ — antes del primer dato real** |
+
+---
+
+## 5. Prompts pendientes de ejecutar en Claude Code
+
+### ETAPA 2G — Cierre Sprint 2 (método)
+**Archivo:** `sprints/sprint-2/ETAPA_2G_PROMPT.md`
+**Qué hace:** promueve Patrón C.8 a estable + documenta AP.10 y AP.11 en method/ANTI_PATTERNS.md con casos reales del proyecto.
+
+### ETAPA 3V — Vista de control /admin/responses
+**Archivo:** `sprints/sprint-3-infra/ETAPA_3V_PROMPT.md`
+**Qué hace:** crea ruta Next.js /admin/responses que muestra tabla de todas las respuestas ordenadas por fecha descendente, sin auth, usando service_role.
+
+---
+
+## 6. Contexto comercial
+
+- **2 empresas comprometidas** para piloto (no en producción aún)
+- **Cronograma deck:** JUN 2026 inicio · JUL-AGO relevamiento · SEP análisis · OCT primer informe + evento
+- **Meta:** 20 empresas pioneras
+- **Precios:** Gs. 4.5M (≤50) · 7.5M (≤200) · 12M (≤400)
+- **GAPs deck vs. producto:**
+ - RE Index no implementado (fórmula: Bienestar×0.35 + Concentración×0.25 + Descanso×0.25 + Apoyo×0.15)
+ - Informe descargable prometido en el deck, no existe todavía
+- **Contacto Fundación:** Valeria Arce — ddff@fundacionsolidaridad.org.py · +595 991 854333
+
+---
+
+## 7. Documentos producidos en estas sesiones
+
+| Archivo | Descripción |
+|---|---|
+| INSTALL_SUPABASE.md | Guía instalación self-hosted (384 líneas) — con tabla de errores conocidos |
+| BACKUP_STRATEGY.md | Estrategia backup GFS (335 líneas) — con procedimiento de restore |
+| DOCUMENTACION_PLATAFORMA.md | Doc conceptual con 3 diagramas Mermaid |
+| DatoPais_DocumentacionConceptual.docx | Word con 3 PNGs de diagramas embebidos |
+| DatoPais_DimensionamientoEconomico.docx | Word con 5 charts económicos |
+| DatoPais_ValidacionInstrumento.xlsx | Planilla colaborativa 4 hojas |
+| sprints/sprint-2/ETAPA_2G_PROMPT.md | Cierre Sprint 2 — PENDIENTE ejecución |
+| sprints/sprint-3-infra/ETAPA_3V_PROMPT.md | Vista /admin/responses — PENDIENTE ejecución |
+
+---
+
+## 8. Decisiones clave tomadas (no reabrir)
+
+1. **docker raw de Dokploy es incompatible con Supabase** — usar siempre modo Git; el compose oficial necesita ./volumes/db/ que docker raw no tiene.
+2. **POSTGRES_PASSWORD siempre con openssl rand -hex** — base64 tiene caracteres especiales que rompen el escaping de Docker Compose.
+3. **kong.yml: una sola entrada por bloque keyauth_credentials** — dos entradas con el mismo valor JWT causa uniqueness violation.
+4. **SMTP_PORT=587 siempre explícito** aunque SMTP esté desactivado — GoTrue lo parsea como int y falla si está vacío.
+5. **GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false siempre explícito** — GoTrue lo parsea como bool y falla si está vacío.
+6. **LUA_AUTH_EXPR con prefijo "Bearer "** en kong-entrypoint.sh rama legacy — sin el prefijo PostgREST recibe JWT vacío.
+7. **kong.yml usa $ANON_KEY y $SERVICE_ROLE_KEY** — alineado con los nombres de variables en Dokploy; SUPABASE_ANON_KEY y SUPABASE_SERVICE_KEY son aliases adicionales.
+8. **App Next.js conecta a Supabase por URL pública** (https://db.inqualityhq.com) — las variables NEXT_PUBLIC_* se embeben en el bundle en build time y el navegador no puede resolver nombres Docker internos.
+9. **NEXT_PUBLIC_SUPABASE_URL va como Build Argument** en Dokploy, no como Environment Setting — se embebe en el bundle de JavaScript durante el build.
+10. **Repos en Gitea** (migrado desde GitHub InqGit) — VS Code configurado apuntando a git.inquality.com.py.
+
+---
+
+## 9. Patrones y anti-patrones del método (pendiente formalización en 2G)
+
+### C.8 — Verificación de códigos contra fuente única (PROMOVER A ESTABLE)
+Todos los widget subtitles y RPC mappings deben verificarse contra el instrumento real (SEED_SPEC.md). El drift silencioso entre instrumento e implementación es un failure mode de alto costo.
+
+### AP.10 — Propagación silenciosa de inconsistencia entre fuentes
+Caso real: Etapa 2D tenía subtítulos con códigos incorrectos que no se detectaron hasta auditoría explícita. Los errores de mapeo de códigos no generan errores en tiempo de compilación.
+
+### AP.11 — Contrato desactualizado por código
+Caso real: Etapa 1D dejó SEED_SPEC.md desactualizado respecto al seed.sql. El documento de referencia divergió del código sin señal visible.
+
+---
+
+## 10. Próximos pasos en orden de prioridad
+
+1. **TECH-DEBT-006** — Backup remoto S3 (bloqueante antes del primer dato real)
+2. **CVE-2025-66478** — Actualizar Next.js antes del primer dato real
+3. **ETAPA 2G** — Ejecutar en Claude Code (método, no infraestructura)
+4. **ETAPA 3V** — Ejecutar en Claude Code (vista /admin/responses)
+5. **RE Index** — Implementar como widget real (confirmar mapeo G1/G2/G4/G5 → códigos seed con Marcos primero)
+6. **Informe descargable** — Prometido en deck comercial, no existe
+7. **Onboarding 2 empresas comprometidas** — cuando validación esté lista
+8. **Validación instrumento** — Respuestas equipo de salud en DatoPais_ValidacionInstrumento.xlsx (12 preguntas metodológicas abiertas)
diff --git a/migrate-claude/PROMPT_NUEVA_SESION.md b/migrate-claude/PROMPT_NUEVA_SESION.md
new file mode 100644
index 0000000..d3e7c35
--- /dev/null
+++ b/migrate-claude/PROMPT_NUEVA_SESION.md
@@ -0,0 +1,70 @@
+# PROMPT DE CONTINUIDAD — nueva sesión de dirección técnica
+## Datos País sobre el Cuidado
+
+> Pegá este prompt al inicio de una nueva conversación con Claude.
+> Actualizado: junio 2026.
+
+---
+
+Sos el copiloto técnico del proyecto "Datos País sobre el Cuidado" — una plataforma soberana de encuestas sobre economía del cuidado en Paraguay. Ya tenemos múltiples sesiones de trabajo juntos. Este mensaje te pone al día antes de continuar.
+
+## Contexto del proyecto
+
+Plataforma de dos caras: formulario web anónimo + dashboard analítico con k-anonimato. Marca RE, Fundación Solidaridad, powered by InQuality.
+
+- **Stack:** Next.js 15.3.3 + TypeScript + Tailwind CSS + Supabase self-hosted
+- **Deploy:** Dokploy v0.29.4 en VPS (IP VPN: 172.20.5.11)
+- **URL producción:** https://economia-cuidado.inqualityhq.com
+- **Repos:** app en `git.inquality.com.py/marcos/motor-de-encuestas`, Supabase stack en `git.inquality.com.py/marcos/supabase-motor-fomularios`
+- **API Supabase:** https://db.inqualityhq.com (Kong con SSL)
+
+## Estado actual — Sprint 3 COMPLETADO
+
+El formulario y el dashboard están operativos con Supabase self-hosted. Las 12 migraciones están aplicadas. Backups locales configurados con retención GFS. El formulario carga y permite completar la encuesta end-to-end.
+
+## Deuda técnica abierta (ver TECH_DEBT.md en el repo)
+
+- **TECH-DEBT-006** — Backup remoto S3 — BLOQUEANTE antes del primer dato real de empresa
+- **TECH-DEBT-007** — auth.jwt() deprecado en GoTrue v2.189+ — no bloqueante para el piloto
+- **CVE-2025-66478** — Next.js 15.3.3 con vulnerabilidad crítica — BLOQUEANTE antes del primer dato real
+
+## Decisiones tomadas — no reabrir
+
+1. Supabase en Dokploy siempre via modo Git (docker raw no tiene ./volumes/db/)
+2. POSTGRES_PASSWORD siempre con openssl rand -hex (evita caracteres problemáticos)
+3. kong.yml: una sola entrada por bloque keyauth_credentials
+4. SMTP_PORT=587 siempre explícito aunque SMTP esté desactivado
+5. GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false siempre explícito
+6. LUA_AUTH_EXPR con prefijo "Bearer " en kong-entrypoint.sh rama legacy
+7. NEXT_PUBLIC_SUPABASE_URL via Build Argument en Dokploy (no Environment Setting)
+8. App Next.js conecta a Supabase por URL pública https://db.inqualityhq.com
+
+## Prompts pendientes de ejecutar en Claude Code
+
+1. **ETAPA 2G** — `sprints/sprint-2/ETAPA_2G_PROMPT.md` — cierre Sprint 2, formaliza patrones C.8, AP.10, AP.11 en el método
+2. **ETAPA 3V** — `sprints/sprint-3-infra/ETAPA_3V_PROMPT.md` — vista /admin/responses para control operativo
+
+## Próximos pasos en orden
+
+1. TECH-DEBT-006 (S3 backup) — bloqueante
+2. CVE-2025-66478 (Next.js update) — bloqueante
+3. Ejecutar ETAPA 2G en Claude Code
+4. Ejecutar ETAPA 3V en Claude Code
+5. RE Index — confirmar mapeo G1/G2/G4/G5 → códigos seed antes de implementar
+6. Informe descargable — prometido en deck comercial, no existe aún
+
+## Método de trabajo
+
+Usamos niveles de autonomía explícitos en los prompts (Nivel 1 = reportar antes de hacer, Nivel 3 = ejecutar con validación al final). Los prompts de Claude Code están en `sprints/`. Las decisiones arquitecturales se documentan como ADRs en `docs/adr/`. Las reglas no negociables del proyecto están en `CLAUDE.md` del repo.
+
+## Preguntas frecuentes de contexto
+
+**¿Por qué Supabase self-hosted y no Cloud?** Soberanía del dato — datos de cuidadores paraguayos no deben vivir en servidores de terceros fuera de control del proyecto. Decisión tomada antes de ingresar el primer dato real.
+
+**¿Por qué k-anonimato k≥5?** Regla no negociable del sistema. En empresas chicas, un cruce demográfico de menos de 5 personas puede reidentificar a un individuo aunque el nombre nunca se haya guardado.
+
+**¿Por qué el dashboard usa service_role?** Porque las políticas de org_admin con JWT custom claims no están implementadas (TECH-DEBT-007). Para el piloto, el dashboard es herramienta interna y service_role es aceptable. Cuando haya org_admin real, hay que resolver TECH-DEBT-007 primero.
+
+---
+
+¿Listo para continuar? El próximo paso es [COMPLETAR CON LO QUE VAYAS A HACER].
diff --git a/migrate-claude/TECH_DEBT_ACTUALIZADO.md b/migrate-claude/TECH_DEBT_ACTUALIZADO.md
new file mode 100644
index 0000000..85fa714
--- /dev/null
+++ b/migrate-claude/TECH_DEBT_ACTUALIZADO.md
@@ -0,0 +1,48 @@
+# TECH_DEBT.md — actualización junio 2026
+## Para aplicar en el repo del proyecto
+
+> Reemplazar el contenido existente de TECH_DEBT.md en el repo con este bloque,
+> o mergear los ítems nuevos si ya existe el archivo con otros ítems previos.
+
+---
+
+## Deuda técnica abierta
+
+### TECH-DEBT-006 — Backup remoto S3
+**Estado:** ABIERTO — BLOQUEANTE antes del primer dato real de empresa
+**Descripción:** Los backups locales están configurados y funcionando (GFS, 3AM diario, retención 3 días + domingos 14 días). Pero los backups viven solo en el mismo VPS. Si el VPS muere, se pierden datos Y backups.
+**Solución:** rsync nocturno a S3-compatible (Backblaze B2 o Wasabi, ~USD 5/mes) usando rclone.
+**Referencia:** BACKUP_STRATEGY.md sección 5 — TECH-DEBT-006.
+
+### TECH-DEBT-007 — auth.jwt() deprecado en GoTrue v2.189+
+**Estado:** ABIERTO — no bloqueante para el piloto
+**Descripción:** Las funciones helper `current_app_role()` y `current_org_id()` en migration 003 usan `auth.jwt()` que fue eliminado en GoTrue v2.189+. Las políticas que dependen de estas funciones no se crearon:
+- `org_select_own` en organizations
+- `surveys_select_auth` en surveys
+- `questions_select_auth` en questions
+**Impacto actual:** ninguno — el dashboard usa service_role que bypasa RLS. Los respondentes usan anon que tiene las políticas básicas de INSERT.
+**Impacto futuro:** org_admin no podrá autenticarse con custom claims (app_role, org_id) hasta resolver esto.
+**Solución:** migrar a auth.session() o implementar JWT claims via hook de GoTrue v2.
+**NO resolver hasta tener caso de uso real de org_admin autenticado.**
+
+### CVE-2025-66478 — Next.js 15.3.3 con vulnerabilidad de seguridad
+**Estado:** ABIERTO — BLOQUEANTE antes del primer dato real de empresa
+**Descripción:** Next.js 15.3.3 tiene una vulnerabilidad de seguridad crítica. El build lo reporta explícitamente: "This version has a security vulnerability. Please upgrade to a patched version."
+**Solución:** actualizar next en package.json a la versión parcheada disponible, correr npm install, hacer redeploy.
+**Referencia:** https://nextjs.org/blog/CVE-2025-66478
+
+---
+
+## Deuda técnica cerrada en Sprint 3
+
+### TECH-DEBT-002 — KPI NPS eliminado ✅
+KPI NPS eliminado — instrumento no tiene pregunta NPS.
+
+### TECH-DEBT-003 — Brecha de políticas ✅
+Widget W6 redefinido como demanda-only (6.2), sin oferta. Documentado.
+
+### TECH-DEBT-004 — Intención directa de salida no capturada
+Intención directa de salida laboral no capturada en el instrumento v2. No resuelto pero documentado como gap conocido.
+
+### TECH-DEBT-005 — A1 País vs A1b ✅
+Cerrado: A1 ya es Departamento (18 opciones Paraguay) en el instrumento final.
diff --git a/sprints/sprint-3-infra/ETAPA_3I2_PROMPT.md b/sprints/sprint-3-infra/ETAPA_3I2_PROMPT.md
new file mode 100644
index 0000000..50bf7fd
--- /dev/null
+++ b/sprints/sprint-3-infra/ETAPA_3I2_PROMPT.md
@@ -0,0 +1,174 @@
+# ETAPA 3I2 — Revisión del instrumento v2.1 (feedback equipo médico)
+
+## 1. Objetivo
+Aplicar 10 cambios al instrumento "Mapeo de Cuidadores" resultado del feedback del equipo
+de salud. Los cambios afectan SEED_SPEC.md, seed.sql, y el código del formulario Next.js.
+
+---
+
+## 2. Contexto previo
+- Instrumento actual: v2, 44 preguntas, 8 secciones, en `SEED_SPEC.md`.
+- La DB tiene al menos 1 respuesta real. Algunos cambios son breaking para datos existentes
+ (opciones renombradas, tipo cambiado) — documentar en OBSERVATIONS.md, no bloquear.
+- El formulario Next.js usa un sistema de conditional_rules en el JSON de cada pregunta.
+- Los 3 triggers (1.1, 1.6, 1.13) controlan bloques condicionales enteros.
+
+---
+
+## 3. DECISIONES YA TOMADAS — no consultar de nuevo
+- Cambio 4: el salto a 6.4 ocurre cuando las TRES preguntas 1.1, 1.6, 1.13 dan "No"
+ simultáneamente. Si al menos una da "Sí", las secciones 2-5 y 6.1-6.3 se muestran normalmente.
+- Cambio 4: las secciones 2, 3, 4, 5, 6.1, 6.2, 6.3 se saltean completamente — ir directo a 6.4.
+- Cambio 5: 2.1 pasa a multiple_choice. Es breaking para datos existentes — documentar, no bloquear.
+- Cambio 6: 2.4 se elimina del seed. Si hay respuestas con question_id de 2.4, se pierden
+ al re-seedear. Documentar en OBSERVATIONS.md.
+- Cambio 4: pregunta 6.6 opciones: Sí · No · Tal vez.
+- Cambio 3: "Tecnología asistiva" va en penúltima posición (antes de "No utilizo"/"No utiliza").
+- "Prefiero no decir" se elimina de TODAS las preguntas que lo tengan.
+
+---
+
+## 4. Los 10 cambios — especificación exacta
+
+### Cambio 1 — Eliminar "Prefiero no decir" de todo el instrumento
+Preguntas afectadas: A5, A6, 1.1, 1.5, 1.6, 1.13, 2.5, 3.1, 3.4.
+En cada una, eliminar la opción "Prefiero no decir" del array de opciones en seed.sql.
+En SEED_SPEC.md, eliminarla de la columna Opciones de cada pregunta.
+
+⚠️ Los triggers 1.1, 1.6, 1.13 quedan con solo 2 opciones: Sí · No.
+La lógica condicional no cambia — solo "Sí" dispara el bloque.
+
+### Cambio 2 — Subtítulo hint en todas las preguntas multiple_choice
+En el componente del formulario que renderiza preguntas de tipo `multiple_choice`,
+agregar un subtítulo/hint debajo del prompt: "Podés marcar más de una opción".
+Preguntas afectadas: 1.2, 1.3, 1.7, 1.8, 1.9, 1.10, 1.15, 2.3, 3.2, 3.3, 4.2, 6.2.
+Es un cambio de UI — no afecta seed.sql ni SEED_SPEC.md.
+
+### Cambio 3 — Agregar "Tecnología asistiva" en 1.3 y 1.10
+1.3 opciones actuales: Silla de ruedas · Bastón · Andador · Muletas · Prótesis · No utilizo
+1.3 opciones nuevas: Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utilizo
+
+1.10 opciones actuales: Silla de ruedas · Bastón · Andador · Muletas · Prótesis · No utiliza
+1.10 opciones nuevas: Silla de ruedas · Bastón · Andador · Muletas · Prótesis · Tecnología asistiva · No utiliza
+
+### Cambio 4 — Salto condicional triple + nueva pregunta 6.6
+
+#### 4a. Lógica de salto
+Cuando las tres responden "No": 1.1="No" AND 1.6="No" AND 1.13="No",
+las secciones 2, 3, 4, 5 completas + preguntas 6.1, 6.2, 6.3 se ocultan.
+El formulario salta directamente a 6.4.
+
+Implementación sugerida: agregar una regla condicional en las preguntas de las secciones
+2-5 y 6.1-6.3 que las oculte cuando se cumple esa condición triple. La forma exacta de
+implementarlo (conditional_rules en cada pregunta vs. lógica en el componente de sección)
+la decide Claude Code según la arquitectura existente del formulario.
+
+#### 4b. Nueva pregunta 6.6
+Agregar al final de la Sección 6 (después de 6.5, antes de Sección 7):
+- Code: 6.6
+- Tipo: single_choice
+- Pregunta: "¿Estarías dispuesto/a a ser voluntario/a en iniciativas y/o redes de apoyo
+ a personas con discapacidad y/o cuidadores?"
+- Opciones: Sí · No · Tal vez
+- is_sensitive: false
+- top-level (siempre visible)
+
+Actualizar position de 7.1 para que quede después de 6.6.
+Actualizar el conteo total de preguntas en SEED_SPEC.md (pasa de 44 a 45, o 44 si se
+compensa con la eliminación de 2.4 — verificar el conteo final).
+
+### Cambio 5 — 2.1 pasa a multiple_choice
+Cambiar tipo de `single_choice` a `multiple_choice` en seed.sql y SEED_SPEC.md.
+Agregar nota en SEED_SPEC.md:
+"⚠️ TECH-DEBT: múltiple selección sin orden de prioridad. Deuda: implementar ranking
+(Principal cuidador soy yo > secundario instituto > principal pagado > secundario familiar)."
+
+### Cambio 6 — Eliminar 2.4
+Eliminar la pregunta 2.4 ("¿Contás con apoyo de otras personas para el cuidado?")
+del seed.sql y SEED_SPEC.md.
+Renumerar positions de las preguntas posteriores si es necesario.
+Documentar en OBSERVATIONS.md: "2.4 eliminada por feedback equipo médico (similar a 2.1)."
+
+### Cambio 7 — En 3.1 eliminar "Sin gastos significativos"
+El cambio 1 ya elimina "Prefiero no decir" de 3.1.
+Adicionalmente, eliminar "Sin gastos significativos" de las opciones de 3.1.
+3.1 opciones finales: Menos de Gs. 500.000 · Gs. 500.000–1.500.000 · Gs. 1.500.000–3.000.000
+ · Gs. 3.000.000–6.000.000 · Más de Gs. 6.000.000
+
+### Cambio 8 — En 3.3 cambiar texto de opción
+Cambiar: "Es difícil sostenerlo con mis ingresos"
+Por: "No puedo sostenerlo con mis ingresos"
+En seed.sql y SEED_SPEC.md.
+Documentar en OBSERVATIONS.md: "Respuestas existentes con el texto anterior quedan
+con valor 'Es difícil sostenerlo con mis ingresos' — breaking change en datos existentes."
+
+### Cambio 9 — En 3.4 reestructurar primera franja salarial
+Opción actual: "Sueldo mínimo – Gs. 5.000.000"
+Reemplazar por dos opciones:
+ 1. "Sueldo mínimo"
+ 2. "Gs. 3.000.000 a 5.000.000"
+Las demás franjas no cambian.
+Documentar en OBSERVATIONS.md: "Respuestas existentes con 'Sueldo mínimo – Gs. 5.000.000'
+quedan con valor anterior — breaking change en datos existentes."
+
+### Cambio 10 — En 6.1 cambiar texto de última opción
+Cambiar: "No, nunca se ha hablado del tema"
+Por: "No, no se puede hablar del tema"
+En seed.sql y SEED_SPEC.md.
+Documentar en OBSERVATIONS.md: "Breaking change en datos existentes para esta opción."
+
+---
+
+## 5. Archivos a modificar
+
+| Archivo | Cambios |
+|---|---|
+| `SEED_SPEC.md` | Todos los cambios de opciones, tipos, preguntas nuevas/eliminadas |
+| `supabase/seed.sql` | Todos los cambios — es la fuente de verdad para re-seedear |
+| Componente formulario (multiple_choice) | Cambio 2: agregar hint |
+| Lógica condicional del formulario | Cambio 4a: salto triple |
+| `OBSERVATIONS.md` o `TECH_DEBT.md` | Documentar breaking changes y nueva deuda |
+
+---
+
+## 6. Entregables (DoD)
+
+- [ ] SEED_SPEC.md actualizado con todos los cambios.
+- [ ] seed.sql actualizado — re-seedeable desde cero produce el instrumento v2.1.
+- [ ] Componente multiple_choice muestra hint "Podés marcar más de una opción".
+- [ ] Lógica de salto 1.1+1.6+1.13 todos "No" → salta a 6.4.
+- [ ] Pregunta 6.6 visible en el formulario.
+- [ ] "Prefiero no decir" ausente en todas las preguntas.
+- [ ] 2.4 eliminada del seed.
+- [ ] Breaking changes documentados en OBSERVATIONS.md.
+- [ ] Build exitoso.
+- [ ] `git diff --name-only HEAD` en scope.
+
+## 7. Qué reportar
+```
+Verificado:
+- SEED_SPEC.md actualizado ✓
+- seed.sql actualizado ✓
+- Conteo final de preguntas: [N]
+- Hint en multiple_choice: ✓
+- Salto triple implementado: ✓ (describir cómo)
+- 6.6 agregada ✓
+- "Prefiero no decir" eliminado de: [lista de preguntas]
+- 2.4 eliminada ✓
+- Build exitoso ✓
+- Diff en scope ✓
+
+Breaking changes documentados en OBSERVATIONS.md:
+- [lista]
+
+Asumido:
+- [decisiones de implementación]
+```
+
+## 8. Qué NO hacer
+- ❌ No re-seedear la DB de producción — solo actualizar seed.sql.
+ Re-seedear producción requiere aprobación explícita del director.
+- ❌ No cambiar la estructura de tablas ni agregar columnas — solo opciones y lógica.
+- ❌ No tocar el dashboard ni las RPCs.
+- ❌ No resolver ítems de TECH_DEBT que no estén listados acá.
+- ❌ No cambiar el diseño visual del formulario más allá del hint de multiple_choice.
\ No newline at end of file
diff --git a/sprints/sprint-3-infra/ETAPA_3V_B_PROMPT.md b/sprints/sprint-3-infra/ETAPA_3V_B_PROMPT.md
new file mode 100644
index 0000000..e1e8ea0
--- /dev/null
+++ b/sprints/sprint-3-infra/ETAPA_3V_B_PROMPT.md
@@ -0,0 +1,175 @@
+# ETAPA 3V-B — Vista de control: tabla plana de respuestas individuales
+
+## 1. Objetivo
+Reemplazar la vista `/admin/responses` actual (una fila por submission) por una tabla plana
+con **una fila por respuesta individual** (una fila por cada pregunta respondida), mostrando
+todos los campos explícitamente incluyendo nulos marcados visualmente.
+
+---
+
+## 2. Contexto previo
+- La Etapa 3V-A implementó la vista de metadatos de submissions (una fila por response).
+- El director quiere ver las respuestas individuales a las preguntas, no solo los metadatos.
+- El esquema relevante: `responses` → `answers` (via response_id) → `questions` (via question_id).
+- `answers.value` es JSONB. Los tipos de preguntas existentes en el instrumento son:
+ - `single_choice` → value es string: `"Asunción (Capital)"`
+ - `multiple_choice` → value es array: `["opción1","opción2"]`
+ - `rating` → value es número: `3`
+ - `text_short` → value es string corto
+ - `text_long` → **NUNCA mostrar** — regla no negociable (RLS + privacidad)
+
+---
+
+## 3. DECISIONES YA TOMADAS — no consultar de nuevo
+- Ruta: `/admin/responses` — reemplaza la página anterior completamente.
+- Sin auth (herramienta interna de control).
+- Sin paginación por ahora — máximo 500 filas (limit en la query).
+- Ordenado por `responses.submitted_at DESC`, luego por `questions.position ASC` dentro de cada submission.
+- Valores nulos mostrados con badge visual "sin dato" — fondo `--color-re-light` (#E7E6E5), texto `--color-re-dark` (#494F4F) opacidad 0.6.
+- text_long excluido completamente — ni la columna ni el valor aparecen.
+- Convención CSS: `.admin-*` en `app/globals.css`, igual que la etapa anterior.
+- Cliente service-role inline en el Server Component, igual que en la versión anterior.
+
+---
+
+## 4. Estructura de la tabla
+
+### 4.1 Encabezado de página
+- Título: "Control de respuestas"
+- Subtítulo: total de submissions + total de respuestas individuales + fecha/hora actualización.
+- Botón "Actualizar" (``).
+
+### 4.2 Columnas de la tabla (una fila por answer)
+
+| Columna | Fuente | Notas |
+|---|---|---|
+| # submission | índice de la submission (1, 2, 3...) | mismo número para todas las filas de esa submission |
+| Fecha | `responses.submitted_at` | DD/MM/YYYY HH:mm — solo en la primera fila de cada submission |
+| Código | `questions.code` | A1, A2, 1.1, 1.6, etc. |
+| Pregunta | `questions.prompt` | texto de la pregunta, truncado a 60 chars con "..." |
+| Tipo | `questions.type` | badge con el tipo |
+| Valor | `answers.value` | renderizado según tipo (ver 4.3) |
+| Departamento | `responses.qi_zone` | solo en primera fila de cada submission |
+| Sector | `responses.qi_sector` | solo en primera fila de cada submission |
+
+### 4.3 Renderizado del valor según tipo
+
+```typescript
+function renderValue(value: unknown, type: string): string {
+ if (value === null || value === undefined) return null // → badge "sin dato"
+
+ switch(type) {
+ case 'single_choice':
+ case 'text_short':
+ return String(value).replace(/^"|"$/g, '') // quitar comillas del JSONB string
+
+ case 'multiple_choice':
+ if (Array.isArray(value)) return value.join(', ')
+ return String(value)
+
+ case 'rating':
+ return String(value) // número, opcionalmente con ★ repetidas
+
+ case 'text_long':
+ return null // NUNCA mostrar — excluir de la query directamente
+
+ default:
+ return String(value)
+ }
+}
+```
+
+### 4.4 Agrupación visual de submissions
+Las filas de una misma submission deben estar visualmente agrupadas:
+- Separador visual entre submissions (border-top más pronunciado o fondo alternado por submission).
+- Fecha, Departamento y Sector solo se muestran en la primera fila de cada submission.
+- Las filas siguientes de la misma submission muestran esas celdas vacías (o con colspan si es HTML puro).
+
+---
+
+## 5. Query recomendada
+
+```typescript
+const { data } = await supabase
+ .from('responses')
+ .select(`
+ id,
+ submitted_at,
+ qi_zone,
+ qi_sector,
+ qi_age_bucket,
+ ip_hash,
+ surveys ( title ),
+ answers (
+ value,
+ questions (
+ code,
+ prompt,
+ type,
+ position,
+ is_sensitive
+ )
+ )
+ `)
+ .order('submitted_at', { ascending: false })
+ .limit(500)
+```
+
+Después en el componente, aplanar el resultado:
+```typescript
+// Para cada response, para cada answer (excluir text_long e is_sensitive):
+// generar una fila con los datos de la response + los datos de la answer/question
+const rows = responses.flatMap((response, responseIdx) =>
+ response.answers
+ .filter(a => a.questions?.type !== 'text_long' && !a.questions?.is_sensitive)
+ .sort((a, b) => (a.questions?.position ?? 0) - (b.questions?.position ?? 0))
+ .map((answer, answerIdx) => ({
+ responseIdx: responseIdx + 1,
+ isFirstInGroup: answerIdx === 0,
+ submitted_at: response.submitted_at,
+ qi_zone: response.qi_zone,
+ qi_sector: response.qi_sector,
+ ip_hash: response.ip_hash,
+ survey_title: response.surveys?.title,
+ code: answer.questions?.code,
+ prompt: answer.questions?.prompt,
+ type: answer.questions?.type,
+ value: answer.value,
+ }))
+)
+```
+
+---
+
+## 6. Entregables (DoD)
+
+- [ ] Ruta `/admin/responses` renderiza con tabla plana (HTTP 200).
+- [ ] Una fila por answer, agrupadas visualmente por submission.
+- [ ] Columnas: # submission, Fecha, Código, Pregunta (truncada), Tipo, Valor, Departamento, Sector.
+- [ ] `text_long` e `is_sensitive=true` excluidos.
+- [ ] Valores nulos muestran badge "sin dato" con estilo tenue.
+- [ ] Fecha, Departamento, Sector solo en primera fila de cada grupo.
+- [ ] Build exitoso.
+- [ ] `git diff --name-only HEAD` solo en `app/admin/` y `app/globals.css`.
+
+## 7. Qué reportar
+```
+Verificado:
+- Ruta /admin/responses accesible ✓
+- Total submissions: [N] · Total filas en tabla: [N]
+- Tipos de preguntas visibles: [lista]
+- text_long excluido ✓ / is_sensitive excluido ✓
+- Badge "sin dato" visible en campos nulos ✓ / no hay nulos en los datos actuales
+- Build exitoso ✓
+- Diff en scope ✓
+
+Asumido:
+- [decisiones cosméticas]
+```
+
+## 8. Qué NO hacer
+- ❌ No mostrar text_long ni is_sensitive bajo ningún concepto.
+- ❌ No exponer SUPABASE_SERVICE_ROLE_KEY al cliente.
+- ❌ No tocar el formulario ni el dashboard.
+- ❌ No agregar dependencias npm.
+- ❌ No paginar todavía — limit 500 es suficiente para el piloto.
\ No newline at end of file
diff --git a/supabase/seed.sql b/supabase/seed.sql
index 3a1e5b7..b3f3c29 100644
--- a/supabase/seed.sql
+++ b/supabase/seed.sql
@@ -1,13 +1,15 @@
-- ============================================================
--- seed.sql — Instrumento "Mapeo de Cuidadores" v2 — 44 preguntas
--- Fuente autoritativa: SEED_SPEC.md v2 (Jun 2026)
+-- seed.sql — Instrumento "Mapeo de Cuidadores" v2.1 — 44 preguntas
+-- Fuente autoritativa: SEED_SPEC.md v2.1 (Jun 2026) — Etapa 3I2
-- Reemplaza v1 (34 preguntas con códigos A-H)
--
-- Estructura condicional:
-- cond-disc-propia: 1.1='Sí' → 1.2–1.5 (4 dependientes)
-- cond-familiar: 1.6='Sí' → 1.7–1.12 (6 dependientes)
-- cond-adulto-mayor: 1.13='Sí' → 1.14–1.16 (3 dependientes)
--- Resto: top-level, siempre visibles
+-- salto-triple (Cambio 4a): si 1.1, 1.6 y 1.13 son TODAS 'No', se ocultan
+-- las secciones 2-5 completas y 6.1-6.3 (eq_any 'Sí' sobre 1.1/1.6/1.13)
+-- Resto (incl. 6.4-6.6 y sección 7): top-level, siempre visibles
--
-- is_sensitive=true: 1.1-1.12, 1.14, 1.15, 3.4 (15 preguntas)
-- required=false en todas (equipo decidió captura completa; obligatoriedad se define luego)
@@ -16,7 +18,8 @@
-- org: 10000000-0000-0000-0000-000000000001
-- cv-v1: 20000000-0000-0000-0000-000000000001
-- survey: 30000000-0000-0000-0000-000000000001
--- preguntas 40000000-0000-0000-0000-00000000000{01..44}
+-- preguntas 40000000-0000-0000-0000-00000000000{01..45} excepto 027 (2.4, eliminada)
+-- → 44 preguntas activas
-- ============================================================
-- ── Organización demo ─────────────────────────────────────────
@@ -137,16 +140,14 @@ VALUES ('40000000-0000-0000-0000-000000000005','30000000-0000-0000-0000-00000000
{"value":"25 a 34 años","label":"25 a 34 años"},
{"value":"35 a 44 años","label":"35 a 44 años"},
{"value":"45 a 54 años","label":"45 a 54 años"},
- {"value":"55 años o más","label":"55 años o más"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"55 años o más","label":"55 años o más"}]'::jsonb);
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
VALUES ('40000000-0000-0000-0000-000000000006','30000000-0000-0000-0000-000000000001',
'A6','single_choice','¿Con qué género te identificás?',6,false,false,
'[{"value":"Femenino","label":"Femenino"},
{"value":"Masculino","label":"Masculino"},
- {"value":"No binario/Otro","label":"No binario/Otro"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"No binario/Otro","label":"No binario/Otro"}]'::jsonb);
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
VALUES ('40000000-0000-0000-0000-000000000007','30000000-0000-0000-0000-000000000001',
@@ -168,8 +169,7 @@ VALUES ('40000000-0000-0000-0000-000000000008','30000000-0000-0000-0000-00000000
'¿Vos mismo/a vivís con alguna discapacidad o condición de salud que afecte tu funcionamiento cotidiano?',
8,true,false,
'[{"value":"Sí","label":"Sí"},
- {"value":"No","label":"No"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"No","label":"No"}]'::jsonb);
-- cond-disc-propia: 1.2–1.5 se muestran si 1.1='Sí'
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
@@ -210,8 +210,7 @@ VALUES ('40000000-0000-0000-0000-000000000012','30000000-0000-0000-0000-00000000
'1.5','single_choice','¿Tu condición afecta tu desempeño o comodidad en el trabajo?',12,true,false,
'[{"value":"Sí, de forma significativa","label":"Sí, de forma significativa"},
{"value":"Sí, pero en aspectos puntuales","label":"Sí, pero en aspectos puntuales"},
- {"value":"No, está bien gestionada","label":"No, está bien gestionada"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb,
+ {"value":"No, está bien gestionada","label":"No, está bien gestionada"}]'::jsonb,
'[{"if_question":"1.1","op":"eq","value":"Sí","action":"show"}]'::jsonb);
-- 1.6 trigger — is_sensitive=true
@@ -221,8 +220,7 @@ VALUES ('40000000-0000-0000-0000-000000000013','30000000-0000-0000-0000-00000000
'¿Tenés algún familiar cercano (hijo/a, pareja, hermano/a, padre/madre) que viva con discapacidad?',
13,true,false,
'[{"value":"Sí","label":"Sí"},
- {"value":"No","label":"No"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"No","label":"No"}]'::jsonb);
-- cond-familiar: 1.7–1.12 se muestran si 1.6='Sí'
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
@@ -267,6 +265,7 @@ VALUES ('40000000-0000-0000-0000-000000000017','30000000-0000-0000-0000-00000000
{"value":"Andador","label":"Andador"},
{"value":"Muletas","label":"Muletas"},
{"value":"Prótesis","label":"Prótesis"},
+ {"value":"Tecnología asistiva","label":"Tecnología asistiva"},
{"value":"No utiliza","label":"No utiliza"}]'::jsonb,
'[{"if_question":"1.6","op":"eq","value":"Sí","action":"show"}]'::jsonb);
@@ -296,8 +295,7 @@ VALUES ('40000000-0000-0000-0000-000000000020','30000000-0000-0000-0000-00000000
'1.13','single_choice','¿Tenés algún adulto mayor (60+ años) a tu cuidado o bajo tu responsabilidad?',
20,false,false,
'[{"value":"Sí","label":"Sí"},
- {"value":"No","label":"No"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"No","label":"No"}]'::jsonb);
-- cond-adulto-mayor: 1.14–1.16 se muestran si 1.13='Sí'
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
@@ -331,26 +329,30 @@ VALUES ('40000000-0000-0000-0000-000000000023','30000000-0000-0000-0000-00000000
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 2 — Intensidad y rol del cuidado (top-level)
+-- Cambio 4a: visible si 1.1='Sí' OR 1.6='Sí' OR 1.13='Sí' (eq_any)
-- ══════════════════════════════════════════════════════════════
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+-- 2.1: Cambio 5 — pasa a multiple_choice (TECH-DEBT: ranking, ver TECH_DEBT.md)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000024','30000000-0000-0000-0000-000000000001',
- '2.1','single_choice','¿Quién es el/la responsable principal del cuidado?',24,false,false,
+ '2.1','multiple_choice','¿Quién es el/la responsable principal del cuidado?',24,false,false,
'[{"value":"Yo soy el/la principal","label":"Yo soy el/la principal"},
{"value":"Comparto con otro familiar","label":"Comparto con otro familiar"},
{"value":"Hay cuidador/a pagado/a","label":"Hay cuidador/a pagado/a"},
- {"value":"Está en institución/centro de día","label":"Está en institución/centro de día"}]'::jsonb);
+ {"value":"Está en institución/centro de día","label":"Está en institución/centro de día"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000025','30000000-0000-0000-0000-000000000001',
'2.2','single_choice','¿Cuántas horas por día dedicás, en promedio, al cuidado directo?',25,false,false,
'[{"value":"Menos de 1h","label":"Menos de 1h"},
{"value":"Entre 1 y 3h","label":"Entre 1 y 3h"},
{"value":"Entre 3 y 6h","label":"Entre 3 y 6h"},
{"value":"Más de 6h","label":"Más de 6h"},
- {"value":"Varía mucho","label":"Varía mucho"}]'::jsonb);
+ {"value":"Varía mucho","label":"Varía mucho"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000026','30000000-0000-0000-0000-000000000001',
'2.3','multiple_choice','¿En qué momentos del día cuidás principalmente?',26,false,false,
'[{"value":"Antes del trabajo","label":"Antes del trabajo"},
@@ -358,98 +360,99 @@ VALUES ('40000000-0000-0000-0000-000000000026','30000000-0000-0000-0000-00000000
{"value":"Al llegar del trabajo","label":"Al llegar del trabajo"},
{"value":"Durante la noche","label":"Durante la noche"},
{"value":"Los fines de semana","label":"Los fines de semana"},
- {"value":"Es continuo sin horario fijo","label":"Es continuo sin horario fijo"}]'::jsonb);
+ {"value":"Es continuo sin horario fijo","label":"Es continuo sin horario fijo"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
-VALUES ('40000000-0000-0000-0000-000000000027','30000000-0000-0000-0000-000000000001',
- '2.4','single_choice','¿Contás con apoyo de otras personas para el cuidado?',27,false,false,
- '[{"value":"Sí, apoyo familiar suficiente","label":"Sí, apoyo familiar suficiente"},
- {"value":"Algo de apoyo, no suficiente","label":"Algo de apoyo, no suficiente"},
- {"value":"No, soy el/la único/a responsable","label":"No, soy el/la único/a responsable"},
- {"value":"Tengo apoyo contratado","label":"Tengo apoyo contratado"}]'::jsonb);
+-- 2.4 eliminada (Cambio 6 — feedback equipo médico, similar a 2.1). Ver OBSERVATIONS.md.
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000028','30000000-0000-0000-0000-000000000001',
'2.5','single_choice',
- 'Como cuidador/a primario, ¿presentás signos de agotamiento o estrés crónico?',28,false,false,
+ 'Como cuidador/a primario, ¿presentás signos de agotamiento o estrés crónico?',27,false,false,
'[{"value":"Sí, frecuentemente","label":"Sí, frecuentemente"},
{"value":"A veces","label":"A veces"},
- {"value":"No, lo llevo bien","label":"No, lo llevo bien"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"No, lo llevo bien","label":"No, lo llevo bien"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 3 — Impacto económico del cuidado (top-level)
-- 3.4: is_sensitive=true + analyst-only (no exponer a org_admin)
+-- Cambio 4a: visible si 1.1='Sí' OR 1.6='Sí' OR 1.13='Sí' (eq_any)
-- ══════════════════════════════════════════════════════════════
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000029','30000000-0000-0000-0000-000000000001',
- '3.1','single_choice','¿Cuánto estimás que gastás por mes en el cuidado?',29,false,false,
- '[{"value":"Sin gastos significativos","label":"Sin gastos significativos"},
- {"value":"Menos de Gs. 500.000","label":"Menos de Gs. 500.000"},
+ '3.1','single_choice','¿Cuánto estimás que gastás por mes en el cuidado?',28,false,false,
+ '[{"value":"Menos de Gs. 500.000","label":"Menos de Gs. 500.000"},
{"value":"Gs. 500.000–1.500.000","label":"Gs. 500.000–1.500.000"},
{"value":"Gs. 1.500.000–3.000.000","label":"Gs. 1.500.000–3.000.000"},
{"value":"Gs. 3.000.000–6.000.000","label":"Gs. 3.000.000–6.000.000"},
- {"value":"Más de Gs. 6.000.000","label":"Más de Gs. 6.000.000"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"Más de Gs. 6.000.000","label":"Más de Gs. 6.000.000"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000030','30000000-0000-0000-0000-000000000001',
- '3.2','multiple_choice','¿Qué servicios o apoyos utilizás actualmente?',30,false,false,
+ '3.2','multiple_choice','¿Qué servicios o apoyos utilizás actualmente?',29,false,false,
'[{"value":"Terapias especializadas","label":"Terapias especializadas"},
{"value":"Cuidador/a particular","label":"Cuidador/a particular"},
{"value":"Centro de día/institución","label":"Centro de día/institución"},
{"value":"Transporte adaptado","label":"Transporte adaptado"},
{"value":"Medicación alto costo","label":"Medicación alto costo"},
{"value":"Equipamiento médico/tecnología asistiva","label":"Equipamiento médico/tecnología asistiva"},
- {"value":"No utilizo servicios formales","label":"No utilizo servicios formales"}]'::jsonb);
+ {"value":"No utilizo servicios formales","label":"No utilizo servicios formales"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000031','30000000-0000-0000-0000-000000000001',
- '3.3','multiple_choice','¿Cómo sostenés los gastos y responsabilidades del cuidado?',31,false,false,
+ '3.3','multiple_choice','¿Cómo sostenés los gastos y responsabilidades del cuidado?',30,false,false,
'[{"value":"Con mi empleo de tiempo completo","label":"Con mi empleo de tiempo completo"},
{"value":"Con mi empleo de tiempo parcial","label":"Con mi empleo de tiempo parcial"},
{"value":"Con trabajo independiente/emprendimiento","label":"Con trabajo independiente/emprendimiento"},
{"value":"Me ayudan mis familiares","label":"Me ayudan mis familiares"},
- {"value":"Es difícil sostenerlo con mis ingresos","label":"Es difícil sostenerlo con mis ingresos"}]'::jsonb);
+ {"value":"No puedo sostenerlo con mis ingresos","label":"No puedo sostenerlo con mis ingresos"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-- 3.4: CONFIDENCIAL — is_sensitive=true, analyst-only
-- No exponer en ninguna vista accesible a org_admin (ADR-001 addendum)
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000032','30000000-0000-0000-0000-000000000001',
- '3.4','single_choice','¿En qué franja salarial mensual te ubicás?',32,true,false,
- '[{"value":"Sueldo mínimo – Gs. 5.000.000","label":"Sueldo mínimo – Gs. 5.000.000"},
+ '3.4','single_choice','¿En qué franja salarial mensual te ubicás?',31,true,false,
+ '[{"value":"Sueldo mínimo","label":"Sueldo mínimo"},
+ {"value":"Gs. 3.000.000 a 5.000.000","label":"Gs. 3.000.000 a 5.000.000"},
{"value":"Gs. 5.000.001–8.000.000","label":"Gs. 5.000.001–8.000.000"},
{"value":"Gs. 8.000.001–12.000.000","label":"Gs. 8.000.001–12.000.000"},
{"value":"Gs. 12.000.001–15.000.000","label":"Gs. 12.000.001–15.000.000"},
{"value":"Gs. 15.000.001–18.000.000","label":"Gs. 15.000.001–18.000.000"},
{"value":"Gs. 18.000.001–25.000.000","label":"Gs. 18.000.001–25.000.000"},
{"value":"Gs. 25.000.001–50.000.000","label":"Gs. 25.000.001–50.000.000"},
- {"value":"Gs. 50.000.001 o más","label":"Gs. 50.000.001 o más"},
- {"value":"Prefiero no decir","label":"Prefiero no decir"}]'::jsonb);
+ {"value":"Gs. 50.000.001 o más","label":"Gs. 50.000.001 o más"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000033','30000000-0000-0000-0000-000000000001',
- '3.5','rating','¿Los gastos de cuidado impactan en tu situación económica personal?',33,false,false,
- '{"min":1,"max":5,"min_label":"Sin impacto","max_label":"Muy alto impacto"}'::jsonb);
+ '3.5','rating','¿Los gastos de cuidado impactan en tu situación económica personal?',32,false,false,
+ '{"min":1,"max":5,"min_label":"Sin impacto","max_label":"Muy alto impacto"}'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 4 — Tiempo y descanso (top-level)
+-- Cambio 4a: visible si 1.1='Sí' OR 1.6='Sí' OR 1.13='Sí' (eq_any)
-- ══════════════════════════════════════════════════════════════
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000034','30000000-0000-0000-0000-000000000001',
- '4.1','single_choice','¿Cómo describirías tu nivel de descanso habitual?',34,false,false,
+ '4.1','single_choice','¿Cómo describirías tu nivel de descanso habitual?',33,false,false,
'[{"value":"Descanso bien, me siento recuperado/a","label":"Descanso bien, me siento recuperado/a"},
{"value":"Descanso de forma irregular, pero lo manejo","label":"Descanso de forma irregular, pero lo manejo"},
{"value":"Me cuesta descansar, me siento cansado/a con frecuencia","label":"Me cuesta descansar, me siento cansado/a con frecuencia"},
- {"value":"Descanso muy poco, el agotamiento afecta mi día a día","label":"Descanso muy poco, el agotamiento afecta mi día a día"}]'::jsonb);
+ {"value":"Descanso muy poco, el agotamiento afecta mi día a día","label":"Descanso muy poco, el agotamiento afecta mi día a día"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000035','30000000-0000-0000-0000-000000000001',
- '4.2','multiple_choice','¿Qué aspectos de tu vida se ven más afectados por tu rol de cuidador/a?',35,false,false,
+ '4.2','multiple_choice','¿Qué aspectos de tu vida se ven más afectados por tu rol de cuidador/a?',34,false,false,
'[{"value":"Tiempo libre y ocio","label":"Tiempo libre y ocio"},
{"value":"Sueño y descanso","label":"Sueño y descanso"},
{"value":"Vida social y amistades","label":"Vida social y amistades"},
@@ -457,80 +460,91 @@ VALUES ('40000000-0000-0000-0000-000000000035','30000000-0000-0000-0000-00000000
{"value":"Salud física personal","label":"Salud física personal"},
{"value":"Salud emocional/mental","label":"Salud emocional/mental"},
{"value":"Desarrollo profesional","label":"Desarrollo profesional"},
- {"value":"Situación económica","label":"Situación económica"}]'::jsonb);
+ {"value":"Situación económica","label":"Situación económica"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000036','30000000-0000-0000-0000-000000000001',
- '4.3','rating','En una escala del 1 al 5, ¿cómo evaluás tu bienestar general en este momento?',36,false,false,
- '{"min":1,"max":5,"min_label":"Muy mal","max_label":"Muy bien"}'::jsonb);
+ '4.3','rating','En una escala del 1 al 5, ¿cómo evaluás tu bienestar general en este momento?',35,false,false,
+ '{"min":1,"max":5,"min_label":"Muy mal","max_label":"Muy bien"}'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 5 — Impacto en el trabajo (top-level)
+-- Cambio 4a: visible si 1.1='Sí' OR 1.6='Sí' OR 1.13='Sí' (eq_any)
-- ══════════════════════════════════════════════════════════════
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000037','30000000-0000-0000-0000-000000000001',
'5.1','single_choice',
'En los últimos 3 meses, ¿con qué frecuencia sentiste que el cuidado afectó tu concentración o rendimiento?',
- 37,false,false,
+ 36,false,false,
'[{"value":"Nunca o casi nunca","label":"Nunca o casi nunca"},
{"value":"Algunas veces al mes","label":"Algunas veces al mes"},
{"value":"Varias veces por semana","label":"Varias veces por semana"},
- {"value":"Casi todos los días","label":"Casi todos los días"}]'::jsonb);
+ {"value":"Casi todos los días","label":"Casi todos los días"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000038','30000000-0000-0000-0000-000000000001',
'5.2','single_choice',
- 'En el último año, ¿faltaste al trabajo por situaciones relacionadas al cuidado?',38,false,false,
+ 'En el último año, ¿faltaste al trabajo por situaciones relacionadas al cuidado?',37,false,false,
'[{"value":"Nunca","label":"Nunca"},
{"value":"Pocas veces (1–3 veces al año)","label":"Pocas veces (1–3 veces al año)"},
{"value":"Varias veces (4–10 veces al año)","label":"Varias veces (4–10 veces al año)"},
- {"value":"Con frecuencia (más de 10 veces al año)","label":"Con frecuencia (más de 10 veces al año)"}]'::jsonb);
+ {"value":"Con frecuencia (más de 10 veces al año)","label":"Con frecuencia (más de 10 veces al año)"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 6 — Conciencia organizacional (top-level)
+-- 6.1-6.3: Cambio 4a — visible si 1.1='Sí' OR 1.6='Sí' OR 1.13='Sí' (eq_any)
+-- 6.4-6.6: siempre visibles (destino del salto triple)
-- 6.2: multiple_choice max_select=3 (ranking en backlog)
-- 6.4/6.5: códigos corregidos (eran 6.2/6.3 duplicados en el HTML original)
+-- 6.6: nueva pregunta (Cambio 4b)
-- ══════════════════════════════════════════════════════════════
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000039','30000000-0000-0000-0000-000000000001',
'6.1','single_choice',
'¿Sentís que tu empresa conoce o comprende la realidad del cuidado y la discapacidad de su gente?',
- 39,false,false,
+ 38,false,false,
'[{"value":"Sí, me siento acompañado/a","label":"Sí, me siento acompañado/a"},
{"value":"Parcialmente, hay cierta comprensión","label":"Parcialmente, hay cierta comprensión"},
{"value":"No, es algo bastante invisible","label":"No, es algo bastante invisible"},
- {"value":"No, nunca se ha hablado del tema","label":"No, nunca se ha hablado del tema"}]'::jsonb);
+ {"value":"No, no se puede hablar del tema","label":"No, no se puede hablar del tema"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,max_select,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,max_select,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000040','30000000-0000-0000-0000-000000000001',
- '6.2','multiple_choice','¿Qué apoyos de la empresa te ayudarían más? Elegí hasta 3',40,false,false,3,
+ '6.2','multiple_choice','¿Qué apoyos de la empresa te ayudarían más? Elegí hasta 3',39,false,false,3,
'[{"value":"Flexibilidad horaria o trabajo remoto","label":"Flexibilidad horaria o trabajo remoto"},
{"value":"Apoyo económico o convenios/descuentos","label":"Apoyo económico o convenios/descuentos"},
{"value":"Acceso a terapias o salud","label":"Acceso a terapias o salud"},
{"value":"Contención/apoyo emocional o psicológico","label":"Contención/apoyo emocional o psicológico"},
{"value":"Capacitación y autocuidado","label":"Capacitación y autocuidado"},
{"value":"Redes de apoyo/grupos de pares","label":"Redes de apoyo/grupos de pares"},
- {"value":"Información sobre recursos y servicios disponibles","label":"Información sobre recursos y servicios disponibles"}]'::jsonb);
+ {"value":"Información sobre recursos y servicios disponibles","label":"Información sobre recursos y servicios disponibles"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
-INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options,conditional_rules)
VALUES ('40000000-0000-0000-0000-000000000041','30000000-0000-0000-0000-000000000001',
'6.3','single_choice',
'¿Estarías dispuesto/a a participar en un grupo de apoyo para cuidadores dentro de la empresa?',
- 41,false,false,
+ 40,false,false,
'[{"value":"Sí, definitivamente","label":"Sí, definitivamente"},
{"value":"Tal vez, si hay más personas en la misma situación","label":"Tal vez, si hay más personas en la misma situación"},
{"value":"No por ahora","label":"No por ahora"},
- {"value":"Prefiero mantenerlo en privado","label":"Prefiero mantenerlo en privado"}]'::jsonb);
+ {"value":"Prefiero mantenerlo en privado","label":"Prefiero mantenerlo en privado"}]'::jsonb,
+ '[{"if_question":["1.1","1.6","1.13"],"op":"eq_any","value":"Sí","action":"show"}]'::jsonb);
INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
VALUES ('40000000-0000-0000-0000-000000000042','30000000-0000-0000-0000-000000000001',
'6.4','single_choice',
'¿Conocés a algún compañero/a de trabajo que esté al cuidado de una persona con discapacidad o dependiente?',
- 42,false,false,
+ 41,false,false,
'[{"value":"Sí, conozco a varias personas","label":"Sí, conozco a varias personas"},
{"value":"Sí, conozco a una persona","label":"Sí, conozco a una persona"},
{"value":"No conozco a nadie","label":"No conozco a nadie"},
@@ -540,12 +554,21 @@ INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitiv
VALUES ('40000000-0000-0000-0000-000000000043','30000000-0000-0000-0000-000000000001',
'6.5','single_choice',
'¿Considerás que la empresa debería ofrecer apoyos a quienes cuidan a una persona dependiente?',
- 43,false,false,
+ 42,false,false,
'[{"value":"Sí, definitivamente","label":"Sí, definitivamente"},
{"value":"Tal vez, dependería del caso","label":"Tal vez, dependería del caso"},
{"value":"No lo considero necesario","label":"No lo considero necesario"},
{"value":"No lo sé","label":"No lo sé"}]'::jsonb);
+INSERT INTO public.questions (id,survey_id,code,type,prompt,position,is_sensitive,required,options)
+VALUES ('40000000-0000-0000-0000-000000000045','30000000-0000-0000-0000-000000000001',
+ '6.6','single_choice',
+ '¿Estarías dispuesto/a a ser voluntario/a en iniciativas y/o redes de apoyo a personas con discapacidad y/o cuidadores?',
+ 43,false,false,
+ '[{"value":"Sí","label":"Sí"},
+ {"value":"No","label":"No"},
+ {"value":"Tal vez","label":"Tal vez"}]'::jsonb);
+
-- ══════════════════════════════════════════════════════════════
-- SECCIÓN 7 — Para cerrar (top-level)