Chat de tickets (features/whaticket)
Pantalla principal del agente y destino por defecto tras el login (/ redirige a whaticket/chat). Reúne en una sola vista la lista de tickets, el hilo de mensajes del contacto seleccionado, el compositor, el panel de datos del contacto y todas las acciones sobre el ticket. Esta página es el índice y la visión general; el detalle está repartido en páginas hermanas:
| Página | Contenido |
|---|---|
| Lista de tickets | Pestañas de estado, alcance, filtros, búsqueda, tarjetas, panel de datos del contacto, ventanas flotantes y vista de solo lectura |
| Hilo de mensajes | Cabecera, carga paginada, tipos de mensaje, menú contextual, selección y reenvío, destacados, visor de medios |
| Compositor y adjuntos | Texto, emoji, respuestas rápidas, firma, nota interna, responder, adjuntos, audio, envío programado |
| Acciones del ticket | Las 54 acciones del slice TicketActions y los 15 identificadores del menú, con efecto, endpoint y permiso |
| Tiempo real y estado | Cómo la feature consume los sockets, el slice NgRx tickets y la resincronización |
| Etiquetas y datos del contacto | Panel de etiquetas y metadatos, asignación rápida, barra de etiquetas, motivo de contacto |
Ruta y guards
| Capa | Archivo | Definición |
|---|---|---|
| Raíz | shared/routes/content.routes.ts | whaticket con canActivate: [AuthGuard], carga WhaticketModule |
| Sub-ruta | whaticket/whaticket-routing.module.ts | '' redirige a chat; chat con permissionGuard y TICKET_READ, carga ChatModule |
| Página | whaticket/chat/chat-routing.module.ts | Ruta vacía a ChatPageComponent con permissionGuard y TICKET_READ |
WhaticketModule solo contiene el routing. ChatModule (chat/chat.module.ts) declara 18 componentes e importa OverlayModule, PortalModule, NgbTooltipModule, NgbModalModule, NgSelectModule, SwitchModule, TranslateModule y SharedModule; además importa como standalone ScheduleSendButtonComponent y ScheduledMessageStripComponent. El estado de la URL es ?chatId=<ticketId> (y highlightMessageId para el salto a un mensaje destacado).
Permisos de la feature
Todos se aplican con *appShowWithPermission salvo que se indique otra cosa; el detalle de cada uno está en la página donde actúa.
| Acrónimo | Dónde | Efecto |
|---|---|---|
TICKET_READ | Rutas | Entrar a la pantalla |
TICKET_FILTER_STATUS_OPEN, _IN_PROGRESS, _PENDING, _POSTPONED, _RESOLVED, _CLOSED, _CANCELLED | Lista | Mostrar cada pestaña de estado (solo visualización) |
TICKET_FILTER_SCOPE_MINE, _ALL, _UNASSIGNED | Lista | Mostrar cada opción del selector de alcance |
TICKET_FILTER_BY_AGENT | Lista | Filtro por operador; habilita GET /tickets/filter-agents |
TICKET_VIEW_AGENT_AVATAR | Tarjeta | Insignia con el avatar del agente asignado |
TICKET_UPDATE | Menú de acciones | Resolver, devolver, bloquear, transferir, posponer, cambiar estado |
TICKET_DELETE | Menú de acciones | Eliminar ticket |
MESSAGE_CREATE | Compositor | Botón de envío programado (junto con el indicador del tenant) |
MESSAGE_DELETE | Menú del mensaje | Eliminar un mensaje propio |
CONTACT_UPDATE | Panel de datos, etiquetas | Editar contacto, asignar etiquetas, editar metadatos |
LABEL_UPDATE, LABEL_DELETE | Panel de etiquetas | Editar o eliminar etiquetas del catálogo desde el panel |
TICKET_UNLOCK_ANY existe pero actúa en Tickets bloqueados, no aquí. Los acrónimos de pestañas y alcance son solo un interruptor de visualización: el código lo aclara y el backend no los comprueba.
Archivos
features/whaticket/
├── whaticket.module.ts, whaticket-routing.module.ts
├── chat/ # chat.module.ts, chat-routing.module.ts
├── pages/chat-page/ # barra de conexión + app-chat-list
└── components/chat/
├── chat-list/ # lista, tarjeta (chat-list-item), panel de datos del contacto
├── chat-conversation/ # hilo + compositor
├── ticket-actions-menu/ # menú de acciones del ticket
├── transfer-panel/, postpone-dialog/ (+ service), forward-tickets-panel/
├── labels-panel/, labels-quick-assign/ (+ overlay service y types), chat-labels-bar/
├── chat-filter-panel/, chat-popout/, chat-view-modal/
├── message-context-menu/, media-viewer/
├── schedule-message-dialog/ (+ service), schedule-send-button/, scheduled-message-strip/
└── contact-reason-modal/, contact-metadata-modal/, ticket-history-bar/
Fuera de la feature, la lógica de datos vive en: core/services/api/ticket.service.ts, message.service.ts, starred-message.service.ts, scheduled-message.service.ts, file-api.service.ts; core/services/ui/chat-socket.service.ts y los ayudantes chat-*.service.ts; core/states/ticket/* y core/states/scheduled-message/*; core/interfaces/api/ticket.interface.ts; AudioPlayerComponent (declarado en ChatModule pero ubicado en shared/components/audio-player).
Composición
ChatPageComponent renderiza app-connection-status-bar (estado del socket con botón de reintento, que llama a ChatSocketService.connect con el _id del perfil) y app-chat-list. ChatListComponent hospeda la lista, la conversación activa (app-chat-conversation), el panel de datos del contacto y las ventanas flotantes. Los demás componentes se abren como overlays CDK o modales NgbModal desde la lista o la conversación.
Servicios y endpoints de la feature
Rutas verificadas contra ticket.controller.ts y message.controller.ts; páginas de backend: Ticket, Message, Starred message, Contact, Label, Quick response, Departments y File. La tabla de endpoints de la página de Ticket del backend puede no listar todas las rutas de abajo.
| Servicio | Petición | Uso |
|---|---|---|
TicketService.getTickets | GET /tickets/get-all (filtros en query) | Lista, historial por contacto, panel de reenvío |
TicketService.getTicketById | GET /tickets/get-one/:id | Abrir un ticket que no está en la lista |
TicketService.getFilterableAgents | GET /tickets/filter-agents | Filtro por operador |
TicketService.findAgentsForSelect, getAgents | GET /users/get-all | Panel de transferencia |
TicketService (acciones) | Ver Acciones del ticket | 12 PATCH, un DELETE y un POST |
MessageService.getContactMessages | GET /messages/:contactId (page, limit 20) | Hilo: mensajes de todos los tickets del contacto |
MessageService.getContactNotes | GET /messages/:contactId con messageType=note (limit 10) | Pestaña de notas |
MessageService.deleteMessage | DELETE /messages/remove/:id | Eliminar mensaje (MESSAGE_DELETE) |
StarredMessageService | POST /starred-messages/toggle/:messageId, GET /starred-messages/ids, GET /starred-messages/by-contact/:contactId, GET /messages/:id/window | Destacados y salto a mensaje |
ScheduledMessageService | POST /scheduled-messages/create, GET .../mine, GET .../by-ticket/:ticketId, PATCH .../update/:id, PATCH .../cancel/:id | Ver Mensajes programados |
FileApiService.upload | POST /files/upload (multipart, campo file) | Adjuntos y audio |
QuickResponseService.findPaginated | GET /quick-responses/get-all (search, ticketId) | Autocompletado con / |
LabelService, ContactService.assignLabels, updateContact | GET /labels/get-all, PATCH /contacts/:id/assign-labels, PATCH /contacts/update/:id | Etiquetas y metadatos |
Los mensajes de texto, archivos y notas del agente no usan REST: se emiten por socket (send-message, send-file, send-note); ver Compositor.
Modelos principales
core/interfaces/api/ticket.interface.ts:
| Tipo | Campos o valores |
|---|---|
TicketStatus | open, closed, inProgress, pending, postponed, resolved, cancelled |
Ticket | _id, identifier, status, tenantId, department?, contact?, assignedUser?, lastMessage?, unreadCount?, agentUnread?, postponedUntil?, reactivated? (solo UI), lockedBy?, lockedByUser?, connection? (channelType, name, phone?; nunca el token), createdAt, updatedAt |
TicketContact | datos del contacto, labels, metadata, isBlocked, isActive, isOnline, lastSeen, lastDisconnectReason |
TicketsFilter | page, limit, status, assignedUserId, assignedUserIds, unassigned, includeUnassigned, contactId, createdFrom, createdTo, hasUnread, lastMessage*, labelId, contactIsBlocked, contactIsActive, groupByContact, lockedOnly |
TicketMenuActionId | resolve, resolveNoFarewell, return, transfer, postpone, cancelPostpone, markUnread, contactReasons, delete, statusOpen, statusInProgress, statusClosed, statusCancelled, lock, unlock |
ChatConversation | Proyección de un ticket para la UI (userId es el _id del ticket; otherParticipantId es el contacto) |
Canal en la UI
La lista y el encabezado de la conversación dibujan el icono del canal del ticket (conversation.channelType) tomándolo de CONNECTION_CHANNELS (ver Iconos de canal y Conexiones). Si el ticket no tiene conexión no se dibuja icono: el código evita asumir web. El tooltip muestra connectionName o, en su defecto, el tipo de canal.
Estado de acuse de los mensajes propios
Un mensaje propio muestra, según message.ack: un check simple (sent o sin valor), doble check (delivered), doble check azul (read) o un icono de exclamación rojo (failed) con tooltip del motivo (messages.failure.<motivo>). Ver Estado failed.
Enlaces clicables
El texto de los mensajes y las leyendas de medios se renderizan con renderMessageMarkup, que activa linkify en renderWhatsappMarkup (ver Compartido).
Pruebas
| Spec | Cubre |
|---|---|
whaticket-routing.module.spec.ts, chat/chat-routing.module.spec.ts | Ambas rutas con permissionGuard y TICKET_READ |
chat-list.component.spec.ts, chat-list-item.component.spec.ts, chat-filter-panel.component.spec.ts | Ver Lista de tickets |
chat-conversation.component.spec.ts, message-context-menu, media-viewer | Ver Hilo |
ticket-actions-menu, transfer-panel, postpone-dialog, ticket.*.spec.ts | Ver Acciones del ticket |
schedule-*, scheduled-message-strip | Ver Compositor |
No hay specs para labels-panel, labels-quick-assign, chat-labels-bar, forward-tickets-panel, contact-reason-modal, contact-metadata-modal, ticket-history-bar, chat-view-modal ni chat-page.
Gotchas
- Hay tres componentes sin uso en las plantillas de la rama:
TicketHistoryBarComponent(declarado enChatModule, nunca renderizado),ContactMetadataModalComponent(no está declarado en ningún módulo; su función la cubre el panel de etiquetas) yContactReasonModalComponent(solo lo abre_handleResolveWithContactReasondeChatListComponent, un método privado que nadie invoca). - El hilo carga los mensajes por contacto (
/messages/:contactId), no por ticket: se ven los mensajes de todos los tickets del contacto en un solo hilo. - El ítem "Motivos de contacto" del menú no abre el modal de motivo de contacto sino el panel de etiquetas y metadatos; ver Etiquetas.