ixiclinicDocs
Desarrolladores

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.

NamespaceAuth del handshakePara qué
/queueJWT (auth.token)Puestos de servicio del staff (cola de turnos)
/screenscreenId + tenantIdPantalla pública de turnos (sin login)
/kioskkioskId + tenantIdKiosko público de tickets (sin login)
/notificationsJWT (auth.token)Campana de notificaciones del staff
/chatJWT (auth.token)Chat interno entre empleados
/bridgeToken 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, /notifications y /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 emite conversation:join y 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:new al room user:{userId} en /notifications.
  • emitNotificationCount(userId, count)notification:count (badge de no leídas).
  • emitBroadcastNotification(tenantId, notification)notification:new a todo tenant:{tenantId}.

Chat interno

  • emitChatMessage(conversationId, message)message:new al room chat:{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 typing con el conversationId; el servidor lo reenvía a chat:{id} solo si el emisor realmente se unió a esa conversación (la verificación de acceso ya ocurrió en el conversation: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.

On this page