From e116e59ee996a2a186a49cd8f3f023d2d3a644e1 Mon Sep 17 00:00:00 2001 From: markosbenitez Date: Tue, 2 Jun 2026 07:21:49 -0300 Subject: [PATCH] =?UTF-8?q?Estructura=20del=20m=C3=A9todo=20+=20fundaci?= =?UTF-8?q?=C3=B3n=20de=20datos=20Sprint=201=20(pre-ejecuci=C3=B3n)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- settings.json => .claude/settings.json | 0 .gitignore | 1 + CLAUDE.md | 16 ++- SEED_SPEC.md | 105 ++++++++++++++++++ .../ADR-001-privacidad-y-gobierno-de-datos.md | 0 .../adr/ADR-002-stack-tecnologico.md | 0 .../adr/ADR-003-consentimiento-versionado.md | 0 docs/adr/ADR-004-capa-de-identidad.md | 56 ++++++++++ BRIEF.md => sprints/sprint-1/BRIEF.md | 0 sprints/sprint-1/ETAPA_1B_PROMPT.md | 65 +++++++++++ .../sprint-1/ETAPA_1_PROMPT.md | 0 11 files changed, 242 insertions(+), 1 deletion(-) rename settings.json => .claude/settings.json (100%) create mode 100644 SEED_SPEC.md rename ADR-001-privacidad-y-gobierno-de-datos.md => docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md (100%) rename ADR-002-stack-tecnologico.md => docs/adr/ADR-002-stack-tecnologico.md (100%) rename ADR-003-consentimiento-versionado.md => docs/adr/ADR-003-consentimiento-versionado.md (100%) create mode 100644 docs/adr/ADR-004-capa-de-identidad.md rename BRIEF.md => sprints/sprint-1/BRIEF.md (100%) create mode 100644 sprints/sprint-1/ETAPA_1B_PROMPT.md rename ETAPA_1_PROMPT.md => sprints/sprint-1/ETAPA_1_PROMPT.md (100%) diff --git a/settings.json b/.claude/settings.json similarity index 100% rename from settings.json rename to .claude/settings.json diff --git a/.gitignore b/.gitignore index e816064..9ac8057 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ # Supabase local — generado por supabase start .supabase/ +supabase/.temp/ # Node node_modules/ diff --git a/CLAUDE.md b/CLAUDE.md index e220fa0..a85d5d3 100644 --- a/CLAUDE.md +++ b/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). \ No newline at end of file diff --git a/SEED_SPEC.md b/SEED_SPEC.md new file mode 100644 index 0000000..4084040 --- /dev/null +++ b/SEED_SPEC.md @@ -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. \ No newline at end of file diff --git a/ADR-001-privacidad-y-gobierno-de-datos.md b/docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md similarity index 100% rename from ADR-001-privacidad-y-gobierno-de-datos.md rename to docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md diff --git a/ADR-002-stack-tecnologico.md b/docs/adr/ADR-002-stack-tecnologico.md similarity index 100% rename from ADR-002-stack-tecnologico.md rename to docs/adr/ADR-002-stack-tecnologico.md diff --git a/ADR-003-consentimiento-versionado.md b/docs/adr/ADR-003-consentimiento-versionado.md similarity index 100% rename from ADR-003-consentimiento-versionado.md rename to docs/adr/ADR-003-consentimiento-versionado.md diff --git a/docs/adr/ADR-004-capa-de-identidad.md b/docs/adr/ADR-004-capa-de-identidad.md new file mode 100644 index 0000000..c19542f --- /dev/null +++ b/docs/adr/ADR-004-capa-de-identidad.md @@ -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. \ No newline at end of file diff --git a/BRIEF.md b/sprints/sprint-1/BRIEF.md similarity index 100% rename from BRIEF.md rename to sprints/sprint-1/BRIEF.md diff --git a/sprints/sprint-1/ETAPA_1B_PROMPT.md b/sprints/sprint-1/ETAPA_1B_PROMPT.md new file mode 100644 index 0000000..47d8e42 --- /dev/null +++ b/sprints/sprint-1/ETAPA_1B_PROMPT.md @@ -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. \ No newline at end of file diff --git a/ETAPA_1_PROMPT.md b/sprints/sprint-1/ETAPA_1_PROMPT.md similarity index 100% rename from ETAPA_1_PROMPT.md rename to sprints/sprint-1/ETAPA_1_PROMPT.md