EONIA — Una app que no recomienda: decide
La capa de decisión biológica que faltaba entre medir e intervenir · Producto propio · Salud · Local-first + IA
- Cliente
- Producto propio de RBT Studio
- Rol
- Producto, diseño de sistema y desarrollo full-stack + IA — autor único
- Periodo
- Junio 2026 → en curso (MVP v1.0 + incremento servidor v1.5)
- Stack
- TypeScript, React Native · Expo, Skia + Reanimated, NestJS, PostgreSQL · pgvector, LangGraph + Claude, Render · EAS
El usuario de alto rendimiento tiene más datos biológicos que ninguna generación anterior — y sigue tomando los mismos suplementos cada mañana, dé igual cómo esté hoy. El mercado resolvió la medición (Oura, WHOOP) y la intervención (suplementación basada en evidencia), y dejó el centro vacío: la decisión. Un sistema que recomienda traslada la carga cognitiva al usuario; uno que decide la absorbe. EONIA convierte un check-in de 30 segundos en un estado biológico calculado y una arquitectura de cápsulas adaptada a ese estado, en el dispositivo.
El problema: El hueco está en la decisión, no en el dato
Medición e intervención están bien resueltas por el mercado; entre ambas no hay nada. La diferencia no es incremental sino estructural: el usuario de EONIA no necesita entender el eje HPA para beneficiarse de él. Cada pantalla responde a una sola pregunta — qué tengo que hacer ahora mismo — sin gráficas y sin jerga por delante.
Cómo se abordó
- Especificar antes de programar, y decidir por escrito. El proyecto arrancó con un set documental completo (SPEC · ARCHITECTURE · SCAFFOLD · DESIGN · HANDOFF) antes de la primera línea de producto, y cada decisión estructural quedó fijada como un ADR con su alternativa descartada. No es burocracia: en un producto que toca salud, la trazabilidad de por qué el sistema decide lo que decide es parte del producto.
- El motor de decisión es puro y determinista. @eonia/engine no tiene I/O, ni React, ni red, ni modelo de lenguaje. Entra un check-in, sale un estado y una arquitectura. Es el único sitio donde se decide. Consecuencia deliberada: el mismo paquete corre en el dispositivo y en el servidor y produce exactamente el mismo veredicto — lo que hace que el modo offline no sea una versión degradada, sino el mismo sistema.
- El Orb es una entidad, no una gráfica. La visualización central (Skia + Reanimated, trazo de pincel sumi-e) tiene que comunicar el estado biológico pre-cognitivamente: en menos de un segundo, sin leer ni interpretar. Un donut chart habría sido más barato y habría fallado en el único requisito que importaba. Las tres capas de información —qué hacer, por qué, y el mecanismo fisiológico— son un límite arquitectónico impuesto en el código, no una convención de diseño.
- Dónde entra la IA. La capa narrativa genera el informe personalizado con LangGraph y Claude, pero bajo una restricción dura: el motor decide, el LLM solo explica. El nodo de preparación es código puro —normalización, aritmética de tendencias, resolución de etiquetas— y el modelo recibe un contexto grounded del que no puede salir. Tres especialistas analizan en paralelo, un nodo sintetiza y una puerta de seguridad decide entre entregar o escalar. Una alucinación en el nodo de normalización se habría propagado a todas las ramas; por eso ese nodo no es una llamada al modelo.
- El catálogo clínico tenía que salir del código. Las fórmulas vivían como constantes en el motor, lo que implicaba que actualizar un protocolo era un despliegue. Se movieron a base de datos y se construyó una consola de back-office con tres roles no jerárquicos: admin, specialist y provider. Editar el catálogo invalida la firma clínica y sube la versión, y la clave de caché de los informes incluye esa versión. Es la única vía por la que un farmacéutico —no un commit— puede firmar un protocolo.
- Un incidente que merece estar en el case study. Con la beta ya en manos de testers, todos los usuarios quedaron bloqueados tras verificar su email. La causa: force_organization_selection activado en el proveedor de identidad. Cada sesión recibía una tarea de «elegir organización» que un usuario B2C nunca puede satisfacer, la sesión se quedaba pending, el cliente la contaba igualmente y el modo de sesión única rechazaba cualquier login nuevo. Se arregló en dos frentes: la configuración y el cliente, porque una app en producción no puede depender de que la consola de un tercero esté bien configurada.
check-in → protocolo: Check-in · 5 dimensiones → Baseline individual → @eonia/engine · puro → 6 estados biológicos → Histéresis 3 días → 6 arquitecturas · circadiano → LangGraph · narrativa grounded → Gate de seguridad → Sync opcional · Postgres
Solución: Un bucle cerrado de señal, decisión y ejecución
Check-in de 5 dimensiones en ~30 segundos → EONIA Score contra tu baseline personal → uno de 6 estados biológicos → una de 6 arquitecturas de cápsulas con ventanas circadianas por compuesto. La arquitectura solo cambia tras 3 días coherentes seguidos: histéresis deliberada contra la oscilación por ruido. El catálogo clínico vive en base de datos con firma profesional y versionado, no en el código.
Decisiones de producto
- mi rol: Producto, diseño de sistema y desarrollo full-stack + IA — autor único
- arquitectura clave: Motor puro y determinista — sin I/O, sin red, sin modelo de lenguaje. El mismo paquete corre en dispositivo y servidor y produce el mismo veredicto: el modo offline no es una versión degradada.
- decisión de IA clave: El motor decide, el LLM solo explica. La capa narrativa recibe un contexto grounded del que no puede salir — no deriva un estado, ni una arquitectura, ni un número propio.
- estado: MVP v1.0 desplegado (Render + APK vía EAS) e incremento de servidor v1.5 en producción. Beta cerrada.
Resultados
Sin métricas de usuarios todavía — la beta es cerrada y no se han publicado cifras. Lo que sí es verificable: MVP v1.0 completo y desplegado, con backend dockerizado en Render vía Blueprint y APK Android distribuida a testers vía EAS. Incremento de servidor v1.5 en producción, con sync opcional autenticado, informes narrativos y consola de back-office con control de acceso por rol. Y un catálogo clínico editable con firma: los protocolos ya no son código, y la firma clínica es un estado del dato que solo un profesional puede establecer.
- 35 — tests sobre el núcleo de decisión, con integración multi-día
- 5 — workspaces con frontera clara: engine, mobile, backend, admin, narrative
- 46 — commits desde el kickoff, con el set SDD sincronizado con el código
"Poner el LLM en el sitio equivocado es fácil y caro. Restringirlo a explicar una decisión ya tomada por código verificable es lo que hace el sistema auditable — y lo que permite que el modo offline no pierda nada esencial."
Qué se aprendió
- El ADR que más valor dio no fue técnico. Reconciliar un backend multitenant heredado con un producto B2C no resolvió un problema de código: resolvió si el producto era B2C o B2B2C, e impidió que el backend arrastrase el producto.
- Lo que un profesional deba poder cambiar no puede vivir en el código. Mover el catálogo clínico a base de datos con firma y versionado convirtió una app en un sistema operable — un farmacéutico firma un protocolo, no un commit.
- La configuración de un tercero es superficie de producción. Un flag mal puesto en el proveedor de identidad bloqueó al 100% de los testers tras verificar el email. Se arregló en la configuración y en el cliente: una app en producción no puede depender de que la consola de un tercero esté bien.
Demo y beta bajo petición.