Estructura del método + fundación de datos Sprint 1 (pre-ejecución)

This commit is contained in:
markosbenitez
2026-06-02 07:21:49 -03:00
parent ef9e4bf998
commit e116e59ee9
11 changed files with 242 additions and 1 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@
# Supabase local — generado por supabase start
.supabase/
supabase/.temp/
# Node
node_modules/

View File

@@ -40,6 +40,20 @@ discapacidad) → la privacidad no es una feature, es una restricción de diseñ
- **v1 corre solo Modo A.** El campo existe pero la lógica dual no se construye todavía.
8. **La empresa nunca accede a dato individual crudo.** Solo recibe agregados/dashboards autorizados
por encima del umbral. (Coherente con Modo A + k-anonimato.)
9. **Manejo de API keys de Supabase — transversal y crítico.** La `anon` key es pública y **respeta
RLS**: es la única que puede tocar el frontend/cliente. La `service_role` key **bypassa RLS por
completo** y por lo tanto saltea k-anonimato, consentimiento y todo el modelo de ADR-001. Reglas:
- La `service_role` **NUNCA** va al frontend, al cliente, a un artefacto público ni a git. Solo
vive server-side (Edge Functions / backend), cargada desde variable de entorno.
- `.env` y `.env.*` siempre en `.gitignore`. Una `service_role` en el historial de git es un
incidente de seguridad aunque el repo sea privado → rotar la key de inmediato.
- Toda operación de cara al respondente o a la empresa usa `anon` key + RLS. Si algo "necesita"
`service_role` para funcionar en el cliente, está mal diseñado: revisar las policies, no exponer
la key.
10. **El flujo del respondente NUNCA se ata a un IdP / SSO.** Encuestas anónimas = sin login, sin
Keycloak, sin cookie de sesión identificable. El SSO (Keycloak) es solo para staff interno. RLS
lee claims por nombre (`org_id`, `role`) para que el emisor del token sea intercambiable; nunca
basar RLS en `user_metadata` (modificable por el usuario). (Ver ADR-004.)
---
@@ -114,4 +128,4 @@ importar el tamaño técnico.
- **Nombre/marca del producto** (Nivel 3).
- **Residencia de datos**: Supabase Cloud vs self-host (ADR-002).
- **`CONVENTIONS.md`**: formato de cifras, idioma de la UI, tono de los mensajes. Apenas una
convención se pregunte 2 veces, documentarla acá (anti-patrón AP.4).
convención se pregunte 2 veces, documentarla acá (anti-patrón AP.4).

105
SEED_SPEC.md Normal file
View File

