02 - Arquitectura Cliente-Servidor de MedTime¶
Identificador: TECH-CS-001 Version: 1.1.0 Fecha: 2025-12-08 Ultima Revision: Regla de 100 registros para catalogos locales - Directiva del Director Autor: SpecQueen + ArchitectureDrone Refs Funcional: MTS-OFF-001, MTS-PRI-001, INV-001, INV-008, INV-011 Estado: Aprobado
- 1. Principios Fundamentales
- 1.1. Offline-First: 100% Operativo Sin Conexion
- 1.2. Zero-Knowledge: Servidor NUNCA Ve Datos PHI en Claro
- 1.3. Client-Heavy: El Dispositivo Hace el Trabajo Pesado
- 1.4. Sync-Light: Solo Blobs Cifrados Viajan al Servidor
- 2. Vision General de la Arquitectura
- 2.1. Division de Responsabilidades
- 2.2. Diagrama de Arquitectura Dual
- 3. Componentes del Sistema
- 3.1. Dispositivo (Cliente) - 95% del Procesamiento
- 3.2. Servidor (Backend) - 5% Coordinacion
- 3.2.1. PostgreSQL (con RLS)
- 3.2.2. API de Sincronizacion
- 3.2.3. API de Catalogos Publicos
- 3.2.3.1. Regla de 100 Registros - Descarga Local vs Busqueda Online
- 3.2.3.2. Catalogos Principales (Enriquecibles con Datos de Usuarios)
- 3.2.3.3. Catalogos Secundarios (Estaticos - No Enriquecibles)
- 3.2.3.4. Catalogos de Codigos Estandar (Externos - No Enriquecibles)
- 3.2.3.5. Mini-Cache Critico de Interacciones CONTRAINDICADO
- 3.2.3.6. Disclaimer de Privacidad para Busquedas Online
- 3.2.4. Firebase Auth Integration
- 3.2.5. OCR Service (Opcional - Pro/Perfect)
- 4. Tabla Maestra de Clasificacion de Datos
- 4.1. Clasificacion por Tipo de Dato
- 4.2. Leyenda de Clasificaciones
- 5. Detalle por Clasificacion de Datos
- 5.1. LOCAL_ONLY - Nunca Sale del Dispositivo
- 5.2. SYNCED_E2E - Sincronizado como Blob Cifrado
- 5.3. SYNCED_HASH - Sincronizado como Hash/Blind Index
- 5.4. SYNCED_PLAIN - Sincronizado en Claro
- 5.5. SERVER_SOURCE - Origen en Servidor, Usado para Catalogo Local
- 5.6. ANONYMIZED_DATA - Datos Anonimizados para Enriquecimiento
- 6. Flujo de Sincronizacion
- 6.1. Diagrama de Flujo de Sincronizacion
- 6.2. Proceso de Cifrado Antes de Envio
- 6.3. Estructura de Datos en Servidor
- 7. Lo que NUNCA Sale del Dispositivo
- 7.1. Lista Exhaustiva de Datos LOCAL_ONLY
- 7.2. Justificacion por Categoria
- 8. Lo que el Servidor VE vs NO VE
- 8.1. Tabla de Visibilidad del Servidor
- 8.2. Ejemplo Practico: Que Ve un Admin de Base de Datos
- 9. Arquitectura de Seguridad
- 9.1. Capas de Cifrado
- 9.2. Gestion de Claves
- 9.3. Flujo de Cifrado E2E
- 10. Implicaciones para Desarrollo
- 10.1. Reglas para Desarrolladores de Cliente
- 10.2. Reglas para Desarrolladores de Backend
- 10.3. Checklist de Seguridad por Feature
- 11. Diferencias por Tier
- 11.1. Tier Free: Local por Defecto
- 11.2. Tier Pro/Perfect: Hibrido
- 12. Diagrama de Despliegue
- 13. Referencias
- 13.1. Documentos Funcionales
- 13.2. Investigaciones
- 13.3. Documentos Tecnicos
- 13.4. Estandares Externos
1. Principios Fundamentales¶
MedTime implementa una arquitectura client-heavy donde el dispositivo del usuario es el centro de procesamiento, y el servidor actua unicamente como coordinador de sincronizacion y proveedor de catalogos publicos.
1.1. Offline-First: 100% Operativo Sin Conexion¶
GARANTIA OFFLINE:
+------------------------------------------------------------------+
| La aplicacion MedTime DEBE funcionar completamente sin |
| conexion a Internet. El 100% de las funcionalidades CORE |
| estan disponibles offline. |
| |
| Funciones que requieren conexion: |
| - Crear cuenta nueva (verificacion inicial) |
| - Sincronizacion de datos (Pro/Perfect) |
| - OCR de recetas (Pro/Perfect) |
| - Actualizacion de catalogo de medicamentos |
+------------------------------------------------------------------+
| Escenario | Comportamiento | Tier |
|---|---|---|
| Usuario sin Internet todo el dia | 100% funcional | Todos |
| Viaje de 1 semana sin conexion | 100% funcional | Todos |
| Zona rural sin cobertura | 100% funcional | Todos |
| Modo avion permanente | 100% funcional | Free permanente |
1.2. Zero-Knowledge: Servidor NUNCA Ve Datos PHI en Claro¶
PRINCIPIO ZERO-KNOWLEDGE:
+------------------------------------------------------------------+
| El servidor MedTime NUNCA tiene acceso al contenido |
| de los datos de salud (PHI) del paciente. |
| |
| - Datos se cifran ANTES de salir del dispositivo |
| - Servidor almacena BLOBS opacos (indescifrable) |
| - Solo el paciente tiene la clave de descifrado |
| - En caso de brecha: atacante obtiene datos inutiles |
+------------------------------------------------------------------+
Referencia: Ver INV-001 (Cifrado E2E Zero Knowledge) e INV-008 (Cifrado de Perfil).
1.3. Client-Heavy: El Dispositivo Hace el Trabajo Pesado¶
| Procesamiento | Ubicacion | Porcentaje |
|---|---|---|
| Logica de negocio | Dispositivo | 95% |
| Calculos de adherencia | Dispositivo | 100% |
| Generacion de alertas | Dispositivo | 100% |
| Machine Learning | Dispositivo | 100% |
| Gamificacion | Dispositivo | 100% |
| Sincronizacion | Compartido | 50/50 |
| Autenticacion | Servidor | 80% |
| Catalogos publicos | Servidor | 100% |
1.4. Sync-Light: Solo Blobs Cifrados Viajan al Servidor¶
flowchart LR
subgraph DEVICE["DISPOSITIVO"]
D1["Datos en claro"]
end
subgraph CRYPTO["CIFRADO"]
C1["AES-256-GCM"]
end
subgraph SERVER["SERVIDOR"]
S1["Blob cifrado"]
S2["Metadata operativa"]
S3["Blind indexes"]
end
D1 -->|"Cifrar E2E"| C1
C1 -->|"Solo blobs opacos"| S1
style DEVICE fill:#e8f5e9,stroke:#2e7d32
style CRYPTO fill:#fff3e0,stroke:#e65100
style SERVER fill:#e3f2fd,stroke:#1565c0
El servidor SOLO recibe:
- Blobs cifrados (indescifrable sin clave del usuario)
- Metadata operativa (timestamps, tamanos, hashes)
- Indices ciegos (blind indexes) para busqueda
2. Vision General de la Arquitectura¶
2.1. Division de Responsabilidades¶
flowchart LR
subgraph DEVICE["DISPOSITIVO (95%)"]
D1["Base datos local<br/>Core Data / Room<br/>Datos en CLARO"]
D2["Cifrado E2E<br/>AES-256-GCM<br/>ANTES de enviar"]
D3["Alertas locales<br/>100% offline<br/>AlarmManager / UNNotification"]
D4["ML On-Device<br/>Patrones y predicciones<br/>NUNCA salen"]
D5["Gamificacion<br/>Puntos/Rachas<br/>Sincronizados"]
D6["Cola de Sync<br/>Operaciones pendientes"]
end
subgraph SERVER["SERVIDOR (5%)"]
S1["PostgreSQL<br/>Blobs cifrados<br/>Metadata + Blind indexes"]
S2["API de Sync<br/>Push/Pull<br/>Conflictos"]
S3["Catalogos<br/>Medicamentos<br/>Interacciones"]
S4["Firebase Auth<br/>Tokens + MFA"]
S5["OCR opcional<br/>Pro/Perfect<br/>Anonimizado"]
end
D6 -->|"Blobs E2E"| S2
style DEVICE fill:#e8f5e9,stroke:#2e7d32
style SERVER fill:#e3f2fd,stroke:#1565c0
2.2. Diagrama de Arquitectura Dual¶
flowchart TB
subgraph DEVICE["DISPOSITIVO (95%)"]
direction TB
UI["UI Layer<br/>(SwiftUI / Compose)"]
BL["Business Logic<br/>(Domain Layer)"]
subgraph LOCAL_STORAGE["Almacenamiento Local"]
DB_LOCAL["Base de Datos<br/>Core Data / Room"]
KEYCHAIN["Keychain / Keystore<br/>(Claves)"]
end
subgraph LOCAL_PROCESSING["Procesamiento Local"]
ALERTS["Motor de Alertas<br/>100% Offline"]
ML["ML Engine<br/>CoreML / TFLite"]
CRYPTO["Motor Cifrado<br/>AES-256-GCM"]
GAME["Gamificacion<br/>Puntos + Rachas"]
end
SYNC_QUEUE["Cola de Sync<br/>(Operaciones Pendientes)"]
UI --> BL
BL --> LOCAL_STORAGE
BL --> LOCAL_PROCESSING
BL --> SYNC_QUEUE
end
subgraph SERVER["SERVIDOR (5%)"]
direction TB
API_GW["API Gateway<br/>(Auth + Rate Limit)"]
subgraph SERVICES["Servicios"]
SYNC_SVC["Sync Service"]
CATALOG_SVC["Catalog Service"]
OCR_SVC["OCR Service<br/>(Pro/Perfect)"]
end
subgraph DATA_STORE["Almacenamiento"]
PG["PostgreSQL<br/>(SQL Estandar)"]
CACHE["Redis Cache"]
end
AUTH["Firebase Auth"]
API_GW --> SERVICES
SERVICES --> DATA_STORE
API_GW --> AUTH
end
SYNC_QUEUE -->|"Blobs E2E<br/>Cifrados"| API_GW
CATALOG_SVC -->|"Datos Publicos"| DB_LOCAL
3. Componentes del Sistema¶
3.1. Dispositivo (Cliente) - 95% del Procesamiento¶
3.1.1. Base de Datos Local¶
| Plataforma | Tecnologia | Cifrado | Capacidad |
|---|---|---|---|
| iOS | Core Data + SQLite | Cifrado de disco iOS | Ilimitado |
| Android | Room + SQLite | Cifrado de disco Android | Ilimitado |
Caracteristicas:
- Datos almacenados en CLARO dentro del sandbox de la app
- Protegidos por cifrado de disco del sistema operativo
- Acceso biometrico/PIN requerido para abrir app
- Datos NUNCA exportados sin cifrar E2E
ESTRUCTURA LOCAL:
/app_sandbox/
├── databases/
│ └── medtime.db # SQLite cifrado por OS
├── documents/
│ ├── prescriptions/ # Imagenes cifradas E2E
│ └── exports/ # Backups cifrados
└── keychain/ # Claves de cifrado
├── master_key # Derivada de PIN/Biometria
├── sync_key # Para sincronizacion
└── recovery_key_hash # Hash de Recovery Key
3.1.2. Motor de Cifrado E2E¶
ESPECIFICACION CRIPTOGRAFICA:
+------------------------------------------------------------------+
| Algoritmo: AES-256-GCM (Galois/Counter Mode) |
| Key Derivation: Argon2id (64 MiB, 3 iteraciones, parallelism 4) |
| Nonce: 96 bits, aleatorio por operacion |
| Auth Tag: 128 bits (autenticacion integrada) |
| Salt: 256 bits, unico por usuario |
+------------------------------------------------------------------+
CUMPLIMIENTO:
- NIST SP 800-132 (Key Derivation)
- FIPS 140-2 (AES)
- HIPAA Safe Harbor (cifrado apropiado)
Flujo de cifrado antes de sincronizacion:
sequenceDiagram
participant U as Usuario
participant A as App
participant C as Crypto Engine
participant K as Keychain
participant S as Servidor
U->>A: Modifica medicamento
A->>A: Guarda en DB local (claro)
A->>K: Obtiene master_key
K->>A: master_key
A->>C: Cifrar(datos, master_key)
C->>C: Generar nonce aleatorio
C->>C: AES-256-GCM encrypt
C->>A: {blob_cifrado, nonce, tag}
A->>S: POST /sync {encrypted_blob}
Note over S: Servidor almacena<br/>blob opaco
3.1.3. Sistema de Alertas y Notificaciones¶
Referencia: OBS-066 - 100% OFFLINE (ver MTS-NTF-001)
ARQUITECTURA DE ALERTAS:
+------------------------------------------------------------------+
| TODAS las alertas se generan y disparan LOCALMENTE |
| Sin dependencia de servidor, push notifications son ADICIONALES |
+------------------------------------------------------------------+
iOS:
- UNUserNotificationCenter
- Limite: 64 notificaciones pendientes
- Persistencia: Sobrevive reinicio
Android:
- AlarmManager + WorkManager
- Sin limite practico
- Persistencia: BroadcastReceiver para boot
| Funcion | Implementacion | Offline |
|---|---|---|
| Programar alerta | APIs nativas OS | 100% |
| Disparar alerta | Sistema operativo | 100% |
| Sonido/Vibracion | Sistema operativo | 100% |
| Acciones en notificacion | App local | 100% |
| Push adicional | FCM/APNs | Requiere conexion |
3.1.4. Motor de ML On-Device¶
Referencia: MTS-NTF-001 - Notificaciones Inteligentes
MODELO ML LOCAL:
+------------------------------------------------------------------+
| Framework: CoreML (iOS) / TensorFlow Lite (Android) |
| Tipo: Clasificador binario (Random Forest ligero) |
| Tamano: < 500 KB |
| Entrenamiento: On-device, incremental |
| Datos: NUNCA salen del dispositivo |
+------------------------------------------------------------------+
FEATURES (12 total):
- dia_semana (one-hot, 7)
- hora_del_dia (normalizado, 1)
- dias_desde_ultima_omision (1)
- racha_actual (1)
- tasa_omision_historica (1)
- tiempo_respuesta_promedio (1)
OUTPUT: Probabilidad de omision [0.0 - 1.0]
| Dato de ML | Sale del dispositivo | Razon |
|---|---|---|
| Patrones detectados | NUNCA | Privacidad maxima |
| Predicciones | NUNCA | Calculo local |
| Modelo entrenado | NUNCA | On-device training |
| Estadisticas de uso | NUNCA | LOCAL_ONLY |
3.1.5. Sistema de Gamificacion¶
GAMIFICACION LOCAL:
+------------------------------------------------------------------+
| Puntos: Calculados localmente por toma/racha |
| Rachas: Conteo local, sincronizado para cuidadores |
| Logros: Desbloqueados localmente |
| Leaderboards: NO hay (privacidad) |
+------------------------------------------------------------------+
| Elemento | Calculo | Sincronizado |
|---|---|---|
| Puntos por toma | Local | Si (Pro/Perfect) |
| Racha actual | Local | Si (Pro/Perfect) |
| Mejor racha | Local | Si (Pro/Perfect) |
| Logros desbloqueados | Local | Si (Pro/Perfect) |
| Nivel de adherencia | Local | Si (Pro/Perfect) |
3.1.6. Cola de Sincronizacion¶
ESTRUCTURA DE COLA:
+------------------------------------------------------------------+
| Operaciones pendientes se encolan para sync cuando hay conexion |
+------------------------------------------------------------------+
SyncQueueItem {
id: UUID
operation: CREATE | UPDATE | DELETE
entity_type: MEDICATION | DOSE | APPOINTMENT | ...
entity_id: UUID
encrypted_payload: Blob // Cifrado E2E ANTES de encolar
local_timestamp: DateTime
attempts: Int
status: PENDING | IN_PROGRESS | COMPLETED | FAILED
priority: HIGH | MEDIUM | LOW
}
Prioridades de sincronizacion:
| Prioridad | Entidades | Timeout |
|---|---|---|
| HIGH | Dosis tomadas, alertas confirmadas | 30 seg |
| MEDIUM | Medicamentos, citas, calendario | 60 seg |
| LOW | Preferencias, estadisticas | 120 seg |
3.2. Servidor (Backend) - 5% Coordinacion¶
3.2.1. PostgreSQL (con RLS)¶
NOTA: La arquitectura utiliza PostgreSQL como base de datos del servidor, incluyendoRow Level Security (RLS) como mecanismo de autorizacion a nivel de base de datos. PostgreSQL es una dependencia aceptable dada su madurez, ubicuidad, y el soporte nativo de RLS que simplifica significativamente la implementacion de seguridad.
Justificacion de la dependencia PostgreSQL:
- Madurez: PostgreSQL es una tecnologia probada con decadas de desarrollo
- Ubicuidad: Disponible en todos los proveedores cloud principales
- RLS nativo: Simplifica autorizacion sin codigo adicional en aplicacion
- Compatibilidad: Si se requiere migracion futura, RLS puede replicarse en capa de aplicacion
-- Ejemplo: Tabla de datos cifrados (PostgreSQL con RLS)
-- RLS proporciona autorizacion automatica a nivel de base de datos
CREATE TABLE encrypted_user_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
-- METADATA VISIBLE (operativa)
entity_type VARCHAR(50) NOT NULL, -- 'medication', 'dose', etc.
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
sync_version BIGINT NOT NULL DEFAULT 1,
-- DATOS CIFRADOS (opacos)
encrypted_blob BYTEA NOT NULL, -- Blob E2E cifrado
blob_hash VARCHAR(64) NOT NULL, -- SHA-256 para integridad
blob_size_bytes INT NOT NULL,
encryption_version VARCHAR(10) NOT NULL DEFAULT '1.0',
-- INDICES CIEGOS (para busqueda sin ver datos)
blind_index_1 VARCHAR(64), -- HMAC-SHA256 de campo 1
blind_index_2 VARCHAR(64), -- HMAC-SHA256 de campo 2
CONSTRAINT unique_entity UNIQUE (user_id, entity_type, id)
);
-- Indices para rendimiento
CREATE INDEX idx_user_entity ON encrypted_user_data(user_id, entity_type);
CREATE INDEX idx_sync_version ON encrypted_user_data(user_id, sync_version);
CREATE INDEX idx_blind_1 ON encrypted_user_data(blind_index_1) WHERE blind_index_1 IS NOT NULL;
3.2.2. API de Sincronizacion¶
Referencia: MTS-OFF-001 Seccion 14 (API Endpoints de Sincronizacion)
| Endpoint | Metodo | Descripcion | Datos |
|---|---|---|---|
/v1/sync/status |
GET | Estado de sincronizacion | Metadata |
/v1/sync/push |
POST | Enviar cambios locales | Blobs E2E |
/v1/sync/pull |
GET | Obtener cambios remotos | Blobs E2E |
/v1/sync/resolve |
POST | Resolver conflictos | Metadata |
// Ejemplo: Push Request
{
"device_id": "uuid",
"last_sync_version": 1234567890,
"changes": [
{
"entity_type": "medication",
"entity_id": "uuid",
"operation": "update",
"encrypted_blob": "base64...", // CIFRADO E2E
"blob_hash": "sha256:abc123...",
"local_timestamp": "2025-12-07T10:30:00Z"
}
]
}
// Ejemplo: Push Response
{
"accepted": 5,
"conflicts": [],
"server_sync_version": 1234567891,
"server_time": "2025-12-07T10:30:05Z"
}
3.2.3. API de Catalogos Publicos¶
Los catalogos son datos publicosque no requieren cifrado. Los clientesNO almacenan cache directade estos catalogos; en su lugar,construyen sus propios catalogos locales combinando:
- Datos descargados de catalogos publicos (cuando estan disponibles)
- Entradas manuales del usuario (medicamentos no encontrados en catalogo)
IMPORTANTE: El acceso a catalogos publicos requiere cuenta de usuario autenticada. Usuarios Free sin cuenta solo pueden agregar medicamentos manualmente.
3.2.3.1. Regla de 100 Registros - Descarga Local vs Busqueda Online¶
REGLA CRITICA DE CATALOGOS:
+------------------------------------------------------------------+
| Para proteger la privacidad y optimizar el rendimiento, |
| MedTime aplica la REGLA DE 100 REGISTROS para determinar |
| si un catalogo puede descargarse al dispositivo. |
+------------------------------------------------------------------+
CRITERIOS:
+------------------------------------------------------------------+
| Catalogos <= 100 registros: |
| - PUEDEN embeberse en la aplicacion |
| - Disponibles offline sin restricciones |
| - Actualizados con cada release de la app |
| |
| Catalogos > 100 registros: |
| - NO se descargan al dispositivo |
| - Requieren BUSQUEDA ONLINE |
| - Cada busqueda requiere conexion a Internet |
| - Se muestra disclaimer de privacidad |
+------------------------------------------------------------------+
EXCEPCION - MINI-CACHE CRITICO:
+------------------------------------------------------------------+
| Para garantizar seguridad offline, se permite un mini-cache |
| de <100 interacciones marcadas como CONTRAINDICADO. |
| Esto permite alertas de seguridad criticas sin conexion. |
+------------------------------------------------------------------+
Justificacion de la regla:
| Aspecto | Razon |
|---|---|
| Privacidad | Descargar catalogos grandes expone patrones de busqueda offline |
| Rendimiento | Catalogos >100 registros impactan almacenamiento y sincronizacion |
| Actualizacion | Catalogos pequenos se actualizan con releases; grandes via API |
| Seguridad | Mini-cache critico garantiza alertas de interacciones peligrosas offline |
Disclaimer de privacidad (busquedas online):
Al realizar busquedas en catalogos online, su consulta de busqueda se envia a los servidores de MedTime. No almacenamos historial de busquedas ni asociamos consultas con su identidad. Los resultados seleccionados se guardan SOLO en su dispositivo local.
3.2.3.2. Catalogos Principales (Enriquecibles con Datos de Usuarios)¶
| Catalogo | Endpoint | Tamano Estimado | Descarga Local | TTL | Enriquecible | Especificacion |
|---|---|---|---|---|---|---|
| Medicamentos | /v1/catalog/medications |
~50,000+ | NO | 7d | SI | INV-010 |
| Estudios/Tratamientos | /v1/catalog/studies |
~100 | SI(limite) | 7d | SI | INV-011 |
| Interacciones Med-Med | /v1/catalog/interactions |
~1,000,000+ | NO | 7d | SI | INV-012 |
| Interacciones Med-Estudio | /v1/catalog/med-study-interactions |
~5,000+ | NO | 7d | SI | INV-013 |
| Efectos secundarios | /v1/catalog/side_effects |
~10,000+ | NO | 7d | SI | INV-014 |
ENRIQUECIMIENTO: Los catalogos marcados como "Enriquecibles" reciben datos anonimizados de usuarios siguiendo el principio de separacion de identidad (datos especificos SIN identificadores de usuario). El consentimiento es OBLIGATORIO para usuarios con cuenta. Ver INV-009.
REGLA DE 100: Solo Estudios/Tratamientos cumple el limite para descarga local. El resto requiere busqueda ONLINE con disclaimer de privacidad.
3.2.3.3. Catalogos Secundarios (Estaticos - No Enriquecibles)¶
| Catalogo | Endpoint | Tamano Estimado | Descarga Local | TTL | Enriquecible |
|---|---|---|---|---|---|
| Contraindicaciones | /v1/catalog/contraindications |
~5,000+ | NO | 7d | NO |
| Unidades | /v1/catalog/units |
~30-50 | SI (embebido) | 30d | NO |
| Formas farmaceuticas | /v1/catalog/forms |
~50-80 | SI (embebido) | 30d | NO |
| Vias administracion | /v1/catalog/routes |
~20-30 | SI (embebido) | 30d | NO |
| Frecuencias | /v1/catalog/frequencies |
~30-50 | SI (embebido) | 30d | NO |
| Categorias terapeuticas | /v1/catalog/categories |
~50-100 | SI (limite) | 30d | NO |
| Laboratorios (fabricantes) | /v1/catalog/labs |
~500+ | NO | 30d | NO |
CATALOGOS EMBEBIDOS: Los catalogos pequenos (Unidades, Formas, Vias, Frecuencias, Categorias) se incluyen directamente en el build de la aplicacion y se actualizan con cada release. Esto permite operacion 100% offline para seleccion de estos valores.
3.2.3.4. Catalogos de Codigos Estandar (Externos - No Enriquecibles)¶
| Catalogo | Fuente | Tamano Estimado | Descarga Local | Uso en MedTime |
|---|---|---|---|---|
| ATC | OMS | ~6,000+ | NO | Categorizar medicamentos |
| LOINC | Regenstrief | ~90,000+ | NO | Codificar estudios |
| ICD-10-PCS | OMS | ~70,000+ | NO | Codificar tratamientos |
| RxNorm | NIH (USA) | ~100,000+ | NO | Normalizar nombres |
| COFEPRIS | Gobierno MX | ~15,000+ | NO | Validar disponibilidad MX |
| CSG | Gobierno MX | ~5,000+ | NO | Medicamentos autorizados MX |
NOTA: Los catalogos de codigos estandar son mantenidos por organismos externos y NO se enriquecen con datos de usuarios. Todos estos catalogos exceden el limite de 100 registros, por lo que requieren consulta ONLINE exclusivamente. El usuario busca, selecciona, y solo el registro seleccionado se guarda en su catalogo local.
Flujo de construccion de catalogo local:
USUARIO BUSCA MEDICAMENTO:
+------------------------------------------------------------------+
| 1. Buscar en CATALOGO LOCAL del usuario (offline) |
| 2. Si no existe Y tiene cuenta: |
| - Mostrar disclaimer de privacidad (primera vez o manual) |
| - Buscar en CATALOGO PUBLICO ONLINE |
| 3. Si no existe en publico: |
| - Usuario ingresa manualmente |
| 4. SOLO el registro seleccionado se guarda en catalogo local |
+------------------------------------------------------------------+
CATALOGO LOCAL = Registros seleccionados de catalogo publico
+ Entradas manuales del usuario
IMPORTANTE - PRIVACIDAD:
+------------------------------------------------------------------+
| El servidor NO sabe que registros selecciono el usuario. |
| Solo ve la consulta de busqueda (texto ingresado). |
| El registro seleccionado se guarda SOLO en el dispositivo. |
| El catalogo local se sincroniza E2E (cifrado). |
+------------------------------------------------------------------+
// Ejemplo: Catalogo de medicamentos (datos PUBLICOS)
{
"medications": [
{
"id": "rxcui-12345",
"name": "Metformin",
"brand_names": ["Glucophage", "Fortamet"],
"strength": "500mg",
"form": "tablet",
"rxnorm_code": "12345",
"category": "antidiabetico_oral"
}
],
"version": "2025-12-07",
"total": 15000
}
3.2.3.5. Mini-Cache Critico de Interacciones CONTRAINDICADO¶
Para garantizar la seguridad del paciente incluso sin conexion a Internet, MedTime incluye un mini-cache de interacciones medicamentosas criticas embebido en la aplicacion.
MINI-CACHE CRITICO:
+------------------------------------------------------------------+
| Proposito: Alertas de seguridad OFFLINE para interacciones |
| marcadas como CONTRAINDICADO |
| Tamano: <100 registros (cumple regla de descarga local) |
| Contenido: Pares de medicamentos con severidad maxima |
| Actualizacion: Con cada release de la aplicacion |
| Disponibilidad: TODOS los tiers (incluso Free sin cuenta) |
+------------------------------------------------------------------+
CRITERIOS DE INCLUSION EN MINI-CACHE:
+------------------------------------------------------------------+
| - Severidad: CONTRAINDICADO (nivel maximo) |
| - Frecuencia: Medicamentos de uso comun |
| - Riesgo: Potencialmente fatal si se combinan |
| - Ejemplo: Warfarina + Aspirina (riesgo hemorragia) |
| - Ejemplo: Metformina + Contraste yodado (riesgo acidosis) |
| - Ejemplo: IMAO + ISRS (riesgo sindrome serotoninergico) |
+------------------------------------------------------------------+
Comportamiento del mini-cache:
| Escenario | Comportamiento |
|---|---|
| Usuario agrega medicamento | Verificar contra mini-cache local |
| Interaccion CONTRAINDICADO encontrada | Alerta INMEDIATA, sin conexion |
| Interaccion no en mini-cache | Verificar online si hay conexion |
| Sin conexion + no en mini-cache | No hay alerta (limitacion conocida) |
NOTA: El mini-cache NO reemplaza la verificacion completa de interacciones online. Es una red de seguridad minima para los casos mas criticos y frecuentes. Los usuarios deben consultar con su medico para informacion completa de interacciones.
3.2.3.6. Disclaimer de Privacidad para Busquedas Online¶
Cuando el usuario realiza busquedas en catalogos que requieren consulta online (>100 registros), se muestra un disclaimer de privacidad.
MOMENTOS DE VISUALIZACION DEL DISCLAIMER:
+------------------------------------------------------------------+
| 1. PRIMERA VEZ que habilita "Busqueda automatica en catalogo" |
| - Se muestra una sola vez al activar la opcion |
| - Checkbox de aceptacion obligatorio |
| - Se guarda preferencia localmente |
| |
| 2. CADA VEZ en busqueda MANUAL (si automatica deshabilitada) |
| - Antes de enviar la consulta al servidor |
| - Opcion para habilitar busqueda automatica |
| - Toggle: "No mostrar de nuevo, activar busqueda automatica" |
+------------------------------------------------------------------+
Texto del disclaimer:
+------------------------------------------------------------------+
| AVISO DE PRIVACIDAD - BUSQUEDA EN CATALOGOS |
| |
| Al buscar en nuestros catalogos de medicamentos, estudios o |
| interacciones, su texto de busqueda sera enviado a los |
| servidores de MedTime. |
| |
| LO QUE HACEMOS: |
| [OK] Procesar su busqueda para mostrar resultados |
| [OK] Eliminar la consulta inmediatamente despues |
| |
| LO QUE NO HACEMOS: |
| [X] Almacenar historial de sus busquedas |
| [X] Asociar busquedas con su identidad |
| [X] Compartir consultas con terceros |
| [X] Usar busquedas para perfilamiento |
| |
| El resultado que seleccione se guardara UNICAMENTE en su |
| dispositivo, cifrado y bajo su control exclusivo. |
| |
| [ ] Entiendo y acepto continuar |
| [ ] Activar busqueda automatica (no preguntar de nuevo) |
+------------------------------------------------------------------+
Configuracion de privacidad relacionada:
| Opcion | Default | Descripcion |
|---|---|---|
| Busqueda automatica | OFF | Si ON, busca sin disclaimer cada vez |
| Mostrar disclaimer | ON | Si OFF (solo con auto ON), no muestra aviso |
REQUISITO REGULATORIO: Este disclaimer cumple con requisitos de transparencia de LFPDPPP (Mexico), LGPD (Brasil), y HIPAA Privacy Rule (USA).
3.2.4. Firebase Auth Integration¶
FIREBASE AUTH:
+------------------------------------------------------------------+
| Proposito: Autenticacion y tokens JWT |
| MFA: Obligatorio para Pro/Perfect |
| Datos en Firebase: |
| - email_hash (blind index) |
| - phone_hash (blind index para MFA) |
| - tier (plain text) |
| - created_at (timestamp) |
| |
| Datos que Firebase NO tiene: |
| - Nombre real del usuario |
| - Medicamentos |
| - Historial medico |
| - Cualquier dato PHI |
+------------------------------------------------------------------+
3.2.5. OCR Service (Opcional - Pro/Perfect)¶
OCR SERVICE:
+------------------------------------------------------------------+
| Disponibilidad: Solo Pro/Perfect |
| Procesamiento: Servidor (requiere GPU) |
| Privacidad: RESPONSABILIDAD DEL USUARIO anonimizar antes |
+------------------------------------------------------------------+
DOCUMENTOS SOPORTADOS:
- Recetas medicas
- Resultados de laboratorio
- Informes medicos
- Prospectos de medicamentos
- Cualquier documento que el usuario desee procesar
ADVERTENCIA DE SEGURIDAD (mostrar al usuario):
+------------------------------------------------------------------+
| ⚠️ IMPORTANTE - RESPONSABILIDAD DE ANONIMIZACION |
| |
| Antes de enviar cualquier documento para OCR, usted es |
| responsable de: |
| |
| 1. Tachar/borrar nombres de personas (paciente, medico) |
| 2. Tachar/borrar direcciones y telefonos |
| 3. Tachar/borrar numeros de identificacion (INE, CURP, etc.) |
| 4. Revisar que no haya datos sensibles visibles |
| |
| MedTime procesara el documento con maxima confidencialidad, |
| pero la imagen sera procesada en servidor. Si contiene datos |
| personales no anonimizados, estos seran visibles durante el |
| procesamiento. |
| |
| [ ] He revisado y anonimizado el documento |
| [ ] Acepto la responsabilidad de los datos enviados |
+------------------------------------------------------------------+
Flujo de OCR con anonimizacion manual:
sequenceDiagram
participant U as Usuario
participant A as App
participant S as Servidor OCR
U->>A: Toma foto de documento
A->>A: Mostrar preview + herramientas de edicion
Note over A: Herramientas: tachar, blur, crop
U->>A: Anonimiza manualmente (tacha nombres, etc.)
A->>U: Mostrar advertencia de seguridad
U->>A: Confirma "He revisado y anonimizado"
A->>A: Guardar imagen ORIGINAL en local (cifrada E2E)
A->>S: Enviar imagen ANONIMIZADA via TLS
S->>S: Procesar OCR
S->>S: Estructurar datos extraidos
S->>S: ELIMINAR imagen inmediatamente
S->>A: Retornar texto estructurado
A->>A: Asociar texto con datos del usuario LOCALMENTE
A->>A: Guardar resultado en DB local (cifrado)
Note over S: Servidor NO retiene la imagen<br/>procesamiento temporal, sin persistencia
Garantias de privacidad:
| Aspecto | Implementacion |
|---|---|
| Imagen original | Permanece en dispositivo, cifrada E2E, NUNCA se envia |
| Imagen anonimizada | Se envia al servidor solo para procesamiento |
| Retencion en servidor | CERO - imagen eliminada inmediatamente despues de OCR |
| Resultado OCR | Texto estructurado, asociado con usuario LOCALMENTE |
| Responsabilidad | Usuario debe anonimizar antes de enviar |
Herramientas de anonimizacion en app:
- Rectangulo de tachado (negro opaco)
- Herramienta de blur/pixelado
- Crop para recortar secciones
- Preview antes de enviar
- Checkbox de confirmacion obligatorio
4. Tabla Maestra de Clasificacion de Datos¶
4.1. Clasificacion por Tipo de Dato¶
| Dato | Local (Claro) | Servidor | Formato Servidor | Clasificacion |
|---|---|---|---|---|
| Identificacion | ||||
| SI | SI | Blind Index (HMAC) | SYNCED_HASH | |
| telefono | SI | SI | Blind Index (HMAC) | SYNCED_HASH |
| nombre_completo | SI | SI | Blob E2E | SYNCED_E2E |
| fecha_nacimiento | SI | SI | Blob E2E | SYNCED_E2E |
| foto_perfil | SI | SI | Blob E2E | SYNCED_E2E |
| Datos Medicos (PHI) | ||||
| medicamentos | SI | SI | Blob E2E | SYNCED_E2E |
| dosis_tomadas | SI | SI | Blob E2E | SYNCED_E2E |
| recetas | SI | SI | Blob E2E | SYNCED_E2E |
| citas_medicas | SI | SI | Blob E2E | SYNCED_E2E |
| mediciones_salud | SI | SI | Blob E2E | SYNCED_E2E |
| alergias | SI | SI | Blob E2E | SYNCED_E2E |
| condiciones_cronicas | SI | SI | Blob E2E | SYNCED_E2E |
| notas_medicas | SI | SI | Blob E2E | SYNCED_E2E |
| Machine Learning | ||||
| patrones_detectados | SI | NO | N/A | LOCAL_ONLY |
| predicciones_omision | SI | NO | N/A | LOCAL_ONLY |
| modelo_ml_entrenado | SI | NO | N/A | LOCAL_ONLY |
| historial_respuestas | SI | NO | N/A | LOCAL_ONLY |
| Gamificacion | ||||
| puntos_totales | SI | SI | Blob E2E | SYNCED_E2E |
| racha_actual | SI | SI | Blob E2E | SYNCED_E2E |
| logros | SI | SI | Blob E2E | SYNCED_E2E |
| Metadata Operativa | ||||
| tier_suscripcion | SI | SI | Plaintext | SYNCED_PLAIN |
| fecha_registro | SI | SI | Plaintext | SYNCED_PLAIN |
| ultimo_acceso | SI | SI | Plaintext | SYNCED_PLAIN |
| sync_version | SI | SI | Plaintext | SYNCED_PLAIN |
| device_id | SI | SI | Plaintext | SYNCED_PLAIN |
| idioma | SI | SI | Plaintext | SYNCED_PLAIN |
| zona_horaria | SI | SI | Plaintext | SYNCED_PLAIN |
| Catalogos | ||||
| catalogo_medicamentos | Cache | Source | Publico | SERVER_SOURCE |
| catalogo_interacciones | Cache | Source | Publico | SERVER_SOURCE |
| catalogo_unidades | Cache | Source | Publico | SERVER_SOURCE |
| Claves Criptograficas | ||||
| master_key | SI (Keychain) | NO | N/A | LOCAL_ONLY |
| recovery_key | SI (Usuario guarda) | NO | N/A | LOCAL_ONLY |
| sync_key_encrypted | SI | SI | Blob E2E | SYNCED_E2E |
4.2. Leyenda de Clasificaciones¶
| Clasificacion | Descripcion | Servidor Puede Leer |
|---|---|---|
| LOCAL_ONLY | Nunca sale del dispositivo bajo ninguna circunstancia | NO |
| SYNCED_E2E | Se sincroniza como blob cifrado E2E, servidor almacena pero no puede leer | NO |
| SYNCED_HASH | Se sincroniza como hash/blind index para busqueda, no reversible | NO (solo buscar) |
| SYNCED_PLAIN | Se sincroniza en texto claro, necesario para operacion | SI |
| SERVER_SOURCE | Datos publicos cuyo origen es el servidor, usados para construir catalogo local | SI (son publicos) |
| ANONYMIZED_DATA | Datos anonimizados para enriquecer catalogos publicos de MedTime | SI (anonimizado) |
4.2.1. Sobre ANONYMIZED_DATA¶
MedTime utiliza informacion anonimizada de los usuarios para enriquecer sus catalogos publicos, siguiendo un proceso de separacion de identidad. Ver INV-010: Anonimizacion de Medicamentos.
CONSENTIMIENTO OBLIGATORIO: Para usuarios con cuenta, el consentimiento para el tratamiento de datos anonimizados es requisito para usar la aplicacion. Este consentimiento se obtiene durante el onboarding y es explicito conforme a LFPDPPP. Ver INV-009: Consentimiento Datos de Salud.
Flujo de anonimizacion (Separacion de Identidad):
DATO ORIGINAL (en dispositivo):
+---------------------------+
| Medicamento: "Glucophage" |
| Dosis: "850mg" |
| Usuario: Juan Perez |
| Region: CDMX |
| user_id: U-12345 |
+---------------------------+
|
ANONIMIZACION LOCAL
(Separacion de identidad)
|
v
DATO ANONIMIZADO (enviado al servidor):
+-----------------------------+
| Medicamento: "Glucophage" | <- SI se envia medicamento especifico
| Dosis: "850mg" | <- SI se envia dosis
| Region: "Centro Mexico" | <- Generalizada
| Periodo: "2025-12" | <- Solo mes/año
| session_token: <efimero> | <- Token aleatorio no vinculado
+-----------------------------+
|
LO QUE SE ELIMINA:
- user_id, email, nombre
- device_id, IP
- Cualquier identificador
Principios de anonimizacion (Separacion de Identidad):
- Los datos NUNCA pueden asociarse a un usuario especifico
- Se eliminan TODOS los identificadores (user_id, email, device_id, IP)
- SI se envian medicamentos especificos (nombre, dosis) - solo se elimina la identidad
- Tokens de sesion efimeros y no correlacionables entre envios
- Region y timestamps generalizados
- Combinaciones muy raras pueden suprimirse por k-anonymity
Que se envia anonimizado para enriquecer catalogos:
| Dato Original | Como se Anonimiza | Uso |
|---|---|---|
| Medicamento especifico | Se envia nombre SIN vinculo a usuario | Enriquecer catalogo de medicamentos |
| Dosis | Se envia dosis SIN vinculo a usuario | Validar dosis estandar |
| Medicamento no encontrado | Se envia para descubrimiento | Expandir catalogo con nuevos meds |
| Combinaciones de meds | Sin correlacion temporal | Detectar interacciones |
Garantia de separacion de identidad:
| Lo que SI se envia | Lo que NUNCA se envia |
|---|---|
| Nombre de medicamento | user_id |
| Dosis | |
| Region generalizada | nombre del usuario |
| Periodo (mes/año) | device_id |
| Token efimero aleatorio | IP |
| timestamps exactos |
IMPORTANTE: La informacion anonimizada NUNCA afecta directamente los catalogos publicos. Los datos anonimizados se procesan por el equipo de MedTime para validacion manual antes de incorporar cualquier informacion a los catalogos oficiales.
5. Detalle por Clasificacion de Datos¶
5.1. LOCAL_ONLY - Nunca Sale del Dispositivo¶
DATOS LOCAL_ONLY:
+------------------------------------------------------------------+
| Estos datos JAMAS abandonan el dispositivo bajo ninguna |
| circunstancia, incluso con orden judicial. |
+------------------------------------------------------------------+
LISTA COMPLETA:
- Patrones de comportamiento detectados por ML
- Predicciones de omision de tomas
- Modelo de ML personalizado entrenado
- Historial de tiempos de respuesta a alertas
- Master key de cifrado
- Recovery key (solo usuario tiene copia fisica)
- Datos biometricos (gestionados por OS)
- Cache de imagenes de recetas originales
- Estadisticas detalladas de uso de app
Justificacion tecnica y legal:
- Datos de ML son inferencias, no datos proporcionados por usuario
- Claves criptograficas: compromiso de seguridad si salen
- LGPD/HIPAA: minimizacion de datos en servidor
5.2. SYNCED_E2E - Sincronizado como Blob Cifrado¶
PROCESO DE SINCRONIZACION E2E:
+------------------------------------------------------------------+
| |
| CLIENTE SERVIDOR |
| +--------+ +--------+ |
| | Datos | --[Cifrar E2E]--> | Blob | |
| | Claro | AES-256-GCM | Opaco | |
| +--------+ +--------+ |
| |
| Servidor almacena: |
| - blob: 0x7F8A9B2C... (indescifrable) |
| - hash: sha256:abc123... |
| - size: 2048 bytes |
| - updated_at: 2025-12-07T10:30:00Z |
| |
| Servidor NO sabe: |
| - Que medicamento es |
| - Nombre del usuario |
| - Ningun dato PHI |
| |
+------------------------------------------------------------------+
5.3. SYNCED_HASH - Sincronizado como Hash/Blind Index¶
BLIND INDEX PARA BUSQUEDA:
+------------------------------------------------------------------+
| Permite buscar sin revelar el valor original |
+------------------------------------------------------------------+
EJEMPLO - Email:
Original: "paciente@ejemplo.com"
Normalizado: "paciente@ejemplo.com" (lowercase, trim)
Blind Index: HMAC-SHA256(global_salt, normalizado) = "7f8a9b2c..."
BUSQUEDA:
1. Usuario intenta login con "Paciente@Ejemplo.com"
2. Cliente normaliza y calcula blind index
3. Servidor busca por blind_index = "7f8a9b2c..."
4. Servidor retorna blob cifrado (sin ver email real)
PROTECCIONES:
- global_salt rotado anualmente
- global_salt almacenado en HSM
- Rate limiting en busquedas
- Audit log de todas las consultas
5.4. SYNCED_PLAIN - Sincronizado en Claro¶
DATOS EN CLARO (justificados):
+------------------------------------------------------------------+
| Estos datos DEBEN estar en claro para operacion del sistema |
+------------------------------------------------------------------+
| Dato | Razon |
|-------------------|-----------------------------------------------|
| tier_suscripcion | Necesario para facturacion y limites de tier |
| fecha_registro | Auditoria, calculos de retencion |
| ultimo_acceso | Deteccion de cuentas inactivas |
| sync_version | Coordinacion de sincronizacion |
| device_id | Identificar dispositivos autorizados |
| idioma | Localizacion de emails transaccionales |
| zona_horaria | Calculos de timestamps |
NINGUNO de estos datos es PHI o PII sensible.
5.5. SERVER_SOURCE - Origen en Servidor, Usado para Catalogo Local¶
CATALOGOS PUBLICOS:
+------------------------------------------------------------------+
| Datos publicos disponibles para usuarios autenticados |
| No contienen informacion personal |
| Se usan para CONSTRUIR el catalogo local del usuario |
+------------------------------------------------------------------+
FLUJO:
1. Usuario busca medicamento en su catalogo local
2. Si no existe, busca en catalogo publico (requiere cuenta)
3. Si no existe en publico, usuario ingresa manualmente
4. Dato se guarda en catalogo LOCAL del usuario
NOTA: Los clientes NO cachean catalogos publicos directamente.
Construyen sus propios catalogos con datos publicos + manuales.
CATALOGOS DISPONIBLES:
- Medicamentos aprobados (FDA, COFEPRIS, ANVISA)
- Tratamientos estandar
- Interacciones medicamentosas
- Contraindicaciones
- Unidades de medida
- Formas farmaceuticas
- Vias de administracion
- Frecuencias de dosificacion
- Categorias terapeuticas
- Efectos secundarios
- Laboratorios
5.6. ANONYMIZED_DATA - Datos Anonimizados para Enriquecimiento¶
DATOS ANONIMIZADOS (Separacion de Identidad):
+------------------------------------------------------------------+
| Informacion de medicamentos que se envia SIN vinculo a usuario |
| para enriquecer los catalogos publicos de MedTime |
+------------------------------------------------------------------+
CONSENTIMIENTO:
- OBLIGATORIO para usuarios con cuenta
- Se obtiene durante el onboarding (explicito, conforme LFPDPPP)
- Requisito para usar la aplicacion con cuenta
- Ver INV-009: Consentimiento Datos de Salud
CARACTERISTICAS:
- Separacion de identidad: Se elimina TODO identificador
- Anonimizacion LOCAL: Se aplica ANTES de enviar
- Tokens efimeros: Cada envio tiene token diferente no correlacionable
- Region/Tiempo generalizados: Solo mes/año, solo region amplia
LO QUE SI SE ENVIA (anonimizado):
- Nombre especifico del medicamento (ej: "Glucophage")
- Dosis (ej: "850mg")
- Region generalizada (ej: "Centro Mexico")
- Periodo (ej: "2025-12")
- Token de sesion efimero aleatorio
LO QUE NUNCA SE ENVIA (eliminado):
- user_id
- email
- nombre del usuario
- device_id
- IP
- timestamps exactos
- CUALQUIER identificador directo o indirecto
PROCESO:
1. Usuario registra medicamento en su app
2. Al sincronizar, datos de medicamento se separan de identidad
3. Se genera token efimero aleatorio (no vinculado a usuario)
4. Servidor recibe: {medicamento, dosis, region_gen, periodo, token_random}
5. Servidor NO puede saber de quien proviene el dato
CRITICO: MedTime SI tiene acceso a los nombres especificos de medicamentos en forma anonimizada. La anonimizacion consiste en separar la identidad del usuario, NO en generalizar los medicamentos a categorias.
Ver detalles completos en INV-010: Anonimizacion de Medicamentos.
6. Flujo de Sincronizacion¶
6.1. Diagrama de Flujo de Sincronizacion¶
flowchart TD
A[Usuario modifica dato] --> B[Guardar en DB local]
B --> C[Cifrar con AES-256-GCM]
C --> D[Agregar a cola de sync]
D --> E{Hay conexion?}
E -->|No| F[Mantener en cola]
F -->|Reintentar periodicamente| E
E -->|Si| G[Enviar batch al servidor]
G --> H{Respuesta OK?}
H -->|Si| I[Marcar como sincronizado]
H -->|Conflicto| J[Resolver conflicto]
H -->|Error| K[Reintentar con backoff]
J --> L{Tipo de conflicto?}
L -->|Mismo campo| M[Mostrar UI de resolucion]
L -->|Campos diferentes| N[Auto-merge]
M --> O[Usuario elige version]
N --> I
O --> I
K --> E
I --> P[Actualizar sync_version local]
6.2. Proceso de Cifrado Antes de Envio¶
FLUJO DETALLADO DE CIFRADO:
+------------------------------------------------------------------+
1. DATOS EN CLARO (local):
{
"medication_name": "Metformina",
"dosage": "500mg",
"frequency": "2x dia",
"notes": "Tomar con alimentos"
}
2. SERIALIZAR:
JSON.stringify() -> "{"medication_name":"Metformina",...}"
3. OBTENER CLAVE:
master_key = Keychain.get("master_key")
4. GENERAR NONCE:
nonce = SecureRandom.generate(96 bits)
5. CIFRAR:
(ciphertext, tag) = AES-256-GCM.encrypt(
key: master_key,
nonce: nonce,
plaintext: serialized_data,
aad: entity_id // Datos adicionales autenticados
)
6. EMPAQUETAR:
encrypted_blob = nonce || ciphertext || tag
7. HASH PARA INTEGRIDAD:
blob_hash = SHA-256(encrypted_blob)
8. ENVIAR AL SERVIDOR:
{
"entity_id": "uuid",
"entity_type": "medication",
"encrypted_blob": base64(encrypted_blob),
"blob_hash": blob_hash,
"encryption_version": "1.0"
}
+------------------------------------------------------------------+
6.3. Estructura de Datos en Servidor¶
-- LO QUE EL SERVIDOR ALMACENA (ejemplo real)
SELECT
id,
user_id,
entity_type,
created_at,
updated_at,
sync_version,
encrypted_blob, -- Blob opaco, indescifrable
blob_hash,
blob_size_bytes,
encryption_version
FROM encrypted_user_data
WHERE user_id = 'user-uuid-123';
-- RESULTADO:
-- id: "med-uuid-456"
-- user_id: "user-uuid-123"
-- entity_type: "medication"
-- created_at: "2025-12-07 10:30:00+00"
-- updated_at: "2025-12-07 10:30:00+00"
-- sync_version: 1234567890
-- encrypted_blob: \x7f8a9b2c4d5e6f... (binario opaco)
-- blob_hash: "sha256:abc123def456..."
-- blob_size_bytes: 256
-- encryption_version: "1.0"
-- NOTA: No hay columna "medication_name", "dosage", etc.
-- Esos datos estan DENTRO del blob cifrado.
7. Lo que NUNCA Sale del Dispositivo¶
7.1. Lista Exhaustiva de Datos LOCAL_ONLY¶
CATEGORIA: CLAVES CRIPTOGRAFICAS
+------------------------------------------------------------------+
| Dato | Razon |
|-------------------------|----------------------------------------|
| master_key | Compromiso total si se filtra |
| derived_keys | Derivadas de master_key |
| recovery_key (original) | Solo usuario debe tener copia fisica |
| biometric_key | Gestionada por Secure Enclave/TEE |
| session_keys | Efimeras, solo para sesion actual |
+------------------------------------------------------------------+
CATEGORIA: MACHINE LEARNING
+------------------------------------------------------------------+
| Dato | Razon |
|-----------------------------|-------------------------------------|
| patrones_comportamiento | Inferencias, no datos del usuario |
| modelo_ml_personalizado | Entrenado on-device, privado |
| predicciones_adherencia | Calculo local, no PHI compartido |
| historial_tiempos_respuesta | Usado solo para ML local |
| scores_riesgo_omision | Calculo local |
+------------------------------------------------------------------+
CATEGORIA: DATOS TEMPORALES
+------------------------------------------------------------------+
| Dato | Razon |
|-----------------------------|-------------------------------------|
| imagenes_recetas_originales | Solo version anonimizada para OCR |
| cache_busquedas | Datos efimeros de sesion |
| estado_ui_temporal | Estado de la app, no persistido |
| logs_debug_locales | Solo para desarrollo, nunca prod |
+------------------------------------------------------------------+
CATEGORIA: CONFIGURACION SENSIBLE
+------------------------------------------------------------------+
| Dato | Razon |
|-----------------------------|-------------------------------------|
| pin_hash_local | Verificacion local solamente |
| intentos_fallidos_local | Contador de seguridad local |
| configuracion_biometria | Gestionada por OS |
+------------------------------------------------------------------+
7.2. Justificacion por Categoria¶
| Categoria | Justificacion Tecnica | Justificacion Legal |
|---|---|---|
| Claves | Si se filtran, todos los datos cifrados quedan comprometidos | N/A tecnico |
| ML | Son inferencias, no datos del usuario; no hay obligacion de compartir | LGPD Art. 20: derecho a explicacion, no a modelo |
| Temporales | Datos efimeros sin valor fuera de sesion | Minimizacion de datos |
| Configuracion | Datos de seguridad local, compartir aumenta superficie de ataque | Seguridad por diseno |
8. Lo que el Servidor VE vs NO VE¶
8.1. Tabla de Visibilidad del Servidor¶
+========================+=========+=========+=======================+
| DATO | LOCAL | SERVIDOR| QUE VE EL SERVIDOR |
+========================+=========+=========+=======================+
| Nombre: "Juan Perez" | Claro | E2E | 0x7f8a9b2c... (blob) |
| Email: "juan@mail.com" | Claro | Hash | "a1b2c3d4..." (HMAC) |
| Metformina 500mg | Claro | E2E | 0x4d5e6f... (blob) |
| Toma 08:00 confirmada | Claro | E2E | 0x1a2b3c... (blob) |
| Patron: "olvida sabados"| Claro | NUNCA | N/A |
| Tier: "Pro" | Claro | Claro | "Pro" |
| Ultimo acceso | Claro | Claro | "2025-12-07T10:30:00Z"|
+========================+=========+=========+=======================+
8.2. Ejemplo Practico: Que Ve un Admin de Base de Datos¶
ESCENARIO: Admin ejecuta query en produccion
+------------------------------------------------------------------+
QUERY:
SELECT * FROM users WHERE tier = 'Pro';
RESULTADO:
+----------+------------------+------+---------------------+--------+
| id | email_blind_idx | tier | created_at | ... |
+----------+------------------+------+---------------------+--------+
| uuid-123 | 7f8a9b2c4d5e6f.. | Pro | 2025-01-15 08:00:00 | ... |
| uuid-456 | a1b2c3d4e5f6a7.. | Pro | 2025-03-20 14:30:00 | ... |
+----------+------------------+------+---------------------+--------+
LO QUE EL ADMIN VE:
- IDs de usuario (UUIDs)
- Hashes de email (no reversibles)
- Tier de suscripcion
- Timestamps
LO QUE EL ADMIN NO VE:
- Emails reales
- Nombres
- Medicamentos
- Historial medico
- CUALQUIER dato PHI
QUERY PARA DATOS DE USUARIO:
SELECT encrypted_blob FROM encrypted_user_data WHERE user_id = 'uuid-123';
RESULTADO:
+------------------------------------------------------------------+
| encrypted_blob |
+------------------------------------------------------------------+
| \x7f8a9b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f |
+------------------------------------------------------------------+
El admin ve bytes aleatorios. Sin la master_key del usuario
(almacenada SOLO en el dispositivo del usuario), estos datos
son completamente indescifrables.
9. Arquitectura de Seguridad¶
9.1. Capas de Cifrado¶
MODELO DE CAPAS DE CIFRADO:
+------------------------------------------------------------------+
CAPA 4: TRANSPORTE
+----------------------------------+
| TLS 1.3 entre cliente y servidor |
| Certificate pinning |
+----------------------------------+
|
v
CAPA 3: APLICACION (E2E)
+----------------------------------+
| AES-256-GCM por usuario |
| Clave derivada de master_key |
| Servidor NO puede descifrar |
+----------------------------------+
|
v
CAPA 2: BASE DE DATOS
+----------------------------------+
| Cifrado transparente de disco |
| Managed by cloud provider |
+----------------------------------+
|
v
CAPA 1: ALMACENAMIENTO
+----------------------------------+
| Cifrado de disco fisico |
| HSM para claves de servidor |
+----------------------------------+
NOTA CLAVE:
- CAPA 3 (E2E) es la que importa para privacidad
- Si atacante compromete CAPAS 1-2, obtiene blobs opacos
- Solo comprometiendo dispositivo del usuario + su PIN/biometria
se pueden descifrar los datos
9.2. Gestion de Claves¶
JERARQUIA DE CLAVES:
+------------------------------------------------------------------+
USER SIDE (Dispositivo):
+----------------------------------+
| Recovery Key (24 palabras BIP39) | <-- Usuario guarda offline
| | |
| v |
| Master Key (256 bits) | <-- Derivada con Argon2id
| | |
| +---> Data Encryption Key | <-- Para cifrar datos PHI
| | |
| +---> Sync Key | <-- Para sincronizacion
| | |
| +---> Share Key | <-- Para compartir datos
+----------------------------------+
SERVER SIDE:
+----------------------------------+
| Global Salt (para blind indexes) | <-- En HSM, rotacion anual
| | |
| Auth Service Keys | <-- Firebase managed
| | |
| TLS Certificates | <-- Auto-renovacion
+----------------------------------+
IMPORTANTE:
- Servidor NUNCA tiene acceso a:
- Recovery Key
- Master Key
- Data Encryption Key
- Share Key
- Servidor SOLO tiene:
- Global Salt (para blind indexes)
- Tokens de sesion (JWT, efimeros)
9.3. Flujo de Cifrado E2E¶
sequenceDiagram
participant U as Usuario
participant K as Keychain
participant C as Crypto Engine
participant DB as Local DB
participant S as Servidor
Note over U,S: ESCRITURA (Cifrar antes de sync)
U->>DB: Crear/Modificar dato
DB->>K: Solicitar master_key
K->>K: Verificar biometria/PIN
K->>DB: master_key
DB->>C: Cifrar(data, master_key)
C->>C: nonce = random(96 bits)
C->>C: (ciphertext, tag) = AES-256-GCM(key, nonce, data)
C->>DB: encrypted_blob
DB->>S: POST /sync {encrypted_blob}
S->>S: Almacenar blob opaco
Note over U,S: LECTURA (Descifrar despues de pull)
S->>DB: GET /sync -> {encrypted_blob}
DB->>K: Solicitar master_key
K->>K: Verificar biometria/PIN
K->>DB: master_key
DB->>C: Descifrar(encrypted_blob, master_key)
C->>C: Extraer nonce, ciphertext, tag
C->>C: data = AES-256-GCM.decrypt(key, nonce, ciphertext, tag)
C->>DB: data (claro)
DB->>U: Mostrar datos
10. Implicaciones para Desarrollo¶
10.1. Reglas para Desarrolladores de Cliente¶
REGLAS OBLIGATORIAS - CLIENTE:
+------------------------------------------------------------------+
1. CIFRAR ANTES DE ENVIAR
- NUNCA enviar datos PHI en claro al servidor
- Usar CryptoService.encrypt() ANTES de cualquier sync
- Verificar que encrypted_blob no contiene datos legibles
2. NO LOGGING DE DATOS SENSIBLES
- NUNCA console.log() con datos PHI
- Usar sanitizeForLogging() en todos los logs
- En debug builds: mostrar "[REDACTED]" para PHI
3. ALMACENAMIENTO LOCAL
- Datos sensibles SOLO en DB cifrada por OS
- Claves SOLO en Keychain/Keystore
- NUNCA UserDefaults/SharedPreferences para PHI
4. ML ON-DEVICE
- Modelos NUNCA se envian al servidor
- Predicciones son LOCAL_ONLY
- Features son LOCAL_ONLY
5. VERIFICAR CLASIFICACION
- Antes de enviar cualquier dato, verificar tabla de clasificacion
- Si dato es LOCAL_ONLY: error fatal
- Si dato es SYNCED_E2E: cifrar primero
- Si dato es SYNCED_HASH: calcular blind index
+------------------------------------------------------------------+
10.2. Reglas para Desarrolladores de Backend¶
REGLAS OBLIGATORIAS - BACKEND:
+------------------------------------------------------------------+
1. BLOBS OPACOS
- NUNCA intentar descifrar encrypted_blob
- NUNCA parsear contenido de blobs
- Tratar blobs como BYTEA sin estructura
2. PostgreSQL con RLS
- USAR Row Level Security para autorizacion a nivel de DB
- RLS es dependencia aceptable de PostgreSQL
- Complementar con validacion en capa de aplicacion
3. NO LOGGING DE BLOBS
- NUNCA loggear contenido de encrypted_blob
- Solo loggear: user_id, entity_type, blob_size, timestamp
- Audit logs de metadata, NUNCA contenido
4. BLIND INDEXES
- Solo usar para busqueda, no para mostrar datos
- NUNCA exponer blind_index a usuarios
- Rate limiting en endpoints de busqueda
5. VERIFICAR AUTORIZACION
- Cada request: verificar que user_id del token == user_id de datos
- NUNCA retornar datos de otro usuario
- 403 Forbidden si no coincide
+------------------------------------------------------------------+
10.3. Checklist de Seguridad por Feature¶
CHECKLIST PARA CADA NUEVO FEATURE:
+------------------------------------------------------------------+
[ ] 1. CLASIFICACION DE DATOS
- Identificar todos los datos del feature
- Asignar clasificacion (LOCAL_ONLY, SYNCED_E2E, etc.)
- Documentar en PR
[ ] 2. CIFRADO
- Datos SYNCED_E2E: implementar cifrado en cliente
- Verificar que servidor no puede leer datos
- Test: enviar blob, verificar que es opaco en DB
[ ] 3. ALMACENAMIENTO LOCAL
- Datos sensibles en DB cifrada
- Claves en Keychain/Keystore
- NO en archivos planos
[ ] 4. LOGGING
- Sanitizar logs de datos PHI
- Verificar que no hay leaks en consola
- Test: revisar logs en escenario real
[ ] 5. SINCRONIZACION
- Verificar clasificacion antes de sync
- Manejar conflictos apropiadamente
- Test offline: feature funciona sin conexion
[ ] 6. CODE REVIEW
- Reviewer verifica clasificacion de datos
- Reviewer verifica cifrado implementado
- Reviewer verifica no hay leaks
+------------------------------------------------------------------+
11. Diferencias por Tier¶
11.1. Tier Free: Local por Defecto¶
TIER FREE:
+------------------------------------------------------------------+
| CARACTERISTICAS: |
| - 100% de datos almacenados localmente |
| - Sin sincronizacion con servidor |
| - Cuenta de usuario OPCIONAL |
| - Backup manual exportando archivo cifrado |
+------------------------------------------------------------------+
MODOS DE OPERACION FREE:
1. SIN CUENTA (100% offline):
- Medicamentos ingresados manualmente
- Sin acceso a catalogos publicos
- Sin posibilidad de ser paciente dependiente
- Sin posibilidad de ser cuidador
- Backup solo manual/local
2. CON CUENTA (hibrido):
- Acceso a catalogos publicos para buscar medicamentos
- Puede ser paciente dependiente (vinculado a cuidador Pro)
- Puede compartir datos con cuidadores Pro
- Sin sincronizacion de datos propios (Free no sincroniza)
- Sin OCR
ARQUITECTURA FREE (SIN CUENTA):
+=====================+
| DISPOSITIVO |
+=====================+
| [x] DB Local |
| [x] Cifrado local |
| [x] Alertas locales |
| [x] ML local |
| [x] Gamificacion |
| [x] Backup manual |
+=====================+
|
| SIN conexion a servidor
v
+=====================+
| SERVIDOR |
+=====================+
| [ ] Catalogos (NO) |
| [ ] Sync (NO) |
| [ ] Auth (NO) |
| [ ] OCR (NO) |
+=====================+
ARQUITECTURA FREE (CON CUENTA):
+=====================+
| DISPOSITIVO |
+=====================+
| [x] DB Local |
| [x] Cifrado local |
| [x] Alertas locales |
| [x] ML local |
| [x] Gamificacion |
| [x] Backup manual |
+=====================+
|
| Auth + Catalogos (solo lectura)
v
+=====================+
| SERVIDOR |
+=====================+
| [x] Catalogos |
| [x] Auth (Firebase) |
| [ ] Sync (NO) |
| [ ] OCR (NO) |
+=====================+
RESUMEN REQUISITOS DE CUENTA:
| Funcionalidad | Sin Cuenta | Con Cuenta Free |
|---------------|------------|-----------------|
| Usar app localmente | SI | SI |
| Catalogos publicos | NO | SI |
| Ser paciente dependiente | NO | SI |
| Vincular con cuidador | NO | SI |
| Sincronizar datos | NO | NO |
| OCR | NO | NO |
11.2. Tier Pro/Perfect: Hibrido¶
TIER PRO/PERFECT:
+------------------------------------------------------------------+
| CARACTERISTICAS: |
| - Todos los datos locales + sincronizacion cifrada |
| - Cuenta en servidor con blind indexes |
| - Backup automatico cifrado E2E |
| - Compartir con cuidadores (Pro) y medicos (Perfect) |
| - OCR de recetas (procesamiento servidor con anonimizacion) |
+------------------------------------------------------------------+
ARQUITECTURA PRO/PERFECT:
+=====================+ +=====================+
| DISPOSITIVO | | SERVIDOR |
+=====================+ +=====================+
| [x] DB Local | <----> | [x] Blobs E2E |
| [x] Cifrado E2E | sync | [x] Blind indexes |
| [x] Alertas locales | | [x] Metadata |
| [x] ML local | | [x] Firebase Auth |
| [x] Gamificacion | | [x] Catalogos |
| [x] Cola de sync | | [x] OCR (Perfect) |
+=====================+ +=====================+
DATOS EN SERVIDOR:
+-------------------+-------------------+-------------------+
| CLASIFICACION | EJEMPLO | SERVIDOR VE |
+-------------------+-------------------+-------------------+
| SYNCED_E2E | Medicamentos | Blob opaco |
| SYNCED_HASH | Email | Hash no reversible|
| SYNCED_PLAIN | Tier, timestamps | Valor real |
| SERVER_SOURCE | Catalogo meds | Datos publicos |
| LOCAL_ONLY | ML patterns | N/A (no sincroniza)|
+-------------------+-------------------+-------------------+
12. Diagrama de Despliegue¶
flowchart TB
subgraph USERS["Usuarios"]
direction LR
IOS["iOS Device<br/>iPhone/iPad"]
ANDROID["Android Device<br/>Phone/Tablet"]
WEB["Web Browser<br/>(Portal Medicos)"]
end
subgraph DEVICE_LAYER["Capa de Dispositivo (95% procesamiento)"]
direction TB
subgraph IOS_STACK["iOS Stack"]
IOS_APP["SwiftUI App"]
COREDATA["Core Data"]
KEYCHAIN["iOS Keychain"]
COREML["CoreML"]
end
subgraph ANDROID_STACK["Android Stack"]
ANDROID_APP["Compose App"]
ROOM["Room DB"]
KEYSTORE["Android Keystore"]
TFLITE["TensorFlow Lite"]
end
end
subgraph CLOUD["Cloud Infrastructure"]
direction TB
subgraph API["API Layer"]
GW["API Gateway<br/>(Cloud Functions)"]
AUTH["Firebase Auth"]
end
subgraph SERVICES["Services"]
SYNC["Sync Service<br/>(Node.js)"]
CATALOG["Catalog Service<br/>(Node.js)"]
OCR["OCR Service<br/>(Python + GPU)"]
end
subgraph DATA["Data Layer"]
PG["PostgreSQL<br/>(Blobs E2E)"]
REDIS["Redis<br/>(Cache)"]
end
subgraph EXTERNAL["External"]
FCM["FCM/APNs<br/>(Push)"]
DRUGBANK["DrugBank API"]
end
end
IOS --> IOS_STACK
ANDROID --> ANDROID_STACK
IOS_STACK -->|"Blobs E2E<br/>TLS 1.3"| GW
ANDROID_STACK -->|"Blobs E2E<br/>TLS 1.3"| GW
WEB -->|"Read-only<br/>TLS 1.3"| GW
GW --> AUTH
GW --> SYNC
GW --> CATALOG
GW --> OCR
SYNC --> PG
SYNC --> REDIS
CATALOG --> PG
CATALOG --> DRUGBANK
OCR --> PG
AUTH --> FCM
13. Referencias¶
13.1. Documentos Funcionales¶
13.2. Investigaciones¶
- INV-001: Cifrado E2E Zero Knowledge
- INV-008: Cifrado de Perfil e Identificacion
- INV-011: Auditoria Zero-Knowledge
13.3. Documentos Tecnicos¶
- 01-arquitectura-tecnica.md - Arquitectura general
- ADR-004: Database Selection
13.4. Estandares Externos¶
Documento generado por SpecQueen + ArchitectureDrone - IT-02 "En MedTime, tus datos son TUYOS. El servidor es solo un mensajero ciego."