npm run dev # Wrangler dev server
npm run typecheck # TypeScript check
npm test # Vitest (unit + integration)
npm run deploy # wrangler deploy
Cloudflare Workers (single worker). Sin assets estáticos: emaemi expone
sólo API. Custom domain emi.emahealth.io configurado.
POST /api/chat # Endpoint principal (auth + access + audit + agent)
GET /api/health # { status, service, version }
Set vía wrangler secret put <NOMBRE>. Agrupados por propósito:
| Secret | Para qué |
|---|
ANTHROPIC_API_KEY | Llamadas a Claude (requerido) |
| Secret | Producto |
|---|
EMAHEALTH_API_KEY | emahealth.io |
EMACLINIC_API_KEY | emaclinic |
EMALAB_API_KEY | emalab |
EMAVAULT_API_KEY | emavault |
EMAPATH_API_KEY | emapath |
EMAWELL_API_KEY | emawell |
| Secret | Para qué |
|---|
AIDBOX_BASE_URL | URL base del servidor Aidbox |
AIDBOX_SECRET_KEY_V1 | Master key AES-GCM (32 bytes base64) para decryptar credenciales per-tenant |
| Secret | Para qué |
|---|
EMASHARED_SUPABASE_URL | Proyecto emahub donde viven las tablas Wave 3 |
EMASHARED_SUPABASE_SERVICE_KEY | Service role para escribir audit log y leer activación |
| Secret | Para qué |
|---|
VAULT_SUPABASE_URL | Mismo proyecto emahub, distinto schema |
VAULT_SUPABASE_KEY | Service role para tools de AnalyticsAgent + lead upsert de SalesAgent |
FHIR_SERVER_URL, FHIR_CLIENT_ID, FHIR_CLIENT_SECRET, FHIR_TOKEN_URL.
Reemplazados por el patrón per-tenant via tenant_aidbox_credentials.
| Código | Causa |
|---|
400 | Body inválido o falta X-Tenant-Id requerido |
401 | API key inválida |
403 | App inactiva para el tenant / sin row en tenant_emi_access / rate limited |
501 | Agent no implementado (ej. WellnessCoach) |
503 | Error de servicio (Anthropic, Supabase, Aidbox) |
| Gotcha | Solución |
|---|
Wrangler secret put aplica al worker del directorio actual | Siempre cd emaemi/ antes de configurar secrets |
@anthropic-ai/sdk falla en CF Workers (bundling) | Usar fetch nativo + wrapper en lib/claude.ts |
| Hardcodear URLs en build time complica OpenNext en consumidores | Pasar URLs por env vars de runtime |
Frontend que llama directo a emaemi sin apiFetch no incluye token Supabase | Forzar uso de apiFetch para que el proxy del producto firme la request |
| Un audit fallido bloqueando la respuesta | logInvocation es fail-silent (F006); errors van al logger estructurado |
- Cloudflare Observability habilitado en
wrangler.toml.
- Logger estructurado JSON (
lib/logger.ts) para todas las
invocaciones, errors y warnings.
- Cost tracking USD por invocación en
emi_invocations.cost_usd.
Vitest. Estructura:
├── integration/ # gated por env vars (ver abajo)
npm test # unit + integration en una pasada
npm run test:integration # solo integration suite
Integration tests requieren:
TEST_API_URL (URL del worker en dev/staging)
TEST_TENANT_ID
TEST_EMASHARED_*
Si las env vars faltan, los integration tests se skippean
silenciosamente (CI nunca depende de servicios externos vivos).