Los triggers 1.1, 1.6, 1.13 muestran solo Sí · No (sin "Prefiero no decir")

Al responder "No" en las tres, el formulario salta a 6.4 (no muestra secciones 2-5 ni 6.1-6.3)
La pregunta 6.6 aparece antes del cierre
Las preguntas multiple_choice muestran el hint "Podés marcar más de una opción"
This commit is contained in:
markosbenitez
2026-06-10 15:49:05 -03:00
parent bf963e8894
commit 157e108322
15 changed files with 1096 additions and 161 deletions

89
OBSERVATIONS.md Normal file
View File

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

View File

@@ -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.0001.500.000 · Gs. 1.500.0003.000.000 · Gs. 3.000.0006.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.0001.500.000 · Gs. 1.500.0003.000.000 · Gs. 3.000.0006.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.0018.000.000 · Gs. 8.000.00112.000.000 · Gs. 12.000.00115.000.000 · Gs. 15.000.00118.000.000 · Gs. 18.000.00125.000.000 · Gs. 25.000.00150.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.0018.000.000 · Gs. 8.000.00112.000.000 · Gs. 12.000.00115.000.000 · Gs. 15.000.00118.000.000 · Gs. 18.000.00125.000.000 · Gs. 25.000.00150.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 (13 veces al año) · Varias veces (410 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 |

View File

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

View File

@@ -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 (
<div className="admin-page" role="main">
<h1 className="admin-title">Control de respuestas</h1>
<p className="admin-error">No fue posible conectarse a la base de datos.</p>
<pre className="admin-error-detail">{error.message}</pre>
<pre className="admin-error-detail">{questionsError.message}</pre>
</div>
)
}
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 (
<div className="admin-page" role="main">
<h1 className="admin-title">Control de respuestas</h1>
<p className="admin-error">No fue posible conectarse a la base de datos.</p>
<pre className="admin-error-detail">{responsesError.message}</pre>
</div>
)
}
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 (
<div className="admin-page" role="main">
@@ -75,7 +179,7 @@ export default async function AdminResponsesPage() {
<div>
<h1 className="admin-title">Control de respuestas</h1>
<p className="admin-subtitle">
{totalCount ?? 0} respuestas en total · últimas {rows.length} ·
{totalSubmissions ?? 0} submissions · {totalAnswers ?? 0} respuestas individuales ·
actualizado {formatDate(new Date().toISOString())}
</p>
</div>
@@ -88,29 +192,35 @@ export default async function AdminResponsesPage() {
<tr>
<th>#</th>
<th>Fecha</th>
<th>Encuesta</th>
<th>Departamento</th>
<th>Código</th>
<th>Pregunta</th>
<th>Tipo</th>
<th>Valor</th>
<th className="admin-col-optional">Departamento</th>
<th className="admin-col-optional">Sector</th>
<th className="admin-col-optional">Rango etario</th>
<th>Respuestas</th>
<th className="admin-col-optional">IP hash</th>
</tr>
</thead>
<tbody>
{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 (
<tr key={row.id}>
<td>{i + 1}</td>
<td>{formatDate(row.submitted_at)}</td>
<td>{survey?.title ?? '—'}</td>
<td>{dash(row.qi_zone)}</td>
<td className="admin-col-optional">{dash(row.qi_sector)}</td>
<td className="admin-col-optional">{dash(row.qi_age_bucket)}</td>
<td>{answerCount}</td>
<td className="admin-col-optional admin-iphash">
{row.ip_hash ? `${row.ip_hash.slice(0, 8)}...` : '—'}
<tr key={row.key} className={trClass}>
<td>{row.submissionNum}</td>
<td>{row.isFirstInGroup ? formatDate(row.submitted_at) : ''}</td>
<td>{row.code}</td>
<td className="admin-prompt">{truncate(row.prompt)}</td>
<td><span className="admin-type-badge">{row.type}</span></td>
<td>
{rendered === null ? <span className="admin-nodata">sin dato</span> : rendered}
</td>
<td className="admin-col-optional">
{row.isFirstInGroup ? (row.qi_zone ?? '—') : ''}
</td>
<td className="admin-col-optional">
{row.isFirstInGroup ? (row.qi_sector ?? '—') : ''}
</td>
</tr>
)

View File

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

View File

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

View File

@@ -40,11 +40,13 @@ export function CheckboxGroup({
{required && <span className="required-mark" aria-hidden="true"> *</span>}
{required && <span className="sr-only"> (obligatorio)</span>}
</legend>
{maxSelect !== null && (
{maxSelect !== null ? (
<p className="max-select-hint" aria-live="polite">
Elegí hasta {maxSelect} opciones
{value.length > 0 && ` · ${value.length}/${maxSelect} seleccionadas`}
</p>
) : (
<p className="max-select-hint">Podés marcar más de una opción</p>
)}
<div className="options-list" role="list">
{options.map((opt) => {

View File

@@ -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<string> {
const triggers = new Set<string>()
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

View File

@@ -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'
}

View File

@@ -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=<JWT anon>
- SUPABASE_SERVICE_ROLE_KEY=<JWT service_role> (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)

View File

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

View File

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

View File

@@ -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.0001.500.000 · Gs. 1.500.0003.000.000
· Gs. 3.000.0006.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.

View File

@@ -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" (`<a href="/admin/responses">`).
### 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.

View File

@@ -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.21.5 (4 dependientes)
-- cond-familiar: 1.6='Sí' → 1.71.12 (6 dependientes)
-- cond-adulto-mayor: 1.13='Sí' → 1.141.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.21.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.71.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.141.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.0001.500.000","label":"Gs. 500.0001.500.000"},
{"value":"Gs. 1.500.0003.000.000","label":"Gs. 1.500.0003.000.000"},
{"value":"Gs. 3.000.0006.000.000","label":"Gs. 3.000.0006.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.0018.000.000","label":"Gs. 5.000.0018.000.000"},
{"value":"Gs. 8.000.00112.000.000","label":"Gs. 8.000.00112.000.000"},
{"value":"Gs. 12.000.00115.000.000","label":"Gs. 12.000.00115.000.000"},
{"value":"Gs. 15.000.00118.000.000","label":"Gs. 15.000.00118.000.000"},
{"value":"Gs. 18.000.00125.000.000","label":"Gs. 18.000.00125.000.000"},
{"value":"Gs. 25.000.00150.000.000","label":"Gs. 25.000.00150.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 (13 veces al año)","label":"Pocas veces (13 veces al año)"},
{"value":"Varias veces (410 veces al año)","label":"Varias veces (410 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)