Skip to main content

Tiempo real y estado del chat

Cómo la pantalla de Chat de tickets mantiene sincronizados la lista, el hilo y las notificaciones: el slice NgRx tickets, los consumidores de ChatSocketService dentro de la feature y las reglas de reconexión. La tabla completa de eventos y la mecánica del socket (autenticación, reintentos, payloads) está en Sockets y el patrón de estado en Estado; aquí solo se describe el uso propio de esta feature. Contraparte en el backend: Chat y Ticket.

Archivos​

ArchivoRol
core/services/ui/chat-socket.service.tsSocket del chat: estado de conexión, emits y observables de eventos
core/helpers/global/chat-socket.constants.tsSOCKET_EVENTS
core/interfaces/ui/chat-socket.interface.tsPayloads de emisión y de recepción
core/states/ticket/*Slice tickets (acciones, efectos, reducer, selectores)
core/states/scheduled-message/*Slice scheduledMessages
core/services/ui/notification-center.service.tsCentro de notificaciones del encabezado
core/services/ui/audio-notification.service.tsSonido de aviso
core/helpers/global/presence.helper.tsPRESENCE_STALE_MS e isPresenceFresh

Ambos slices se registran en core/states/app.state.ts (tickets: ticketReducer, scheduledMessages: scheduledMessageReducer) junto con TicketEffects y ScheduledMessageEffects.

Conexión​

ContentComponent (el layout autenticado) llama connect(user._id) de ChatSocketService cuando carga el perfil y disconnect() al destruirse. ChatListComponent.initSocketConnection y el botón de reintento de ChatPageComponent vuelven a llamar a connect, que es idempotente. app-connection-status-bar refleja el estado (idle, connecting, authenticated, error); mientras no esté authenticated los emits (join-ticket, send-message, send-file, send-note, mark-read, message-delivered) no hacen nada.

Estado tickets​

TicketState guarda tickets, totalCount, currentPage, hasMore, isLoading, isInitialLoad, isLoadingMore, error y activeFilter (el filtro de la última carga, que usan los reducers para decidir si una fila sigue perteneciendo a la pestaña actual). Selectores (ticket.selectors.ts): selectTickets, selectTicketsTotalCount, selectTicketsCurrentPage, selectTicketsHasMore, selectTicketsIsLoading, selectTicketsIsInitialLoad, selectTicketsIsLoadingMore, selectTicketsError y selectTicketsActiveFilter.

Los reducers comparten dos ayudantes:

  • removeIfFilterMismatch(tickets, id, nuevoEstado, activeFilter, extra): si el filtro tiene un estado y no coincide con el nuevo, quita la fila; si coincide (o no hay estado), actualiza el estado y los campos extra.
  • removeIfUnassignedMismatch: para devolver a la cola; quita la fila si la pestaña no es open o si la vista está acotada a otro operador.

El catálogo de las 54 acciones, con su efecto en el reducer, está en Acciones del ticket.

Quién escucha qué​

ConsumidorEvento u observableReacción
ChatSocketServiceticket_updated, ticket_assigned, ticket_status_changed, ticket_reactivated, ticket_deleted, contact_presence_changed, ticket_lock_changedDespacha la acción ...FromSocket de TicketActions (y expone también un Subject salvo presencia y bloqueo)
ChatSocketServicescheduled_message_sent, _cancelled, _failed, _created, _updatedDespacha las acciones ...FromSocket de ScheduledMessageActions
ChatListComponentticketAssigned$Toast "Asignación automática" si el usuario es el asignado o es administrador; si el ticket asignado al usuario no está en la lista, recarga
ChatListComponentticketCreated$Recarga la lista si la pestaña es open (o no hay)
ChatListComponentticketStatusChanged$Recarga si el ticket no está en la lista y coincide con la pestaña (permite que aparezca un ticket que cambió de estado)
ChatListComponentticketReactivated$Recarga si no está en la lista y la pestaña es open
ChatListComponentticketUpdated$ (solo sendType === 'contact')Si no es el ticket abierto y el usuario es administrador o el ticket está en su lista: suena el aviso y ticketIncrementUnread
ChatListComponentmessageAckChanged$ticketLastMessageAckChanged; si el acuse es read, ticketClearUnread
ChatListComponentnewNote$Invalida la caché de notas del contacto abierto y recarga si la pestaña de notas está visible
ChatListComponentreconnected$Recarga la lista (loadTickets)
ChatConversationComponentconnectionStatus$Se une a la sala del ticket en cada transición a authenticated; actualiza isConnectionLive
ChatConversationComponentnewMessage$, newFileMessage$, newNote$Agrega al hilo si el contactId coincide; confirma entrega de los del contacto
ChatConversationComponentmessageAckChanged$applyAck sobre los mensajes del ticket
ChatConversationComponentreconnected$Vacía y recarga el hilo
NotificationCenterServiceticketUpdated$ (contacto)Agrega a las notificaciones, muestra toast (salvo ticket activo) y notificación del navegador; un agente solo recibe las de tickets de su lista
ConnectionsTableComponent, ConnectionWhatsappModalComponentwhatsappQr$, whatsappSessionStatus$Ver Conexiones

El centro de notificaciones recibe setActiveTicket al seleccionar o cerrar un ticket: al abrir uno marca como leídas sus notificaciones y descuenta el contador. Carga las no leídas recientes al iniciar con GET /messages/recent-unread (paginado).

Salas y confirmaciones​

  • Al abrir un ticket el hilo emite join-ticket y, en la primera carga, mark-read y message-delivered para los mensajes del contacto que seguían en sent. Al cerrar emite leave-ticket.
  • Al llegar un mensaje del contacto con el hilo abierto se emite message-delivered con su dbId.
  • isPaused (modo pausa del agente, set-agent-status) se reenvía al autenticar; lo gestiona el encabezado, no esta feature.

Presencia del contacto​

contact_presence_changed llega con { contactId, isOnline, lastSeen, disconnectReason }; el reducer parchea todas las filas de ese contacto. Como un contacto puede caer en estado "conectado" sin recibir el evento de desconexión, isPresenceFresh considera desconectado a quien no haya refrescado lastSeen en 90 s (PRESENCE_STALE_MS) y la lista se rehidrata cada 30 s para reevaluarlo sin esperar un evento.

Reconexión​

Los eventos perdidos durante una caída no se reenvían. Por eso reconnected$ (emite en cada reautenticación posterior a la primera) hace que la lista vuelva a pedir la página 1 con el filtro vigente y el hilo abierto vuelva a cargar su historial; el texto que el agente tenía escrito se conserva. Si el servidor corta con io server disconnect, el servicio reintenta hasta cinco veces con espera creciente de 1 s (ver Sockets).

Pruebas​

SpecCubre
core/states/ticket/ticket.reducers.spec.tsReducers de posposición, reactivación, borrado por socket idempotente, no leído, presencia, bloqueo, estado terminal, devolver con filtro de operador, asignación y transferencia
core/states/ticket/ticket.effects.spec.ts, ticket.actions.spec.tsEfectos y creadores de posposición, bloqueo y conflicto 409
core/states/scheduled-message/*.spec.tsEfectos y reducer del slice de mensajes programados
chat-list.component.spec.tsTick de presencia (se mantiene en línea dentro de 90 s, pasa a desconectado al cruzar el umbral, se detiene al destruir), recarga tras reconexión, invalidación por newNote$
chat-conversation.component.spec.tsUnirse a la sala en cada reautenticación y recarga del hilo tras reconectar

No hay specs de chat-socket.service.ts ni de notification-center.service.ts dentro de esta feature.

Gotchas​

  • ticket_created no tiene acción NgRx: solo hace que la lista se vuelva a cargar.
  • Tickets bloqueados no usa este slice: llama a TicketService.getLockedTickets directamente, de modo que sus filas no se actualizan con los eventos de socket descritos aquí.
  • El contador "no leídos" de una tarjeta se incrementa en el cliente con cada ticket_updated del contacto y solo se pone a cero al abrir el ticket o al llegar un acuse de lectura; no se recalcula contra el backend hasta la siguiente recarga.