Vault se despliega como worker único de Cloudflare con dos
backends de Supabase como dependencias —el principal de emahub y
el de solo lectura sobre emawell— más cuatro servicios externos
controlados (Anthropic, GitHub, Stripe, Loops). La operación es
simple por diseño: los deploys son manuales, no hay pipeline de CI
configurada y la sincronización entre cambios de schema y código
del worker se gestiona explícitamente con migraciones numeradas. La
audiencia es interna y el producto está en producción continua;
las ventanas de despliegue se eligen para tener al equipo
disponible si algo falla.
Entorno URL Notas Dev local http://localhost:5173 (SPA) + :8787 (Worker)Vite proxy Producción vault.emahealth.ioCF Worker (no Pages)
Vault es single-tenant — un solo dominio, un solo deploy. No hay
preview branches.
cd /home/cristianruiz/Dev/emahealth/emavault
npm run dev # Vite SPA :5173 + proxy /api → :8787
npm run dev:worker # Wrangler dev (Hono) :8787
npm run build # Vite build SPA + worker
npm run deploy # build + wrangler deploy
npm run typecheck # tsc --noEmit
npm run guards # Wave 3 regression guards (scripts/guards.sh)
npm run test # Vitest run (todas las suites)
npm run test:unit # Suite unit
npm run test:integration # Suite integration (timeout 15s)
npm run test:regression # Suite regression
npm run test:perf # Suite performance (timeout 30s)
npm run test:security # Suite security (timeout 20s)
Configurar con wrangler secret put NAME o desde CF dashboard.
Secret Propósito SUPABASE_URLURL de emahub bzikofdvfvchntvfzyni SUPABASE_ANON_KEYAnon key (servida via /api/config) SUPABASE_SERVICE_ROLE_KEYService role (uso interno middleware)
Secret Propósito PLATFORM_API_KEYOutbound a clinic/lab/well via X-Platform-Key AIDBOX_BASE_URLURL Aidbox para provisioning de Organizations AIDBOX_AES_KEYAES-GCM key para encrypt client_secret (S9-3)
Secret Propósito EMI_API_KEYInbound auth a emi.emahealth.io EMI_BASE_URLDefault https://emi.emahealth.io
Secret Propósito ANTHROPIC_API_KEYSonnet — proposal, contract, summary
Secret Propósito GITHUB_TOKENPersonal access token, scope: repo read-only GITHUB_WEBHOOK_SECRETHMAC sig verification para /api/github/webhook
Secret Propósito EMAWELL_SUPABASE_URLURL del proyecto separado emawell EMAWELL_SUPABASE_SERVICE_ROLE_KEYService role read-only
Si faltan estas dos, /api/well/* retorna 503 “not configured”.
Secret Propósito LOOPS_API_KEYEmails operativos (invitaciones admin tenant)
# wrangler.toml (extracto)
compatibility_date = " 2026-01-01 "
compatibility_flags = [ " nodejs_compat " ]
directory = " ./dist " # Vite build output
crons = [ " 0 * * * * " ] # GitHub issues sync horario
pattern = " vault.emahealth.io/* "
zone_name = " emahealth.io "
# Aplicar migraciones a hub
psql postgresql://postgres@db.bzikofdvfvchntvfzyni.supabase.co/postgres \
Estado actual: 7 migraciones aplicadas.
Migration Topic 001_fiscal_config.sqlConfiguración de facturación electrónica + dte_emissions_log 002_pricing_tool.sqlPricing tool — costs, plans, quotes 003_provision_columns.sqlColumnas para provisioning cross-product 004_editorial_metrics.sqlMétricas editorial 005_schema_catchup.sqlSync schemas 006_rename_slug_and_product_status.sqlSlug-based domain (F074) 007_slug_history.sqlSlug rename audit
(El audit_trigger F085 vive como migración aparte fuera del repo
local — administrada vía MCP / staging.)
https://api.anthropic.com
img-src 'self' data: https://avatars.githubusercontent.com;
font-src 'self' fonts.gstatic.com;
style-src 'self' 'unsafe-inline' fonts.googleapis.com;
Crear webhook en github.com/emahealth/<repo>/settings/hooks.
URL: https://vault.emahealth.io/api/github/webhook.
Content type: application/json.
Secret: el valor de GITHUB_WEBHOOK_SECRET.
Events: Issues, Pull requests, Pushes.
SSL verification: enable.
Ver docs/SETUP_GITHUB_WEBHOOK.md para detalles.
HTTP Significado Solución 401 JWT expiró Re-login 403 vault_users.is_active=false o NO_VAULT_USERAdmin activa el usuario 403 + module RBAC: rol sin acceso al módulo Admin actualiza vault_permissions 503 Ema Well not configured Setear EMAWELL_* secrets 503 Aidbox unreachable Verificar AIDBOX_BASE_URL 451 Provisioning failed Revisar audit_log + retry
Public routes ANTES de authMiddleware — /api/health,
/api/config, /auth/*, /api/github/webhook,
/api/internal/tenants. Si las pones después, se bloquean por JWT
required.
Cross-product endpoints /cross/* ANTES de /:slug/* en
/api/products/* — sino el :slug los intercepta.
CSP estricto — agregar dominios nuevos cuesta tiempo. Probar en
staging antes.
Slug rename con history — la URL pre-rename redirige 12 meses.
No purgar tenant_slug_history antes de ese plazo.
Audit_trigger SECURITY DEFINER — tiene impacto perf en INSERTs
pesados. Considerar partitioning de audit_log cuando crezca.
Imports relativos — no usar alias @/ por convención del repo.
Si NO_VAULT_USER en login: significa que el usuario se
autenticó con Google pero no existe en vault_users. Admin debe
crearlo primero.
No hay GitHub Actions para deploy (preferencia EMA: deploys
manuales con npm run deploy o CF git integration).
Tests corren localmente antes de merge.
Branches: trabajo directo en main post-S9 (solo-dev).
curl https://vault.emahealth.io/api/health
# Expect: 200 { status: "ok" }
# Config endpoint público
curl https://vault.emahealth.io/api/config
# Expect: 200 { supabaseUrl, supabaseAnonKey }
# Auth endpoint requiere JWT
curl https://vault.emahealth.io/api/tenants
# Expect: 401 sin Authorization
curl -I https://vault.emahealth.io/
# Expect: Content-Security-Policy: default-src 'self'; ...
Ver docs/SMOKE_S8b_CROSS_PRODUCT.md y docs/SMOKE_S8c_HUMAN.md para
checklist completo post-deploy.