Estructura del método + fundación de datos Sprint 1 (pre-ejecución)
This commit is contained in:
110
docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md
Normal file
110
docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# ADR-001 — Modelo de privacidad y gobierno de datos
|
||||
|
||||
- **Estado:** Aceptado
|
||||
- **Fecha:** (completar con fecha de commit)
|
||||
- **Decisores:** Director del proyecto + asistente de dirección técnica
|
||||
- **Contexto legal:** Paraguay, Ley N° 7593/2025 de Protección de Datos Personales
|
||||
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
El producto corre la misma encuesta en N empresas con **dos propósitos en tensión**: operativo
|
||||
(dashboard por empresa para decidir acciones) y macro (estudio país de la "economía de la
|
||||
discapacidad"). Se tratan **datos sensibles** (salud/discapacidad). Resolver mal esta tensión
|
||||
significa o filtrar datos confidenciales entre empresas, o perder el análisis macro que es el
|
||||
diferencial.
|
||||
|
||||
**Marco legal aplicable:** la Ley N° 7593/2025 fue promulgada el 27/11/2025, está alineada con el
|
||||
RGPD europeo, crea la Agencia Nacional de Protección de Datos Personales (ANPDP) con régimen
|
||||
sancionatorio, y tiene vacatio legis de 24 meses (exigibilidad plena ~nov 2027). Reconoce categoría
|
||||
especial para datos sensibles (incluye salud) y derechos de acceso, rectificación, oposición,
|
||||
supresión, portabilidad y revocación del consentimiento. *No es asesoría legal; validar con
|
||||
especialista antes de producción.*
|
||||
|
||||
## Decisión
|
||||
|
||||
Adoptamos un modelo de **privacy by design** con los siguientes componentes cerrados:
|
||||
|
||||
### 1. Anonimato como propiedad por-encuesta
|
||||
El anonimato no es global de la plataforma: cada encuesta lo declara en su configuración. El wizard
|
||||
de creación lo togglea y eso propaga a cómo se guarda cada respuesta.
|
||||
|
||||
### 2. Dos ejes de clasificación, ortogonales
|
||||
- **Sensibilidad legal** (`comun` | `sensible` | `anonimizado`) → alimenta el **consentimiento**.
|
||||
- **Seguridad** (`publico` | `interno` | `confidencial`) → alimenta **RLS y cifrado**.
|
||||
|
||||
"Confidencial" es seguridad, NO una categoría legal. Una encuesta puede ser confidencial en storage
|
||||
y no-sensible legalmente, o al revés.
|
||||
|
||||
### 3. La herramienta constriñe, no solo permite
|
||||
La clasificación no es pura auto-declaración del creador. Ciertos tipos de pregunta
|
||||
(salud/discapacidad) **se auto-marcan sensibles** y elevan la encuesta, sin importar lo que elija el
|
||||
creador. El wizard guía y bloquea por defecto hacia la clasificación correcta. (Operacionaliza los
|
||||
principios de minimización y responsabilidad proactiva de la ley.)
|
||||
|
||||
### 4. El titular del consentimiento es el respondente, no la empresa
|
||||
La empresa autoriza el despliegue; el individuo consiente el tratamiento de su propio dato sensible.
|
||||
Ante divergencia entre un documento firmado por la empresa y la elección granular del titular en la
|
||||
app, **gana siempre la elección del titular**.
|
||||
|
||||
### 5. Consentimiento granular, por capas y versionado
|
||||
Checkboxes separados por propósito (operativo / macro N1 / macro N2...), no un único "acepto todo".
|
||||
Cada respuesta guarda un registro inmutable con timestamp contra la versión vigente del texto. (Ver
|
||||
ADR-003.)
|
||||
|
||||
### 6. Rol de responsable parametrizable (`controller_role`), con cascada
|
||||
- **Modo A** (nosotros = responsable): macro a todos los niveles consentidos por el titular.
|
||||
- **Modo B** (empresa = responsable, nosotros = encargado): macro **solo** vía Nivel 1 anonimizado
|
||||
irreversible. Usar dato de Modo B para Nivel 2/3 sería ejercer de responsable encubierto → ilícito.
|
||||
|
||||
> **Regla limpia:** Modo A → macro a todos los niveles consentidos. Modo B → macro solo vía Nivel 1
|
||||
> anonimizado. El wizard debe **mostrar la consecuencia** al elegir el modo, no ofrecerlo como
|
||||
> dropdown plano.
|
||||
|
||||
### 7. Anónimo a nivel respondente, conservando atribución de empresa (las 4 de v1)
|
||||
- **Sí** se conserva `company_id` + cuasi-identificadores (edad en bucket, sector, zona) → habilita
|
||||
Nivel 2 (empresa A vs el resto) y comparación por segmento.
|
||||
- **No** se recolecta `respondent_id`. El campo queda **reservado** en el esquema para encuestas
|
||||
futuras, pero para estos 4 estudios el dato queda permanentemente en Nivel 1/2.
|
||||
|
||||
### 8. Distinción anónimo vs pseudónimo (textual, para no re-confundir)
|
||||
La **comparación por segmento** ("25 / sector X / zona A" vs "zona B") usa los cuasi-identificadores
|
||||
de cada fila y **no requiere** `respondent_id`. La clave pseudónima solo sirve para vincular varias
|
||||
respuestas a la misma persona desconocida (longitudinal, re-contacto, dedup avanzado) — nada de eso
|
||||
es caso de v1. La dedup simple se hace con `ip_hash`.
|
||||
|
||||
### 9. Umbral de k-anonimato mandatorio en visualización
|
||||
Anónimo en storage **no** es a prueba de re-identificación: un n=1 por cruce de cuasi-identificadores
|
||||
es identificable. Toda visualización compartida (incluido el dashboard de la empresa) suprime celdas
|
||||
con n < umbral (default 5). La comparación individuo-vs-individuo está prohibida en dashboards
|
||||
compartidos; la inspección fila-por-fila es solo análisis interno (Modo A).
|
||||
|
||||
### 10. Texto libre fuera del store analítico crudo
|
||||
Las preguntas abiertas no se exponen crudas a reportes (vector de re-identificación). Se categorizan
|
||||
antes (a futuro, vía LLM — está en BACKLOG).
|
||||
|
||||
## Alcance de esta etapa (v1)
|
||||
Solo **Modo A**, solo encuestas **anónimas**, **sin Nivel 3 atribuible**. El esquema soporta más
|
||||
(controller_role, respondent_id) pero v1 no lo opera. Principio: el esquema guarda la opción, estos
|
||||
datos puntuales no la usan.
|
||||
|
||||
## Alternativas consideradas
|
||||
- **Macro 100% anonimizado siempre (solo Nivel 1):** más simple, pero mata el benchmark sectorial que
|
||||
el director quiere a futuro. Rechazado por cerrar opción de bajo costo.
|
||||
- **Pseudonimización con `respondent_id` desde v1:** habilita longitudinal/re-contacto pero agrega
|
||||
carga legal (es dato personal) sin caso de uso inmediato. Rechazado para v1; campo reservado.
|
||||
- **Nivel 3 atribuible desde v1:** máximo poder analítico, pero requiere acuerdo de tratamiento por
|
||||
empresa + consentimiento explícito + vía de re-identificación. Diferido hasta tener feedback real.
|
||||
|
||||
## Consecuencias
|
||||
- (+) Cumplimiento alineado a Ley 7593 desde el diseño; soberanía; protección del empleado frente a
|
||||
su empleador.
|
||||
- (+) Mantiene viva la opción de Nivel 2 sin recolectar datos personales adicionales.
|
||||
- (−) El dato de los 4 estudios no podrá usarse jamás para análisis atribuible ni longitudinal
|
||||
individual (decisión consciente).
|
||||
- (−) El umbral de k-anonimato puede dejar segmentos chicos sin reporte ("n insuficiente") — es el
|
||||
costo correcto de la privacidad.
|
||||
|
||||
## Pendiente
|
||||
Validación del flujo de consentimiento y del lenguaje legal con asesor paraguayo antes de producción.
|
||||
52
docs/adr/ADR-002-stack-tecnologico.md
Normal file
52
docs/adr/ADR-002-stack-tecnologico.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# ADR-002 — Stack tecnológico y residencia de datos
|
||||
|
||||
- **Estado:** Aceptado (stack) · **Abierto** (residencia de datos)
|
||||
- **Fecha:** (completar con fecha de commit)
|
||||
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Se necesita un stack moderno, con experiencia móvil fluida, reportes potentes, soberanía de datos y
|
||||
sin lock-in caro. El volumen no justifica una suite SaaS profesional. Se tratan datos sensibles.
|
||||
|
||||
## Decisión (stack)
|
||||
|
||||
| Capa | Tecnología | Razón |
|
||||
|---|---|---|
|
||||
| Frontend | Next.js (App Router) | SSR/SSG, ecosistema maduro, buena DX, móvil sólido |
|
||||
| Form engine | React + react-hook-form + zod | Validación tipada, flexible, sin vendor lock-in |
|
||||
| Backend / datos | Supabase (Postgres + Auth + RLS + Realtime) | Postgres real para reportes SQL, RLS nativo multi-tenant, self-host posible |
|
||||
| Auth | Supabase Auth | Anónimo / OAuth / magic link según encuesta |
|
||||
| Reportes/gráficos | ECharts (preferido) o Recharts | Gratis, potente, tipos de gráfico variados (heatmap, polar, pirámide), no-SaaS |
|
||||
| Deploy app | Vercel | Rápido para arrancar, migrable |
|
||||
|
||||
Supabase es la pieza clave: Postgres real habilita los reportes cruzados (filtrar respuesta A por
|
||||
respuesta B), RLS da el aislamiento multi-tenant que ADR-001 exige, y el modo self-host (Docker)
|
||||
preserva la opción de soberanía total.
|
||||
|
||||
## Decisión ABIERTA — residencia de los datos
|
||||
|
||||
Supabase Cloud aloja datos en regiones US/EU por defecto. Para un producto cuyo requisito explícito
|
||||
es **soberanía** y que trata **datos sensibles de ciudadanos paraguayos**, la ubicación física del
|
||||
dato no es neutral:
|
||||
|
||||
- La Ley 7593/2025 tiene reglas de **transferencia internacional** (requiere nivel de protección
|
||||
adecuado o garantías apropiadas en el país destinatario).
|
||||
- "Soberanía total" sugiere self-host (Supabase en infraestructura propia/regional).
|
||||
|
||||
**Opciones:**
|
||||
1. **Supabase Cloud** (US/EU) — más rápido de arrancar, menos operación, pero transferencia
|
||||
internacional a evaluar legalmente y tensión con "soberanía".
|
||||
2. **Supabase self-host** (Docker, infra propia o proveedor regional) — soberanía real, más operación.
|
||||
3. **Híbrido:** arrancar en Cloud para el POC interno, migrar a self-host antes de producción con
|
||||
datos reales de ciudadanos.
|
||||
|
||||
**No se cierra todavía.** Requiere: (a) decisión del director sobre apetito de operación, (b) input
|
||||
legal sobre transferencia internacional bajo Ley 7593. **Bloqueante para producción, no para v1
|
||||
interno** si v1 corre con datos de prueba/simulados.
|
||||
|
||||
## Consecuencias
|
||||
- (+) Stack migrable; Supabase no es lock-in si se self-hostea.
|
||||
- (−) Mantener la opción self-host implica disciplina (no usar features Cloud-only sin registrarlo).
|
||||
- Registrar en este ADR cuándo y cómo se resuelve la residencia.
|
||||
54
docs/adr/ADR-003-consentimiento-versionado.md
Normal file
54
docs/adr/ADR-003-consentimiento-versionado.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# ADR-003 — Consentimiento granular, versionado e inmutable
|
||||
|
||||
- **Estado:** Aceptado
|
||||
- **Fecha:** (completar con fecha de commit)
|
||||
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Bajo lógica RGPD / Ley 7593, el consentimiento para datos sensibles debe ser específico, informado y
|
||||
**demostrable**: hay que poder probar qué consintió cada persona, cuándo, y contra qué texto. Además,
|
||||
el director planteó dos caminos de consentimiento que pueden coexistir y divergir: documentos
|
||||
firmados (empresa/encuestado) y checkboxes in-app.
|
||||
|
||||
## Decisión
|
||||
|
||||
### 1. El consentimiento es evidencia, no solo UI
|
||||
Mostrar el checkbox no alcanza. Cada respuesta guarda un **registro de consentimiento** con: versión
|
||||
del texto, scopes efectivamente aceptados, timestamp. Es **inmutable** (solo INSERT/SELECT; sin
|
||||
UPDATE/DELETE).
|
||||
|
||||
### 2. Versionado del texto
|
||||
El texto de consentimiento vive en `consent_versions`. Si cambia el wording en el futuro, las
|
||||
respuestas viejas **conservan su versión vieja**. Nunca se reescribe retroactivamente lo que alguien
|
||||
aceptó.
|
||||
|
||||
### 3. Granular por capas
|
||||
Scopes separados por propósito, aceptables independientemente:
|
||||
`operativo` (dashboard de su empresa), `macro_n1` (agregados anónimos del estudio país), y a futuro
|
||||
`macro_n2` / `macro_n3`. **v1 ofrece solo `operativo` + `macro_n1`** (las encuestas son anónimas y
|
||||
Modo A sin Nivel 3). Un titular puede aceptar operativo y rechazar macro.
|
||||
|
||||
### 4. La elección del titular gana sobre la firma de la empresa
|
||||
Si la empresa firmó "todos los datos van al macro" pero el empleado destildó macro en la app, la
|
||||
respuesta NO entra al macro. El registro in-app del titular es la fuente de verdad sobre su propio dato.
|
||||
|
||||
### 5. Honestidad sobre derechos en encuestas anónimas
|
||||
En encuesta anónima:
|
||||
- No se puede hacer firmar al encuestado nada identificable (destruiría el anonimato) → el registro
|
||||
in-app es el **único** registro de consentimiento posible; no se complementa con firma del titular.
|
||||
- No se puede **revocar** después (no se puede ubicar la respuesta). El texto debe declararlo
|
||||
explícitamente **antes** de responder: "esta encuesta es anónima; no vas a poder revocar luego".
|
||||
- El wizard, al togglear anonimato, cambia qué derechos se pueden ofrecer y ajusta el texto.
|
||||
|
||||
## Consecuencias
|
||||
- (+) Demostrabilidad ante la ANPDP; trazabilidad por versión.
|
||||
- (+) Respeta la autonomía del titular sobre su dato sensible.
|
||||
- (−) Un mismo dato puede estar en el dashboard de la empresa pero excluido del macro (lógica de
|
||||
reportes debe respetar `granted_scopes` por fila — no es opcional).
|
||||
- (−) Encuestas anónimas pierden el derecho de revocación; debe comunicarse con transparencia.
|
||||
|
||||
## Relación con otros ADR
|
||||
Implementa el componente de consentimiento de ADR-001. El esquema está en `DATA_MODEL.md`
|
||||
(`consent_versions`, `consent_records`).
|
||||
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.
|
||||
Reference in New Issue
Block a user