Tickets bloqueados (features/locked-tickets)
Vista de administración que lista todos los tickets bloqueados del tenant (un ticket queda bloqueado por el agente que lo "mantiene") y permite liberarlos a la fuerza. Es una salida de emergencia para el bloqueo de tickets, protegida por un permiso propio. El módulo de backend es Ticket; el uso normal del bloqueo está en el Chat.
Ruta
LockedTicketsModule se carga en shared/routes/content.routes.ts bajo locked-tickets (PAGE_URLS.LOCKED_TICKETS) con canActivate: [AuthGuard, permissionGuard] y data: { permission: 'TICKET_UNLOCK_ANY' }. LockedTicketsRoutingModule repite permissionGuard con el mismo permiso. Entrada de menú: lockedTickets.titleSidebar, dentro del submenú de mensajería (sidebarMenu.messaging), con permission: 'TICKET_UNLOCK_ANY'. El comentario en nav.constant.ts y en la ruta aclara que se usa un permiso dedicado y no TICKET_READ ni TICKET_UPDATE.
Archivos
| Archivo | Rol |
|---|---|
locked-tickets.module.ts / locked-tickets-routing.module.ts | Declaran página y tabla; importan NgbTooltipModule, TranslateModule, SharedModule (sin formularios) |
pages/locked-tickets-page/locked-tickets-page.component.* | Compone shared-breadcrumb (lockedTickets.titleSidebar, ruta padre tickets.titleSidebar) y locked-tickets-table |
components/locked-tickets-table/locked-tickets-table.component.* | Tabla, paginación y acción de liberar |
components/locked-tickets-table/locked-tickets-table.component.spec.ts | Spec de la tabla |
Permisos
| Acrónimo | Efecto |
|---|---|
TICKET_UNLOCK_ANY | Ruta, menú y botón "liberar" (*appShowWithPermission) |
Servicios y endpoints
No tiene servicio propio: reutiliza TicketService (core/services/api/ticket.service.ts), como explica el comentario del componente.
| Método | Endpoint | Detalle |
|---|---|---|
getLockedTickets({ page, limit }) | GET /tickets/get-all con lockedOnly=true | Delega en getTickets; el backend añade el filtro lockedBy != null. Respuesta data.results y data.meta.total |
forceUnlockTicket(id) | PATCH /tickets/force-unlock/:id | Body vacío; el backend exige TICKET_UNLOCK_ANY y difunde el cambio de bloqueo por socket |
:::caution Permiso del listado
En el backend, GET get-all exige TICKET_READ (ticket.controller.ts), mientras que la ruta del frontend solo pide TICKET_UNLOCK_ANY. Un usuario con TICKET_UNLOCK_ANY pero sin TICKET_READ entraría a la pantalla y el listado fallaría.
:::
Modelos
Usa Ticket y TicketsFilter de core/interfaces/api/ticket.interface.ts. Campos relevantes: lockedBy?: string | null (id del agente), lockedByUser?: TicketAssignedUser | null (poblado por una consulta lockedByUserStages() solo en lecturas de lista y detalle), contact e identifier. TicketsFilter.lockedOnly?: boolean activa el filtro. No define interfaces propias.
Listado
LockedTicketsTableComponent guarda currentPage y currentLimit (10) y pide una página al servidor en fetchPage; actualiza count, page y limit de tableConfig. No tiene filtros. Columnas (ninguna ordenable): identificador (título móvil), contacto (subtítulo móvil), bloqueado por y acciones.
getContactName:firstnamemáslastName; si no hay,username; si no hay contacto, vacío (se muestra—).getLockedByName: igual conlockedByUser; si queda vacío se muestralockedTickets.fields.lockedByUserUnknown.- Botón de recarga (
words.reloadTable) que vuelve a la página 1.
Flujo
- Abrir
locked-tickets: el guard compruebaTICKET_UNLOCK_ANYy se pide la primera página conlockedOnly. - Liberar:
releaseTicketabreConfirmDialogService.confirm(título, mensaje y etiquetaslockedTickets.releaseConfirm.*, iconowarning). - Si se confirma,
forceUnlockTicket(_id)y, en éxito,loadTickets()(vuelve a la página 1). Si se cancela, no se llama al backend.
Estado, sockets y utilidades
Esta feature no usa NgRx ni sockets: el comentario explica que reutilizar el slice de tickets del chat corrompería su filtro activo y la lógica removeIfFilterMismatch. El backend sí difunde ticket_lock_changed al liberar; el chat-socket.service.ts lo procesa (ticketLockChangedFromSocket) para el chat, pero esta tabla no lo escucha, así que no se refresca sola. Reutiliza DEFAULT_NGX_DATATABLE_CONFIG, shared-tables-ngx-datatable, ConfirmDialogService y *appShowWithPermission (ver Compartido).
Pruebas
locked-tickets-table.component.spec.ts (12 casos): carga inicial con página 1 y límite 10, población de tickets, isLoading, onPageChange, onLimitChange, getContactName, getLockedByName y releaseTicket (no llama a forceUnlockTicket si se cancela; llama y recarga si se confirma). No hay spec de la página ni del módulo de rutas.
Gotchas e inconsistencias
- Desajuste de permisos entre ruta (
TICKET_UNLOCK_ANY) y listado (TICKET_READ), descrito arriba. - La lista no se actualiza por socket: otro administrador o el propio agente pueden liberar el ticket y la fila seguirá visible hasta recargar.
getLockedByNameaccede afirstname,lastNameyusernamecon(user as any)aunqueTicketAssignedUserya los declara opcionales; el cast es innecesario.getLockedTicketspasalockedOnlyporHttpParamsconfromObject, de modo que viaja como la cadenatrue.- La acción no ofrece motivo ni nota: libera el bloqueo sin más datos.