Local-first: qué corre en el teléfono y qué en la nube
Por qué así: por-que-asi/arquitectura-local-first.md · Normativo:
ROADMAP_MAESTRO.md§0, §2.1, §2.2 y §2.2-bis · Código:lib/core/,lib/data/,lib/features/audio/,backend/Verificado contraf15a912el 2026-09-20 · Probado en iPhone: no
Qué es, en tres frases
Todo lo que importa clínicamente ocurre dentro del teléfono: la síntesis del sonido de las cuatro terapias, el perfil, el historial y los cuestionarios. La app se instala, se configura y entrega doce semanas de terapia sin red y sin haber aceptado nada. El servidor es un accesorio asíncrono: recibe telemetría si el paciente consintió, sirve el catálogo de música y sostiene el panel del terapeuta — pero si está caído, nadie se entera.
El reparto
| En el teléfono | En la nube (Django) |
|---|---|
| Síntesis del estímulo de las cuatro terapias | Telemetría de uso y adherencia |
| Perfil de tinnitus completo | Panel clínico: qué usó cada paciente, cuánto, series |
| Historial de sesiones y bitácora de uso | Catálogo de música curada (descarga única, cacheada) |
| Cuestionarios (THI, GÜF, Goldberg, EVA) | Configuración remota de parámetros / versión de spec |
| Reproducción y dosificación | Datos para investigación, agregados y sin UUID |
Ninguna pantalla queda bloqueada esperando red. Todo request es opcional y reintentable, y la cola de telemetría se drena cuando hay conexión.
Sin cuentas ni login
No hay registro, no hay correo, no hay contraseña. La identidad es un UUID seudónimo generado en el
primer arranque y guardado en el teléfono (PerfilTinnitus.idLocal). Solo al vincularse con un
equipo clínico —canjeando un código que entrega el médico— se crea una cuenta del lado del servidor.
Qué sale del teléfono, y con qué permiso
Sin consentimiento no sale un byte. Los tres consentimientos son opt-in, están apagados por defecto y se pueden rechazar sin perder ninguna función:
| Qué permite | Cuándo se pide | |
|---|---|---|
| C1 | Telemetría seudónima al centro del proyecto, ligada al UUID, sin correo ni bóveda de identidad | Primer arranque |
| C2 | El equipo clínico ve los datos del paciente; crea la cuenta | Al canjear el código del centro |
| C3 | Uso en investigación, agregado y sin UUID, solo en estudios con comité ético-científico | Primer arranque; requiere C1 o C2 |
La cola de telemetría no drena nada sin C1 o C2 vigente, y el servidor lo vuelve a comprobar del otro lado. La única petición que sale sin consentimiento vigente es el borrado.
Dónde se guardan las cosas
- Perfil y ajustes: JSON en
shared_preferences. Hoy el esquema va en la versión 6, con reglas de migración escritas para cada salto. - Bitácora de uso: archivo JSONL en el área de datos de la app.
- Audio descargado: caché en disco por
sha256. - No hay base de datos en el teléfono:
drift/sqlite se evaluó y no se instaló (ver la capa 2).
Cómo está partido el código
lib/
core/ theme · router · config · api_client · storage
domain/ modelos puros y reglas: PerfilTinnitus, Terapia, Sesion, Tinnitumetria
data/ repositorios: local (fuente de verdad) + remoto, con cola de subida
features/ una carpeta por pestaña, más audio/ y laboratorio/
Regla dura: features/audio/ no importa nada de UI ni de red. El motor se puede probar solo, y se
prueba: existe un arnés que compara muestra a muestra el estímulo generado en Dart contra el del
oráculo Python de referencia (tools/oracle/mmidst_reference.py, v3.3). Coinciden al último bit
(diferencia de 1,110e-16, un ulp).
El backend no se botó: además de panel y catálogo, es el renderizador de referencia contra el que se valida el motor Dart.
Lo que NO hace, a propósito
- No pide cuenta, correo ni contraseña para funcionar.
- No manda el audio por la red: generar un estímulo en el teléfono cuesta casi nada; servirlo son ~51 MB por estímulo.
- No bloquea nada por falta de señal.
- No sincroniza el perfil «por si acaso»: sin consentimiento, el perfil no existe fuera del teléfono.
Las pantallas de laboratorio
Las dos pantallas de los primeros hitos siguen en el repo tras un flag de depuración
(kLaboratorioHabilitado), porque son parte del arnés de validación. Son el único código que
todavía le pide audio al servidor (POST /api/render/), y no aparecen en una build de release. Desde
H6.2 esa API no responde a anónimos: para usarla en el Mac hay que levantar el servidor con
MINDTUNE_API_ABIERTA_EN_DESARROLLO=true, que solo funciona con DEBUG=True y que check bloquea en
producción.