Arquitectura
Arquitectura de componentes
Sección titulada «Arquitectura de componentes»Estructura de carpetas
Sección titulada «Estructura de carpetas»emaemi/├── src/│ ├── index.ts # Hono app, CORS, /api/health│ ├── env.d.ts # Cloudflare bindings (Wave 3 surface)│ ├── api/│ │ └── chat.ts # POST /api/chat — access + audit + agent│ ├── orchestrator/│ │ └── router.ts # AppName → AgentName│ ├── agents/│ │ ├── base.ts # BaseAgent (tool loop + usage agg + enrichResponse)│ │ ├── sales.ts # emahealth — lead_management│ │ ├── support.ts # emaclinic — Aidbox tools per-tenant│ │ ├── analytics.ts # emavault — Vault Supabase│ │ ├── lab.ts # emalab — alpha placeholder│ │ └── pathology.ts # emapath — planned placeholder│ ├── middleware/│ │ └── auth.ts # API key + X-Tenant-Id│ ├── lib/│ │ ├── claude.ts # Anthropic API client + runAgent loop│ │ ├── types.ts # AppName, AgentName, ChatContext, AgentResponse│ │ ├── permissions.ts # App → agents/tools scoping│ │ ├── access-check.ts # tenant_emi_access + rate limit│ │ ├── audit.ts # logInvocation + summarizeContext (privacy-safe)│ │ ├── cost.ts # computeCost per model│ │ ├── aidbox-credentials.ts # AES-GCM decrypt + cache TTL 5 min│ │ ├── fhir-client.ts # ORGBAC-scoped fhirRequest/fhirQuery│ │ └── logger.ts # JSON structured logger│ └── tools/│ ├── clinic.ts # FHIR queries (per-tenant)│ ├── vault.ts # Supabase Vault queries│ └── lead-management.ts # CRM upsert + activity log└── tests/Flujo de una request (secuencia)
Sección titulada «Flujo de una request (secuencia)»Mapeo App → Agent
Sección titulada «Mapeo App → Agent»Tabla en permissions.ts → APP_SCOPES:
| AppName | Agente | Status | Tools | Tenant requerido |
|---|---|---|---|---|
emahealth | sales | GA | lead_management | no |
emaclinic | support | GA | aidbox per-tenant | sí |
emavault | analytics | GA | supabase-vault | no |
emalab | lab_assistant | alpha | — | sí |
emapath | pathology_assistant | planned | — | sí |
emawell | wellness_coach | 501 | — | sí |
emawell retorna 501 Not Implemented explícito hasta que se construya
el agente. emahealth y emavault no requieren tenant: el primero
recibe visitantes anónimos del sitio público (con tracking opcional via
ChatContext), el segundo es dashboard interno sin multi-tenancy.
Patrones clave
Sección titulada «Patrones clave»- Activación per-tenant:
tenant_emi_accesscontrola qué apps puede invocar cada tenant. Sin row activa → 403. - Rate limit:
daily_invocation_limitpor app por tenant; cuenta invocaciones exitosas del día (UTC). - Audit obligatorio:
logInvocationcorre siempre (success y errors) y es fail-silent — no bloquea respuestas al usuario. - Multi-tenancy FHIR: cada tenant tiene credenciales Aidbox propias
encriptadas con AES-GCM en
tenant_aidbox_credentials. Decryptadas runtime con cache TTL 5 min in-memory por Worker isolate. - Sin persistencia de conversaciones: stateless. Historial viene en el body de cada request.