01Lo que hay hoy
inventario honestoMedi no parte de cero — y eso es la mejor noticia de esta pieza. Hoy conviven un panel funcional con problemas de traje, y un backend correcto en sus decisiones de seguridad pero artesanal en su runtime.
Funciona bien, viste mal
- Docked / flotante / fullscreen móvil con
⌘J, resize, focus trap y scroll-lock — la ingeniería del panel se queda. - El avatar mezcla
brand-600 → violet-600: el violeta no existe en la paleta Amedi. Es el hex ajeno clásico. - Cards de sugerencia shadcn de fábrica, animaciones
animate-ingenéricas, typing dots iguales a cualquier chat. - Nada en el panel dice «Amedi»: ni la noche, ni la aurora, ni el ECG del isotipo de salud.
Seguro en el diseño, artesanal en el runtime
- ~40 acciones en un registry ya gateado por rol (schedule, doctor, admin, user) — esta capa es oro y sobrevive entera.
- Mutaciones con flujo de confirmación explícito (
PendingActionsBar) — la doctrina correcta, se conserva. - Provider factory +
gemini.provider+ mock: un solo modelo, cero fallback. Este cableado sale entero — no se migra, se retira. prompt.builder+conversation.managerhechos a mano: memoria efímera, sin durabilidad, sin schedules.
No es un rewrite: es un trasplante de traje y de cerebro sobre el mismo esqueleto. El panel conserva su ingeniería (portal, posturas, atajos); el backend conserva sus acciones y su gate de confirmación. Cambia lo que se ve (aurora clínica) y lo que piensa (eve + OpenRouter).
02El panel, rediseñado
aurora clínicaLa dirección elegida: corona noche con la aurora respirando — la misma escena del login del admin y de aurora auth — y el cuerpo de la conversación en blanco clínico, porque las respuestas largas se leen sobre claro. El violeta muere: el avatar de Medi es el isotipo real de Amedi (la cruz con la venda, vector del manual) en pequeño, y el ECG queda reservado para el loader — la línea que late cuando piensa.
Tu agenda de hoy, tus pacientes y tus números — pregúntame lo que necesites.
Medi puede equivocarse — las acciones siempre te piden confirmación.
Anatomía de la corona: la barra (el isotipo pequeño como avatar + nombre, sin
pill de ruta — Medi es uno solo, esté donde esté) y el saludo comparten la escena noche con
dos glows que respiran a 7s/9s. El ECG plano bajo el saludo es la línea en reposo — cuando Medi piensa,
esa misma línea late. El pie fija la expectativa honesta: Medi propone, tú confirmas.
El jueves 7 tienes consulta hasta las 4:30 pm. Puedo abrir un cupo presencial de 5:00 a 5:30 pm en tu consultorio de Las Mercedes. Revisa y confirma:
- Día
- jueves 7 · ago 2026
- Horario
- 5:00 – 5:30 pm
- Modalidad
- presencial · Las Mercedes
Medi puede equivocarse — las acciones siempre te piden confirmación.
La card de acción es el contrato de seguridad hecho componente: resumen
factual con cifras tabulares (día, horario, modalidad), la etiqueta «requiere tu confirmación»
en petroleo, y el botón que nombra el resultado — «Abrir el cupo», nunca «Aceptar». En
conversación la corona se pliega a barra: la noche queda como techo fino y la lectura sucede
sobre blanco. Los mensajes de Medi van sin burbuja — texto sobre lienzo con el avatar
mínimo — y los del doctor en burbuja --paper alineada a la derecha.
Acoplado: el contenido reflowea con --medi-panel-width.
Acoplado — columna a la derecha, el consultorio reflowea con la variable
--medi-panel-width que ya existe. El rail noche del sidebar y la corona de Medi
comparten escena: la aurora une los dos extremos de la pantalla.
Esta semana llevas $540 cobrados en 9 consultas; 2 pagos siguen pendientes.
Flotante — lámina --r-lg con la sombra grande, para
consultar sin ceder el ancho de la vista.
El botón flotante abre esta vista completa.
Móvil — fullscreen con la corona intacta; el scroll-lock y el focus trap actuales no se tocan.
03Pruébalo: dos tools en vivo
demos interactivosLas dos capacidades que Medi ya tiene, contadas con el lenguaje nuevo. Son simulaciones guionadas — mismo flujo, mismos estados, cero backend. Toca la sugerencia y mira el panel pensar, proponer y ejecutar. La visual de la derecha reacciona igual que lo haría la agenda real.
Hola, Dra. Rivas — dime qué hacemos con tu agenda.
Simulación — no toca ninguna agenda real.
El tool agenda.bloquear es nivel 2: Medi puede
proponerlo, pero el bloqueo solo sucede cuando la doctora toca «Bloquear los viernes».
Al confirmar, la columna del viernes se apaga en cascada — la agenda reacciona en el
mismo gesto.
Buenos días — ¿revisamos tu miércoles?
Simulación — datos de ejemplo.
El tool dia.resumen es nivel 1: lectura pura, sin
confirmación. Medi narra el día y la línea de tiempo pinta cada cita en cascada — y
señala el hueco de 10:30 como oportunidad, no como reproche.
La firma de motion · tres gestos, tokens de la casa
El ECG que piensa
Reemplaza los typing dots: la línea de vida se dibuja en 1.5s con
--ease-out y se repite mientras Medi razona. Es el isotipo de salud
convertido en estado del sistema.
La aurora que late
Los glows de la corona respiran a 7s/9s en reposo y aceleran a
~3s durante el streaming — el panel «respira más rápido» cuando trabaja.
Escala y opacidad, nunca color nuevo.
La cascada
Sugerencias y cards entran con --duration-slow + --ease-out,
escalonadas 80ms. Las celdas del calendario al confirmar usan
--ease-spring — el único rebote permitido.
Con prefers-reduced-motion todo esto colapsa a estados finales: el ECG
aparece dibujado, la aurora queda quieta, cascada y streaming se muestran completos al
instante. Ya es ley en el panel actual y la pieza lo respeta — pruébalo activando la
preferencia del sistema.
04El cerebro: eve + OpenRouter
arquitecturaeve es un framework de agentes durables donde el agente es un
directorio: instrucciones, tools, conexiones y schedules son archivos versionados en el
repo. Lo que hoy son cuatro clases artesanales de NestJS pasa a ser contenido declarativo —
revisable en PR, testeable por archivo. OpenRouter entra como gateway de
modelos: un solo SDK, modelo pinneado por config y fallback automático si el proveedor
primario se cae.
Qué no cambia: el controller de /ai/chat sigue siendo
NestJS con los mismos guards JWT; el panel sigue hablando el mismo contrato de streaming.
eve corre detrás del API — nunca expuesto directo al navegador.
Esta es la naturaleza de eve: Medi deja de ser un asistente cosido a cada ruta y pasa a ser un agente independiente y aislado que sirve consultorio y admin por igual. Los knowledge packs por ruta mueren — lo que decide qué puede hacer Medi no es la página donde está, sino el rol del context sellado (doctor ve sus tools, admin los suyos). La ruta viaja apenas como hint opcional para sugerir mejor, nunca como cerebro. Un solo directorio, una sola memoria durable, permisos por rol.
«Bloquea mis viernes por la tarde» viaja con el JWT de la doctora. El guard resuelve el perfil y su rol — nada del cliente decide permisos.
El API invoca al agente con el mensaje y un context sellado: doctorId, rol, locale, ruta. El agente solo ve los tools que el rol permite.
Razona con el modelo pinneado (fallback automático si el primario cae). Sale el mínimo necesario: instrucciones, mensaje, schemas de tools. Nunca la base de datos.
agenda.ver (nivel 1) se ejecuta directo: consulta Prisma scopeada por el doctorId del JWT — el modelo jamás pasa ids de otros doctores.
agenda.bloquear (nivel 2) no se ejecuta: vuelve como acción pendiente y se pinta la card de confirmación. El gate de hoy, intacto.
La doctora toca «Bloquear los viernes». La confirmación viaja con su JWT y el id de la acción — no con los argumentos del modelo: el servidor re-valida todo.
El tool muta vía Prisma, deja rastro en el audit log (actor, tool, args, resultado) y el panel celebra: card en verde éxito y la agenda actualizada.
Un gateway, cero lock-in
- OpenRouter es el único provider. El cableado Gemini se retira completo — no queda como fallback: el fallback vive entre modelos dentro de OpenRouter.
- Se cablea en
agent.tsvíadefineAgentcomo LanguageModel del AI SDK (@openrouter/ai-sdk-provider) — modelo pinneado por env (MEDI_MODEL), fallback declarado, ydefineDynamicpara A/B de modelos por sesión. - Allowlist de proveedores con retención cero de datos: solo providers que no entrenan ni almacenan con nuestros prompts.
- Presupuesto y rate limit por doctor/día — un tope conocido, no una sorpresa en la factura.
- Cambiar de modelo = cambiar config, no código. A/B de modelos por flag.
Durabilidad y proactividad
- Memoria durable: las sesiones del runtime sobreviven deploys y reinicios, con compaction automática al acercarse a la ventana — hoy la conversación vive en un manager en memoria.
- El gate es nativo: cada tool declara su política de aprobación
(
always/once/neverdeeve/tools/approval) y la llamada gateada pausa y reanuda durable — los niveles 2 y 3 no se construyen, se declaran. - Evals de primera clase: la prueba de tool-calling en español que elige el
modelo se escribe en
evals/, junto al agente, y corre en CI. - Schedules: el resumen de «tu día» cada mañana como archivo YAML, opt-in del doctor — la sugerencia proactiva del panel deja de ser un placeholder.
- Skills nativas: procedimientos en
skills/que el modelo carga bajo demanda (load_skill) cuando el pedido lo amerita — el playbook de planear la agenda (ver → proponer → confirmar, jamás borrar cupos con citas) viaja solo cuando se necesita, no en cada turno. Una skill agrega instrucciones, nunca permisos: los tools visibles los decide el rol. - Tools = archivos: cada tool se revisa en PR y se testea aislado, igual que las actions de hoy pero sin el boilerplate del registry.
- El chat streaming del panel no cambia de contrato: la migración es invisible para el frontend.
05Seguridad y permisos
doctrina, no apéndiceMedi lo van a usar doctores sobre datos de salud. La regla madre es una sola: el modelo propone, el servidor decide. Todo lo demás — niveles, matriz, anti-fuga — son consecuencias de esa regla.
Leer
Consultas sin efectos: agenda.ver, dia.resumen,
pagos.resumen. Se ejecutan directo, siempre scopeadas al doctor del JWT.
Devuelven agregados y slots — nunca historias clínicas.
Mutar, con gate
Crean o cambian agenda: agenda.crear, agenda.bloquear,
agenda.semana. Vuelven como card de confirmación con resumen factual;
solo el toque del doctor las ejecuta.
Destruir, reforzado
agenda.borrar y lo que elimine datos: card en rojo derivado, el resumen
expandido por defecto y el botón nombra la pérdida — «Borrar 8 cupos del viernes».
Si hay citas de pacientes dentro, Medi se niega y ofrece reagendar.
| Tool | Nivel | Doctor · mvp | Secretaria · prevista | Admin · prevista |
|---|---|---|---|---|
dia.resumen | 1 · lee | ✓ directo | ✓ del doctor que asiste | — |
agenda.ver | 1 · lee | ✓ directo | ✓ del doctor que asiste | — |
pagos.resumen | 1 · lee | ✓ directo | — sin montos | — |
agenda.crear / agenda.semana | 2 · muta | ✓ con confirmación | ✓ confirma el doctor | — |
agenda.bloquear | 2 · muta | ✓ con confirmación | ✓ confirma el doctor | — |
agenda.borrar | 3 · destruye | ✓ reforzado | — | — |
plataforma.conteos | 1 · lee | — | — | ✓ agregados, sin PHI |
La lectura clave de la matriz: la secretaria nunca ejecuta una mutación sola — Medi le prepara la acción y la confirmación viaja al doctor (el mismo patrón de la sala de espera). El admin ve números de plataforma, jamás datos clínicos de un paciente en un chat. Crecer de rol = activar columnas ya diseñadas, no rediseñar.
El scope vive en el servidor
- Cada tool recibe
doctorIddel JWT del request — jamás de los argumentos que genera el modelo. - El modelo nunca toca Prisma: los tools devuelven agregados y slots tipados, la base no entra al contexto.
- Hacia OpenRouter viaja lo mínimo: agenda y conteos en el MVP. Sin historias clínicas en el prompt mientras no haya una decisión explícita de PHI.
- Audit log por tool call: actor, tool, argumentos, resultado, timestamp — la agenda de un doctor es dato sensible.
- Kill switch conocido:
MEDI_ENABLEDya existe y se conserva; apagar Medi nunca rompe el consultorio.
Confianza que se ve
- La card de acción muestra exactamente lo que va a pasar, con cifras tabulares — el resumen es el contrato.
- El botón nombra el resultado («Bloquear los viernes», «Borrar 8 cupos») — nunca un «Aceptar» ambiguo.
- El pie del panel dice la verdad siempre visible: «Medi puede equivocarse — las acciones siempre te piden confirmación».
- Deshacer donde se pueda: el bloqueo recién aplicado ofrece «Deshacer» durante unos segundos antes de asentarse.
06La receta, por fases
nada breakingTres fases independientes — cada una entrega valor sola y ninguna rompe la anterior. El traje no espera al cerebro.
El traje
Restyle del panel en components/ai-panel/: muere el violeta, entra la
corona aurora, el ECG pensante y la cascada. Solo frontend — el backend actual sigue
sirviendo igual. Es la fase que el doctor nota al día siguiente.
El cerebro
El agente eve nace en apps/api/agents/medi/ detrás de
MEDI_RUNTIME=eve. Mismas rutas, mismo contrato de streaming; las actions
se portan a tools con sus niveles. Conmutable por flag — A/B contra el legacy, rollback
en un env var.
La poda
Con eve estable: sale el cableado Gemini entero (factory, provider, mock) junto a
prompt builder y conversation manager; la memoria pasa a ser durable y se enciende el
primer schedule (resumen matinal, opt-in). La poda es amplia por diseño — del módulo
ai/ sobreviven guards, controller y la lógica de las actions hecha tools.
El modelo primario en OpenRouter y su fallback se eligen en fase 2 con una prueba real sobre los tools de agenda (tool-calling fiable en español > benchmark genérico). La pieza no pinnea un modelo: pinnea la política — retención cero, fallback declarado, presupuesto por doctor.
El mismo Medi que ya trabaja — con la marca puesta y un cerebro que no se apaga.
El panel conserva toda su ingeniería y gana la aurora, el ECG y la cascada. El backend conserva sus acciones y su gate — y gana durabilidad, multi-modelo y proactividad. La seguridad no se negocia en ninguna fase: el modelo propone, el servidor decide, el doctor confirma.
Dirección aurora clínica del panel y la arquitectura eve + OpenRouter con su matriz de permisos.
El traje: restyle del panel, solo frontend — visible en días.
El cerebro: agente eve tras flag conmutable, mismas rutas.
La poda del runtime artesanal y el primer schedule proactivo.