Privacidad
Privacidad
El punto de partida
Lo que una persona necesita evitar puede revelar una condición de salud. En el marco del RGPD eso es una categoría especial de datos, y el diseño entero parte de ahí.
Por defecto, nada sale del dispositivo
No es una opción de configuración. Es el comportamiento por defecto, y hoy es el único que existe:
- Los NO se guardan en
localStorage. No hay cuenta, no hay servidor, no hay identificador que enviar. - El historial de escaneos es local.
- La caché de productos es local.
- No se usa geolocalización. Ni una llamada. Si algún día hay «dónde comprar cerca», se pedirá permiso en ese momento y para eso.
- No hay cookies. Ni propias ni de terceros.
- No hay terceros en el camino crítico. El único dominio externo que se toca es Open Food Facts, y solo al buscar un producto. El CDN del OCR solo se toca si la persona hace una foto.
Se puede comprobar: la CSP del servidor de desarrollo es la misma que la de producción y solo permite self, world.openfoodfacts.org y cdn.jsdelivr.net.
Minimización
La función sanear() de apps/pwa/js/perfil.js es deliberadamente estricta: descarta cualquier campo que no sea id, nombre, lista de NO validada contra un patrón, condiciones de una lista cerrada, rigor y marca de menor. Si alguien añade un campo al perfil, no se guarda hasta que se añada ahí. Es una lista blanca, no negra.
Separación de identidad
Cuando existan cuentas, la identidad y el perfil de salud no comparten tabla:
users (email_hash) ──► user_pseudonyms (pseudonym) ──► profiles (owner_pseudonym)
└─► profile_restrictions
profiles no tiene columna user_id. Vincular una persona con sus alérgenos exige cruzar dos tablas a propósito. Y el correo se guarda como hash, nunca en claro.
Fotos
Antes de leer una foto se redibuja en un <canvas>. Eso no es solo para mejorar el OCR: redibujar elimina los metadatos EXIF, incluida la geolocalización. Lo que se procesa y lo que se muestra como vista previa sale del canvas, no del archivo original.
Analizar tu foto y cedérnosla para mejorar la base de datos son dos permisos distintos. En ocr_jobs, image_retained es false por defecto y solo pasa a true con un consentimiento aparte, registrado en consents con su propósito, su versión de política y cómo se recogió. Las imágenes retenidas llevan retention_until.
Analítica
Eventos permitidos: una lista cerrada de veinte (scan_started, product_found, ocr_success, contains_result…).
Dimensiones prohibidas, filtradas en el propio módulo: perfil, evita, condiciones, alergenos, allergen, nombre, gtin, texto, ingredientes, salud, diagnostico. Si alguien las pasa por error, se descartan antes de registrar nada.
La tabla scan_events tampoco tiene columna de alérgeno ni de perfil. No es un olvido: está comentado en la migración para que nadie la añada sin pensarlo.
Lo que nunca llega a la publicidad
El motor publicitario no recibe perfil, diagnóstico, historial de alérgenos ni historial de escaneos sensible. Su entrada es un contexto que lanza una excepción si detecta un campo de salud. Ver ADS_ARCHITECTURE.md.
Menores
Los perfiles infantiles los gestiona un adulto (profiles.is_minor). No hay publicidad personalizada para menores. La contextual se mantiene, porque no usa nada de la persona.
Pendiente antes de producción
- [ ] DPIA. Evaluación de impacto documentada.
- [ ] Registro de actividades de tratamiento.
- [ ] Políticas de retención concretas por tipo de dato.
- [ ] Flujos de acceso, exportación y supresión.
- [ ] Designación de DPO y su procedimiento.
- [ ] Auditoría ePrivacy. Hoy no hay cookies no esenciales; hay que mantenerlo así y documentarlo.
- [ ] Base legal para cada tratamiento cuando existan cuentas.