API
Backend REST de ixiclinic — Fastify 5, Drizzle ORM, PostgreSQL y Zod 4. Patrón de módulos, cadena de plugins, tiempo real y despliegue.
El API (apps/api) es el backend REST multi-tenant de ixiclinic: todo lo demás (admin, console,
portal, lab-site, mobile) consume sus endpoints. Es la única pieza con acceso directo a la base
de datos.
| Stack | Fastify 5, Drizzle ORM, PostgreSQL, Zod 4 |
| Puerto local | 5000 |
| Producción | api.ixiclinic.com (VPS, Nginx → Docker) |
| Paquete | @ixiclinic/api |
| Docs OpenAPI | /docs (Scalar UI) |
Estructura
El bootstrap vive en apps/api/src/index.ts (arranca el servidor, ejecuta
ensureNcfFunction() y monta Socket.IO). La construcción de la instancia Fastify está en
apps/api/src/app.ts (buildApp()), que registra plugins y rutas. La configuración validada
con Zod está en apps/api/src/config/env.ts.
Patrón de módulos (4 archivos)
Cada dominio vive en apps/api/src/modules/<dominio>/ y sigue la misma estructura de cuatro
archivos:
| Archivo | Responsabilidad |
|---|---|
routes.ts | Registro de rutas en Fastify y preHandler (auth/RBAC). |
handlers.ts | Recepción de la petición, validación y formato de respuesta. |
service.ts | Lógica de negocio y consultas a la base de datos (Drizzle). |
schemas.ts | Esquemas de validación Zod (entrada y salida). |
Para crear uno nuevo paso a paso, ver Añadir un módulo al API.
Excepciones a la estructura base:
instruments/tiene un subdirectorioparsers/con parsers de datos de instrumentos de laboratorio:astm.ts,csv.ts,hl7.ts,xml.ts.appointments/añadereminder-service.tsyappointment-notifications.ts.
Módulos (45)
Los módulos se agrupan por categoría. Todos viven en apps/api/src/modules/.
Núcleo y operación del laboratorio
auth, impersonation, health, tenants, patients, catalog, orders, results,
search, billing, ecf, reception, dashboard, inventory, settings, qc,
uploads, cash, lookup, companies, doctors, samples, instruments, queue,
notifications, appointments, prescriptions, printing, chat, templates.
Nota: La lógica fiscal DGII (e-CF) es un módulo del API (
ecf), no vive en el admin. El admin solo expone la UI y llama a estos endpoints vía server actions.
Clínicos
consultations, procedures, cardiology, encounters, programs, activities. Cubren
encuentros estructurados, procedimientos, cardiología y los programas crónicos de seguros (ARS).
Portal del cliente (sin token de tenant)
portal, client-auth, client-portal, client-company. Atienden al lab-site público y al
portal del paciente/empresa. Sus rutas se saltan el middleware de auth de tenant.
SaaS (superadmin)
saas-auth, demos, subscriptions, saas, organizations. Sirven a la console interna
bajo /api/saas/* y exigen el preHandler requireSuperadmin (excepto POST /api/saas/demos,
que es público y lo llama la landing).
Cadena de plugins
buildApp() registra los plugins en este orden exacto (apps/api/src/app.ts):
@fastify/swagger— genera el OpenAPI.@fastify/multipart— subidas, límite de 5 MB.@fastify/cors— permite*.ixiclinic.com, los orígenes deCORS_ORIGINSylocalhosten dev.@fastify/helmet— cabeceras de seguridad (CSP desactivado).@fastify/rate-limit— 500 peticiones/min por IP.errorHandler— capturaZodErrory formatea los errores de validación con la ruta del campo.authPlugin— verifica el JWT (Bearer) salvo en rutas públicas.tenantScopePlugin— inyecta el contexto de tenant en cada petición.@scalar/fastify-api-reference— UI de docs en/docs.@fastify/schedule+schedulerPlugin— tareas en segundo plano.- Todas las rutas de los módulos.
Los plugins viven en apps/api/src/plugins/: auth.ts, tenant-scope.ts, error-handler.ts,
scheduler.ts, socket.ts, rbac.ts / rbac-config.ts, require-superadmin.ts,
client-auth.ts, instrument-auth.ts, bridge-auth.ts.
Rutas públicas (sin token)
El authPlugin (apps/api/src/plugins/auth.ts) deja pasar sin JWT estos prefijos:
/api/auth/login /api/auth/register /api/auth/refresh
/api/portal/ /api/health /docs
/api/saas/auth/ /api/client/auth/ /api/client/
/api/queue/board/ /api/queue/patient/
/api/webhooks/ /api/bridge/Y estas rutas concretas por método:
POST /api/saas/demos(formulario de demo de la landing).POST /api/instruments/{id}/push(datos entrantes de instrumentos).POST /api/queue/kiosks/{id}/tickety.../appointment-ticket(kiosko).GET /api/queue/kiosks/{id}/config(configuración del kiosko).
Base de datos
Drizzle ORM sobre PostgreSQL. Los esquemas viven en apps/api/src/db/schema/ (25 archivos por
dominio: tenants, branches, patients, clients, catalog, orders, results,
inventory, billing, insurance, qc, dgii, audit, saas, appointments,
instruments, notifications, queue, prescriptions, encounters, programs,
activities, templates, printing, chat). Las migraciones salen a
apps/api/src/db/migrations. Detalle completo en Base de datos.
Comandos (desde apps/api):
pnpm db:generate # genera migraciones desde el schema
pnpm db:migrate # aplica migraciones
pnpm db:push # empuja el schema directo (solo dev)
pnpm db:studio # abre Drizzle Studio
pnpm db:seed # datos de demoTiempo real (Socket.IO)
Socket.IO se inicializa sobre el servidor HTTP en index.ts (initSocket, definido en
apps/api/src/plugins/socket.ts). Usa varios namespaces: /queue y /screen (turnos),
/kiosk, /notifications, /chat (chat interno autenticado, con salas por conversación,
usuario y tenant) y /bridge (trabajos hacia ixiclinic Connect en la LAN del cliente).
Tareas programadas (scheduler)
schedulerPlugin (toad-scheduler vía @fastify/schedule) registra trabajos recurrentes:
| Trabajo | Frecuencia |
|---|---|
| Procesar recordatorios de citas | cada 5 min |
| Detectar no-shows | cada 10 min |
| Cerrar cajas obsoletas + liberar turnos | cada 15 min |
| Resumen diario | cron 0 7 * * * |
Los envíos de recordatorios usan apps/api/src/lib/email-reminders.ts y
apps/api/src/lib/whatsapp.ts. Otros helpers de apps/api/src/lib/: jwt.ts, password.ts,
pagination.ts, audit.ts, tenant-seed.ts, y los generadores de impresión escpos/
(recibos térmicos) y labels/ (etiquetas TSPL).
OpenAPI / Scalar
La documentación interactiva del API está en /docs (Scalar UI), generada automáticamente
desde los esquemas registrados con @fastify/swagger.
Build y despliegue
build compila con tsc a dist/; start ejecuta node dist/migrate.js && node dist/index.js
(las migraciones corren al arrancar). En producción se empaqueta con un Dockerfile
multi-stage (node:22-alpine, usuario no-root, EXPOSE 5000, healthcheck contra
/api/health). El despliegue corre en el VPS con Docker Compose. Ver
Operaciones.