Estructura del método + fundación de datos Sprint 1 (pre-ejecución)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
|
||||
# Supabase local — generado por supabase start
|
||||
.supabase/
|
||||
supabase/.temp/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
16
CLAUDE.md
16
CLAUDE.md
@@ -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
105
SEED_SPEC.md
Normal 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.
|
||||
56
docs/adr/ADR-004-capa-de-identidad.md
Normal file
56
docs/adr/ADR-004-capa-de-identidad.md
Normal 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.
|
||||
65
sprints/sprint-1/ETAPA_1B_PROMPT.md
Normal file
65
sprints/sprint-1/ETAPA_1B_PROMPT.md
Normal 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.
|
||||
Reference in New Issue
Block a user