@@ -0,0 +1,105 @@
# SEED_SPEC.md — Contrato del instrumento "Mapeo de cuidadores"
> Inventario completo y autoritativo de la encuesta, extraído del mockup real
> (`formulario_mapeo.html`). Reemplaza el seed inventado de la Etapa 1 (que tenía 9 de 34 preguntas y
> el supuesto incorrecto C1→G4). Claude Code reescribe `seed.sql` a partir de ESTE contrato.
## Convenciones (decisiones cerradas)
- **Mapeo de tipos al esquema:** radio→`single_choice`, checkbox→`multiple_choice`, escala 1-5→
`rating`, textarea→`text_long`.
- **`conditional_rules` viven en la pregunta DEPENDIENTE** (cada pregunta condicional declara su
propia condición de visibilidad: `{if_question, op:'eq', value, action:'show'}`). El motor, al
decidir si renderiza X, mira las reglas de X. (Resuelve la ambigüedad que el agente había dejado.)
- **B1/C1/D1 son `single_choice` de 3 opciones** (Sí / No / Prefiero no decir), **NO `yes_no`**.
Solo el valor **"Sí"** dispara el bloque condicional; "No" y "Prefiero no decir" lo ocultan.
- **`is_sensitive = true`** en toda pregunta que elicita salud/discapacidad/diagnóstico (marcadas
abajo). La encuesta entera es `data_sensitivity = 'sensible'`.
## Estructura condicional (3 bloques, el resto siempre visible)
- **cond-B** — visible si `B1 = 'Sí'` → contiene B2, B3, B4, B5
- **cond-C** — visible si `C1 = 'Sí'` → contiene C2, C3, C4, C5
- **cond-D** — visible si `D1 = 'Sí'` → contiene D2, D3, D4, D5
- Secciones A (datos generales), E (rol cuidador), F (económico), G (bienestar), H (necesidades):
**top-level, siempre visibles.**
---
## Sección 0 — Datos Generales (top-level, cuasi-identificadores)
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| A1 | single_choice | ¿En qué área o gerencia trabajás? | Operaciones/Producción · Comercial/Ventas · Administración/Finanzas · RRHH/Cultura · Tecnología/Sistemas · Logística/Distribución · Otra área |
| A2 | single_choice | ¿Cuántos años tenés? | Menos de 25 · 25-34 · 35-44 · 45-54 · 55+ · Prefiero no decir |
| A3 | single_choice | ¿Con qué género te identificás? | Femenino · Masculino · No binario/Otro · Prefiero no decir |
| A4 | single_choice | ¿Cuál es tu modalidad de trabajo actual? | Presencial · Remoto · Híbrido |
## Sección 1 — ¿Vivís con discapacidad? *(sensible)*
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|---|---|---|---|---|---|
| B1 | single_choice | trigger | ¿Vos mismo/a tenés alguna discapacidad o condición de salud…? | Sí · No · Prefiero no decir | ✅ |
| B2 | multiple_choice | B1=Sí | ¿Qué tipo de discapacidad tenés? (más de una) | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial · Condición crónica · Otra | ✅ |
| B3 | single_choice | B1=Sí | ¿Contás con certificado oficial de discapacidad? | Sí · En trámite · No | ✅ |
| B4 | single_choice | B1=Sí | ¿Tu discapacidad afecta tu desempeño o comodidad en el trabajo? | Sí, significativamente · Sí, aspectos puntuales · No, bien gestionada · Prefiero no decir | ✅ |
| B5 | single_choice | B1=Sí | ¿La empresa conoce tu situación? | Sí, con apoyo · Sí, sin apoyo · No, privado | ✅ |
## Sección 2 — Familiares con Discapacidad *(sensible)*
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|---|---|---|---|---|---|
| C1 | single_choice | trigger | ¿Tenés algún familiar cercano que viva con discapacidad? | Sí · No · Prefiero no decir | ✅ |
| C2 | multiple_choice | C1=Sí | ¿Cuál es el vínculo? (más de una) | Hijo/a · Cónyuge/pareja · Padre/madre · Hermano/a · Otro familiar | ✅ |
| C3 | multiple_choice | C1=Sí | ¿Qué tipo de discapacidad tiene? (más de una) | Física/motriz · Visual · Auditiva · Intelectual · TEA · Psicosocial · Condición crónica | ✅ |
| C4 | single_choice | C1=Sí | ¿Esta persona requiere apoyo o cuidado cotidiano? | Alta dependencia · Apoyo parcial · Autónomo/a | ✅ |
| C5 | single_choice | C1=Sí | ¿Quién es el responsable principal del cuidado? | Yo principal · Compartido · Cuidador pagado · Institución | |
## Sección 3 — Adultos Mayores a Cargo *(sensible)*
| Code | Tipo | Cond. | Pregunta | Opciones | is_sensitive |
|---|---|---|---|---|---|
| D1 | single_choice | trigger | ¿Tenés algún adulto mayor (60+) a tu cuidado? | Sí · No · Prefiero no decir | |
| D2 | single_choice | D1=Sí | ¿Cuántos adultos mayores están bajo tu cuidado? | 1 · 2 · 3+ | |
| D3 | single_choice | D1=Sí | ¿Qué nivel de dependencia tiene/n? | Supervisión · Dependencia leve · Dependencia moderada · Dependencia severa | ✅ |
| D4 | multiple_choice | D1=Sí | ¿Tiene condición diagnosticada? (más de una) | Demencia/Alzheimer · ACV/Parálisis · Enf. crónica · Movilidad reducida · Deterioro cognitivo · Ninguna específica | ✅ |
| D5 | single_choice | D1=Sí | ¿La persona vive con vos o en otra vivienda? | Vive conmigo · Otra vivienda · Institución | |
## Sección 4 — Tu Rol como Cuidador/a (top-level)
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| E1 | single_choice | ¿Cuántas horas por día dedicás, en promedio, al cuidado directo? | Menos de 1h · 1-3h · 3-6h · Más de 6h · Variable |
| E2 | multiple_choice | ¿En qué momentos realizás principalmente el cuidado? (más de una) | Antes del trabajo · Durante horario laboral · Al llegar · Durante la noche · Fines de semana · Continuo |
| E3 | single_choice | ¿Contás con apoyo de otras personas para el cuidado? | Apoyo familiar suficiente · Apoyo insuficiente · Único responsable · Apoyo contratado |
| E4 | single_choice | ¿Tuviste que faltar al trabajo por el cuidado en el último año? | Nunca · Pocas veces (1-3) · Varias veces (4-10) · Con frecuencia (10+) |
## Sección 5 — Impacto Económico (top-level)
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| F1 | single_choice | ¿Cuánto estimás que gastás mensualmente en el cuidado? | Sin gastos significativos · Menos de 500k · 500k-1.5M · 1.5M-3M · 3M-6M · Más de 6M · Prefiero no decir |
| F2 | multiple_choice | ¿Qué servicios o apoyos utilizás actualmente? (más de una) | Terapias especializadas · Cuidador particular · Centro de día · Transporte adaptado · Medicación alto costo · Tecnología asistiva · Apoyo ONG · Sin servicios formales |
| F3 | rating | ¿Los gastos de cuidado impactan en tu situación económica personal? | Escala 1 (Sin impacto) → 5 (Muy alto) |
## Sección 6 — Bienestar y Descanso (top-level)
| Code | Tipo | Pregunta | Opciones |
|---|---|---|---|
| G1 | single_choice | ¿Cómo describirías tu nivel de descanso habitual? | Descanso bien · Descanso irregular · Me cuesta descansar · Muy poco descanso |
| G2 | single_choice | En los últimos 3 meses, ¿con qué frecuencia el cuidado afectó tu concentración o rendimiento laboral? | Nunca/casi nunca · Algunas veces al mes · Varias veces por semana · Casi todos los días |
| G3 | multiple_choice | ¿Qué aspectos de tu vida se ven más afectados? (más de una) | Tiempo libre · Sueño y descanso · Vida social · Vida en pareja/familia · Salud física · Salud emocional · Desarrollo profesional · Situación económica |
| G4 | rating | ¿Cómo evaluás tu bienestar general en este momento? | Escala 1 (Muy mal) → 5 (Muy bien) |
| G5 | single_choice | ¿Sentís que tu empresa comprende la realidad de ser cuidador/a? | Sí, acompañado/a · Parcialmente · No, invisible · Nunca lo supo |
## Sección 7 — Necesidades (top-level)
| Code | Tipo | Pregunta | Opciones / nota |
|---|---|---|---|
| H1 | multiple_choice (**max_select=3**) | ¿Qué tipo de apoyo de tu empresa te ayudaría más? (hasta 3) | Flexibilidad horaria/remoto · Info recursos y servicios · Apoyo psicológico · Espacio de escucha · Convenios descuentos · Grupos de pares · Capacitación autocuidado · Nada, estoy bien |
| H2 | single_choice | ¿Estarías dispuesto/a a participar en un grupo de apoyo para cuidadores? | Sí, definitivamente · Tal vez · No por ahora · Prefiero privacidad |
| H3 | text_long | ¿Querés compartir algo más sobre tu experiencia? (opcional) | Texto libre, opcional. **No entra al store analítico crudo** (regla 4 CLAUDE.md). |
---
## Mapeo a cuasi-identificadores (`responses.qi_*`)
- `qi_age_bucket`**A2** (ya viene en bucket — bien).
- `qi_sector`**A1** (área).
- `qi_zone`**no aplica en este instrumento** (no se pregunta zona geográfica; es intra-empresa). Va NULL.
## ⚠️ Hallazgo para el director (decisión de modelo de datos, Nivel 3 — NO ejecutar)
El dashboard original quería filtrar por **género (A3)** y **modalidad (A4)**, pero `agg_segment`
solo tiene en su whitelist `qi_age_bucket / qi_sector / qi_zone`. Para segmentar/filtrar por género o
modalidad en el dashboard (Sprint 3), hay que decidir: ¿se agregan columnas `qi_gender` / `qi_modality`
(y a la whitelist de `agg_segment`), o se resuelven por otra vía? Es decisión de esquema → consultar
antes de Sprint 3, no resolver en el seed.

