Módulo Message
src/app/modules/message/
Persistencia de mensajes de las conversaciones.
Modelo — Message
| Campo | Tipo | Descripción |
|---|---|---|
_id | ObjectId | ID de MongoDB |
tenantId | ObjectId → Tenant | — |
ticketId | ObjectId → Ticket | Conversación a la que pertenece |
sendType | string | contact | agent | user | system |
messageType | string | chat | media | system |
sendUserRefModel | string | User | Contact — modelo de referencia polimórfico |
sendUserId | ObjectId (refPath) | Emisor: Contact._id si sendType=contact, User._id si agent/user |
body | string | Contenido del mensaje |
parentId | ObjectId → Message | Mensaje al que responde (threading) |
MediaId | ObjectId → File | Archivo adjunto (si messageType=media) |
ack | string | sent | delivered | read |
isRead | boolean | Si el agente lo marcó como leído |
readAt | Date | Fecha de lectura |
createdAt | Date | — |
updatedAt | Date | — |
Endpoints
CRUD general
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
POST | /api/v1/messages/create | @Auth() | Crea mensaje |
GET | /api/v1/messages/get-all | @Auth() | Lista mensajes (filtrable por ticketId) |
GET | /api/v1/messages/get-one/:id | @Auth() | Obtiene un mensaje |
PATCH | /api/v1/messages/update/:id | @Auth() | Actualiza (ej: cambiar ack) |
DELETE | /api/v1/messages/remove/:id | @Auth() | Elimina mensaje |
PATCH | /api/v1/messages/mark-read/:id | @Auth() | Marca un mensaje como leído |
PATCH | /api/v1/messages/mark-read-ticket/:ticketId | @Auth() | Marca todos los mensajes de un ticket como leídos |
Por contacto
Endpoints de consulta de historial centrados en el contacto. Todos filtran automáticamente por tenantId del usuario autenticado.
Orden de registro en el router:
/:contactId/tickets-allse registra antes de/:contactId/:ticketIdpara evitar que Express capture el literaltickets-allcomo un parámetro:ticketId.
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
GET | /api/v1/messages/:contactId/tickets-all | @Auth() | Todos los tickets del contacto con su último mensaje |
GET | /api/v1/messages/:contactId | @Auth() | Todos los mensajes del contacto (todos sus tickets) |
GET | /api/v1/messages/:contactId/:ticketId | @Auth() | Mensajes de un ticket específico del contacto |
Endpoints por contacto — detalle
GET /api/v1/messages/:contactId/tickets-all
Devuelve todos los tickets del contacto con el último mensaje de cada uno populado, junto con el remitente, el agente asignado y el conteo de mensajes no leídos. Ordenados por fecha del último mensaje (más reciente primero).
Delega internamente al mismo pipeline de TicketService.getTicketsList con contactId como filtro, por lo que la respuesta es idéntica a la de GET /api/v1/tickets/get-all.
Query params
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
page | number | 1 | Número de página (base 1) |
limit | number | 10 | Ítems por página (máx. 100) |
createdFrom | string (ISO 8601) | — | Tickets creados desde esta fecha |
createdTo | string (ISO 8601) | — | Tickets creados hasta esta fecha |
status | string | — | Estado del ticket: open | closed | inProgress | pending | resolved | cancelled |
Respuesta exitosa — 200 OK
{
"message": "message.findAll",
"data": {
"results": [
{
"_id": "507f1f77bcf86cd799439011",
"identifier": "uuid-1234",
"status": "open",
"lastMessage": {
"_id": "507f1f77bcf86cd799439020",
"body": "Hola, necesito ayuda",
"messageType": "chat",
"sendType": "contact",
"ack": "read",
"isRead": true,
"sentAt": "2026-05-20T10:00:00Z",
"sender": {
"_id": "507f1f77bcf86cd799439013",
"username": "juan_perez"
}
},
"contact": {
"_id": "507f1f77bcf86cd799439012",
"username": "juan_perez",
"phone": "+5491112345678",
"isActive": true,
"isBlocked": false,
"labels": []
},
"unreadCount": 3,
"assignedUser": null,
"createdAt": "2026-05-19T09:00:00Z",
"updatedAt": "2026-05-20T10:00:00Z"
}
],
"meta": {
"total": 5,
"page": 1,
"limit": 10,
"totalPages": 1
}
}
}
GET /api/v1/messages/:contactId
Devuelve todos los mensajes del contacto a través de todos sus tickets, ordenados del más reciente al más antiguo. Cada mensaje incluye el sender (Contact o User según sendType) y el parent (mensaje al que responde, si existe).
Query params
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
page | number | 1 | Número de página (base 1) |
limit | number | 10 | Mensajes por página (máx. 100) |
createdFrom | string (ISO 8601) | — | Mensajes enviados desde esta fecha |
createdTo | string (ISO 8601) | — | Mensajes enviados hasta esta fecha |
messageType | string | — | chat | media | system |
sendType | string | — | contact | agent | user | system |
Respuesta exitosa — 200 OK
{
"message": "message.findAll",
"data": {
"results": [
{
"_id": "507f1f77bcf86cd799439021",
"ticketId": "507f1f77bcf86cd799439011",
"body": "¡Claro! Te ayudo ahora.",
"messageType": "chat",
"sendType": "agent",
"ack": "delivered",
"isRead": false,
"readAt": null,
"MediaId": null,
"parentId": "507f1f77bcf86cd799439020",
"createdAt": "2026-05-20T10:05:00Z",
"updatedAt": "2026-05-20T10:05:00Z",
"parent": {
"_id": "507f1f77bcf86cd799439020",
"body": "Hola, necesito ayuda",
"messageType": "chat",
"sendType": "contact",
"createdAt": "2026-05-20T10:00:00Z"
},
"sender": {
"_id": "507f1f77bcf86cd799439030",
"username": "agent_laura",
"firstname": "Laura",
"lastName": "García",
"email": "laura@empresa.com"
}
}
],
"meta": {
"total": 42,
"page": 1,
"limit": 10,
"totalPages": 5
}
}
}
Notas
parentesnullcuando el mensaje no es una respuesta directa.senderexpone campos deContact(username,firstname,lastName,profilePic) o deUser(username,firstname,lastName,email) según elsendType.- El pipeline hace un
$lookupal collectionticketspara filtrar porContactId, por lo que solo se devuelven mensajes de tickets que pertenecen al contacto indicado dentro del tenant.
GET /api/v1/messages/:contactId/:ticketId
Devuelve los mensajes de un ticket específico perteneciente al contacto, ordenados del más reciente al más antiguo. Verifica que el ticket pertenezca al contacto antes de consultar; si no, devuelve 404.
Path params
| Parámetro | Descripción |
|---|---|
contactId | MongoDB ObjectId del contacto |
ticketId | MongoDB ObjectId del ticket |
Query params
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
page | number | 1 | Número de página (base 1) |
limit | number | 10 | Mensajes por página (máx. 100) |
createdFrom | string (ISO 8601) | — | Mensajes enviados desde esta fecha |
createdTo | string (ISO 8601) | — | Mensajes enviados hasta esta fecha |
messageType | string | — | chat | media | system |
sendType | string | — | contact | agent | user | system |
Respuesta exitosa — 200 OK
La forma de cada mensaje es idéntica a la de GET /:contactId, incluyendo parent y sender.
Error — 404 Not Found
Devuelto cuando el ticketId no existe o no pertenece al contactId dentro del tenant del usuario autenticado.
{
"statusCode": 404,
"message": "ticket.notFound"
}
DTOs de filtro
MessageContactFilterDto
Usado por GET /:contactId y GET /:contactId/:ticketId.
class MessageContactFilterDto {
page?: number // default 1, min 1
limit?: number // default 10, max 100
createdFrom?: string // ISO 8601
createdTo?: string // ISO 8601
messageType?: 'chat' | 'media' | 'system'
sendType?: 'contact' | 'agent' | 'user' | 'system'
}
TicketContactFilterDto
Usado por GET /:contactId/tickets-all.
class TicketContactFilterDto {
page?: number // default 1, min 1
limit?: number // default 10, max 100
createdFrom?: string // ISO 8601 — fecha de creación del ticket
createdTo?: string // ISO 8601 — fecha de creación del ticket
status?: 'open' | 'closed' | 'inProgress' | 'pending' | 'resolved' | 'cancelled'
}
Tipos de mensaje
messageType | sendType | Descripción |
|---|---|---|
chat | contact | Mensaje de texto del cliente externo |
chat | agent | Respuesta del agente |
media | cualquiera | Mensaje con archivo adjunto (MediaId) |
system | system | Evento automático (ticket creado, cerrado, etc.) |
Flujo con ChatGateway
El ChatGateway crea los mensajes en BD llamando MessageService tras recibir un evento WebSocket SEND_MESSAGE o SEND_FILE. Los endpoints HTTP son para consulta e historial.
Dependencias del módulo
MessageModule
├── MongooseModule (Message schema)
├── UserModule
└── TicketModule ← importado para los endpoints por contacto
├── TicketService ← getTicketsList, findByContactAndTicket
└── TicketRepository