Tiempo real
Socket.IO en el API, namespaces y rooms por tenant/usuario, y los eventos en vivo de cola, notificaciones, chat y agentes de impresión.
Las funciones en vivo (cola de turnos, notificaciones, chat interno entre empleados y los
agentes de impresión) usan Socket.IO. El servidor (socket.io) corre en el API; los
clientes (socket.io-client) viven en el admin, el portal, el lab-site y la app ixiclinic
Connect.
Inicialización
Socket.IO se monta sobre el mismo servidor HTTP de Fastify, no en un puerto aparte. En
apps/api/src/index.ts, tras app.listen(...):
const httpServer = app.server;
initSocket(httpServer);initSocket vive en apps/api/src/plugins/socket.ts. Crea el SocketServer con CORS abierto
y transports ['websocket', 'polling'], registra todos los namespaces y guarda la instancia en
un singleton accesible vía getIO(). Los demás módulos no tocan io directamente: emiten a
través de helpers exportados (emitQueueEvent, emitNotification, emitChatMessage, …).
Namespaces
Cada superficie tiene su propio namespace. Algunos exigen JWT en el handshake; las pantallas públicas no.
| Namespace | Auth del handshake | Para qué |
|---|---|---|
/queue | JWT (auth.token) | Puestos de servicio del staff (cola de turnos) |
/screen | screenId + tenantId | Pantalla pública de turnos (sin login) |
/kiosk | kioskId + tenantId | Kiosko público de tickets (sin login) |
/notifications | JWT (auth.token) | Campana de notificaciones del staff |
/chat | JWT (auth.token) | Chat interno entre empleados |
/bridge | Token de dispositivo (auth.token) | Agentes ixiclinic Connect (impresión) |
Los namespaces autenticados usan un middleware io.of(ns).use(...) que toma
socket.handshake.auth.token, lo verifica con verifyAccessToken y guarda el payload en el
socket. El namespace /bridge valida en cambio contra la tabla bridges por su deviceToken.
Rooms
Al conectar, cada socket entra a rooms que acotan a quién llega cada evento:
tenant:{tenantId}— todo el laboratorio. Lo usan/queue,/screen,/kiosk,/notificationsy/chat.branch:{tenantId}:{branchId}— la sucursal (en/queue).user:{userId}— entrega personal (notificaciones y bumps de chat).screen:{screenId}/kiosk:{kioskId}— la pantalla o kiosko concretos.chat:{conversationId}— una conversación de chat. No se entra al conectar: el cliente emiteconversation:joiny el servidor verifica que sea participante (o que el canal sea público y del tenant) antes de unirlo, de modo que el payload completo de un mensaje nunca llega a un no-participante.bridge:{bridgeId}— el agente de impresión concreto.
Eventos en vivo
Cola de turnos
emitQueueEvent(tenantId, event, data) emite el evento tanto al room tenant:{id} del
namespace /queue (los puestos del staff) como al de /screen (la pantalla pública), para
que ambos reflejen el cambio sin lógica duplicada.
Notificaciones
emitNotification(userId, tenantId, notification)→notification:newal roomuser:{userId}en/notifications.emitNotificationCount(userId, count)→notification:count(badge de no leídas).emitBroadcastNotification(tenantId, notification)→notification:newa todotenant:{tenantId}.
Chat interno
emitChatMessage(conversationId, message)→message:newal roomchat:{id}(solo quienes tienen la conversación abierta).emitChatEvent(conversationId, event, data)→ eventos arbitrarios de la conversación (ediciones, borrados, recibos de lectura).emitChatToUser(userId, event, data)→ bump de la lista de conversaciones / badge de no leídos de un usuario aunque no tenga esa conversación abierta.emitChatToTenant(tenantId, event, data)→ bump del canal público a todo el tenant.- Indicador de typing: el cliente emite
typingcon elconversationId; el servidor lo reenvía achat:{id}solo si el emisor realmente se unió a esa conversación (la verificación de acceso ya ocurrió en elconversation:join).
Agentes de impresión (bridge)
Al conectar, el agente entra a bridge:{bridgeId} y tenant:{tenantId}, y la conexión/
desconexión voltea su estado online/offline en la tabla bridges. El servidor le empuja
trabajos con emitBridgeJob(bridgeId, job) (job:new) y comandos con emitBridgeCommand
(p. ej. printers:rescan). El push es solo un "despierta": la fuente de verdad es el poll HTTP
(ver Impresión + Bridge).
Siguiente: Scheduling.
Autenticación
Los tres flujos de auth (staff, superadmin SaaS y cliente del portal), rutas públicas, plugins de auth y RBAC por módulo.
Scheduling
Tareas recurrentes en background con @fastify/schedule (toad-scheduler) — recordatorios de citas, no-shows, limpieza de ocupación, resumen diario y canales WhatsApp/email.