View File

@@ -0,0 +1,56 @@
# ADR-004 — Capa de identidad: respondente anónimo vs staff con SSO
- **Estado:** Aceptado (frontera) · Diferido (integración Keycloak)
- **Fecha:** (completar con fecha de commit)
---
## Contexto
El equipo construye una familia de productos Inquality que se autentican contra un **Keycloak**
propio (proyecto en curso de otro equipo). Surge la pregunta de si esa capa de identidad aplica a
este producto. El producto tiene dos audiencias con necesidades opuestas (respondente anónimo vs
staff interno), por lo que la respuesta no es única.
## Decisión
### Frontera dura (no negociable)
- **El flujo del respondente NUNCA se ata a un IdP.** Las encuestas de v1 son anónimas: el
respondente no se loguea, ni con Keycloak ni con nada. Atarlo a SSO destruiría el anonimato (la
premisa de v1) y agregaría fricción que baja la tasa de respuesta. Lado respondente = `anon` key +
RLS + sin identidad. Para siempre, mientras la encuesta sea anónima.
- **El staff interno SÍ puede usar SSO.** El acceso del equipo a dashboards/administración —a través
de la familia de productos— es donde Keycloak aporta: SSO único, gestión de usuarios centralizada,
offboarding en un solo lugar (control de seguridad real para dato sensible), una sola fuente de
verdad de identidad. Y Keycloak es self-hosteable → alinea con la dirección Dokploy/soberanía.
### Integración técnica (cuando Keycloak madure)
Supabase soporta *third-party auth*: confía en JWT de un emisor externo si son firmados
asimétricamente (RS256/ES256), expuestos vía OIDC Discovery URL, con `kid` en el header. Keycloak
cumple. Dos caminos válidos:
1. Registrar Keycloak como emisor OIDC confiable (más libre en self-host).
2. Patrón universal: verificar el JWT de Keycloak en el edge e intercambiarlo por un JWT firmado por
Supabase. Funciona con cualquier emisor.
RLS lee los claims del token externo con `auth.jwt()` igual que con tokens de Supabase Auth.
### No bloquear v1
Keycloak es un proyecto en curso de otro equipo. **v1 no depende de él.** v1 casi no necesita auth
(encuestas anónimas + dashboard que presenta el staff). Estrategia:
1. Diseñar las policies de RLS para que lean **claims por nombre** (`org_id`, `role`), no helpers
exclusivos de Supabase Auth, para que el emisor sea intercambiable.
2. v1 corre con auth nativa de Supabase para los pocos usuarios internos (o sin login interno si
alcanza).
3. Cuando Keycloak esté listo: emite esos claims, se registra como emisor confiable, y RLS sigue
andando sin reescribir.
## Consecuencias
- (+) Identidad portable: el emisor del token se cambia por config, no por reescritura.
- (+) Encaja con la familia de productos y con la soberanía (Keycloak self-host).
- () Disciplina: nunca basar RLS en `user_metadata` (modificable por el usuario); usar claims
verificados del servidor (Keycloak los da).
- () Cuando se integre, alinear los claims de Keycloak (`org_id`, `role`) con lo que esperan las
policies — tarea de config, no de esquema.
## Relación con otros ADR
Vive sobre el patrón de backend de ADR-002 (Supabase como backend). La regla de keys de `CLAUDE.md`
(rule 9) sigue aplicando: `service_role` nunca al cliente, pase lo que pase con el IdP.

