Files
motor-de-encuestas/docs/adr/ADR-001-privacidad-y-gobierno-de-datos.md
2026-06-04 14:30:28 -03:00

123 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
### 11. Salary range — dato de acceso analyst-only (addendum Jun 2026)
La pregunta `3.4` captura la franja salarial del respondente. Es el dato clave para calcular la
"carga económica del cuidado como % del ingreso" — núcleo del argumento de la economía del cuidado.
Decisión del equipo:
- Se recolecta y almacena en `answers.value` como cualquier respuesta. `is_sensitive=true`.
- Acceso: **exclusivamente rol analyst** (vía `service_role`, que bypassa RLS). Nunca expuesto
a org_admin — ni en vistas crudas ni en funciones de agregación accesibles a ese rol.
- Ninguna llamada a `agg_segment` ni ningún reporte de org_admin incluye salary como dimensión
o como valor. El GRANT existente ya lo bloquea (org_admin tiene 0 filas en `answers`).
- Protección "técnicamente casi imposible": RLS bloquea el path de org_admin + cifrado en reposo
de Supabase + audit logs de accesos. Riesgo de breach externo es categoría separada
(plan de respuesta a incidentes, fuera del scope de este ADR).
## 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.