View File

@@ -0,0 +1,65 @@
# ETAPA 1B — Corrección del seed + ejecución/validación real
> Prompt versionado (Patrón B.1). Cierra los huecos que la auditoría encontró en la Etapa 1: seed
> inventado/incompleto y nada ejecutado. Claude Code lo ejecuta tras leer `CLAUDE.md`, `DATA_MODEL.md`,
> `SEED_SPEC.md` y este prompt.
## 1. Objetivo
Reescribir `supabase/seed.sql` para que refleje el instrumento real (34 preguntas) según
`SEED_SPEC.md`, y ejecutar la base contra el proyecto **Supabase Cloud** ya vinculado (sin Docker)
para verificar de verdad lo que la Etapa 1 dejó sin correr.
## 2. Contexto previo
La Etapa 1 creó esquema, RLS, triggers, `agg_segment` y un seed **incorrecto** (9 de 34 preguntas;
B1/C1/D1 tipadas como `yes_no`; supuesto inválido C1→G4; D1 sin reglas; falta Sección A). El esquema
NO se ejecutó (no había Docker). Ahora hay un proyecto Supabase Cloud vinculado (`supabase link`).
## 3. Decisiones ya tomadas (no consultar de nuevo)
- Las 34 preguntas, tipos, opciones y condicionales están en `SEED_SPEC.md` — es el contrato.
- `conditional_rules` van en la pregunta **dependiente** (`{if_question, op:'eq', value:'Sí', action:'show'}`).
- B1/C1/D1 = `single_choice` de 3 opciones; solo "Sí" dispara el bloque.
- `is_sensitive=true` en las preguntas marcadas en `SEED_SPEC.md`. Encuesta = `sensible`, anónima, Modo A.
- `qi_age_bucket`←A2, `qi_sector`←A1, `qi_zone`=NULL (no se pregunta zona).
- `respondent_id` sigue NULL (encuesta anónima).
## 4. Tareas
1. Reescribir `supabase/seed.sql`: 1 organización demo, 1 `consent_version` v1 (scopes
`["operativo","macro_n1"]`), 1 `survey` (anónima/sensible/confidencial/Modo A/k=5), y las **34
preguntas** con sus tipos, opciones, `max_select` (H1=3), `is_sensitive`, y `conditional_rules`
en las dependientes (B2-B5, C2-C5, D2-D5).
2. Aplicar a Cloud: `supabase db push` y luego cargar el seed contra el proyecto vinculado.
3. Ejecutar las validaciones de la sección 6 contra la base real.
## 5. Definition of Done
- [ ] `seed.sql` con 34 preguntas que coinciden 1:1 con `SEED_SPEC.md`.
- [ ] `supabase db push` aplica limpio en Cloud (reportar si algún GRANT/policy falla — la auditoría sospechaba de 003/006).
- [ ] Seed cargado y consultable en el proyecto Cloud.
- [ ] Validaciones de la sección 6 verdes.
- [ ] `git diff --name-only HEAD` solo con `supabase/seed.sql` (+ tests si los tocás).
## 6. Validaciones automatizadas mandatorias (contra la base Cloud)
- Conteo: `SELECT count(*) FROM questions` = **34**; condicionales = **12** (B2-B5, C2-C5, D2-D5),
cada una con `if_question` correcto y `value='Sí'`.
- Tipos: B1/C1/D1 son `single_choice` (no `yes_no`); H1 `multiple_choice` con `max_select=3`;
F3/G4 `rating`; H3 `text_long`.
- `is_sensitive=true` en exactamente las preguntas marcadas en `SEED_SPEC.md`.
- RLS: como rol de organización, `SELECT * FROM responses` y `FROM answers`**0 filas**.
- Constraint: `INSERT` en `responses` con `respondent_id` no nulo en encuesta anónima → ERROR.
- k-anonimato: `agg_segment` sobre un segmento sembrado con 3 respuestas → "n insuficiente"; con ≥5 → valor.
- Inmutabilidad: `UPDATE`/`DELETE` sobre `consent_records` → ERROR.
- Si CUALQUIERA falla: **NO declarar lista la etapa.** Reportar el fallo y esperar instrucciones.
## 7. Qué reportar
- **Verificado:** salida concreta de cada validación (conteos, resultado de `agg_segment` n=3 y n=5,
comportamiento de RLS, errores esperados que efectivamente saltaron).
- **Asumido:** cualquier supuesto (ej. textos exactos de opciones que hayas normalizado).
- **Hallazgos no esperados** y **Decisiones por iniciativa** (clasificadas por nivel).
- **`git diff --name-only HEAD`** pegado.
## 8. Qué NO hacer
- NO tocar el esquema (migraciones 001-006) salvo que una validación revele un bug — y si lo revela,
**reportar antes de corregir** (cambiar esquema es Nivel 3 si toca RLS/privacidad).
- NO agregar columnas `qi_gender`/`qi_modality` (hay un hallazgo abierto en `SEED_SPEC.md` para el
director — es decisión Nivel 3, no la tomes acá).
- NO exponer texto libre (H3) en ninguna vista/función analítica.
- NO usar `respondent_id`. NO usar `service_role` en nada de cara al cliente.