Respuestas rápidas (features/quick-responses)
CRUD de plantillas de mensaje con atajo (shortcut) asociadas a uno o varios departamentos: listado paginado en servidor con filtros, alta, edición, vista de solo lectura y activación o desactivación. Contraparte del backend: QuickResponse. El compositor del Chat consume estas plantillas; los departamentos se gestionan en Departamentos.
Ruta
QuickResponsesModule se carga en shared/routes/content.routes.ts bajo quick-responses (PAGE_URLS.QUICK_RESPONSES) con canActivate: [AuthGuard, permissionGuard] y data: { permission: 'QUICK_RESPONSE_READ' }. QuickResponsesRoutingModule repite permissionGuard con el mismo permiso. Entrada de menú: quickResponses.titleSidebar, con permission: 'QUICK_RESPONSE_READ'.
Archivos
| Archivo | Rol |
|---|---|
quick-responses.module.ts / quick-responses-routing.module.ts | Declaran los cuatro componentes; importan ReactiveFormsModule, FormsModule, NgbModalModule, NgbTooltipModule, NgSelectModule, SharedModule |
pages/quick-responses-page/quick-responses-page.component.* | Compone shared-breadcrumb y quick-responses-table |
components/quick-responses-table/quick-responses-table.component.* | Tabla, paginación, filtros aplicados, modal y activar o desactivar; hospeda el filtro |
components/quick-responses-filter/quick-responses-filter.component.* | Filtro por atajo y estado |
components/quick-response-modal-form/quick-response-modal-form.component.* | Modal de alta, edición y vista |
*.spec.ts | Specs de modal, filtro y tabla |
Permisos
| Acrónimo | Efecto |
|---|---|
QUICK_RESPONSE_READ | Ruta y menú |
QUICK_RESPONSE_CREATE | Botón "agregar" |
QUICK_RESPONSE_UPDATE | Botones "editar" y "activar / desactivar" por fila |
El botón "ver" no tiene restricción adicional. El backend además define QUICK_RESPONSE_DELETE para DELETE remove/:id, que la UI no usa.
Servicios y endpoints
QuickResponseService (core/services/api/quick-response.service.ts, providedIn: 'root') usa API_URLS.QUICK_RESPONSES (/quick-responses). Las mutaciones muestran toast de éxito con res.message (título quickResponses.title).
| Método | Endpoint | Detalle |
|---|---|---|
findPaginated(page, limit, filter?) | GET /quick-responses/get-all | Query page, limit más shortcut, isActive, y opcionalmente search y ticketId |
create(data) | POST /quick-responses/create | |
update(id, data) | PATCH /quick-responses/update/:id | id es el identifier, no el _id |
toggleActive(id) | PATCH /quick-responses/toggle-active/:id | Body vacío; id es el identifier |
remove(id) | DELETE /quick-responses/remove/:id | Sin consumidores en la UI |
Además, DepartmentService.getAll() alimenta el selector de departamentos del modal.
Fuera de esta feature, chat-conversation.component.ts llama findPaginated(1, 10, { search, ticketId }) para el selector de plantillas del compositor (escribiendo el atajo o desde el botón manual) y copia qr.message al mensaje; con ticketId el backend limita a plantillas activas del departamento del ticket (verificado en quick-response.service.spec.ts del backend).
:::note Modelo del backend
QuickResponse describe un modelo con title y body y rutas REST simplificadas. El esquema real (quick-response/models) y el frontend usan identifier, shortcut, message, departmentIds e isActive, con rutas get-all, create, update/:id, toggle-active/:id y remove/:id.
:::
Modelos
core/interfaces/api/quick-response.interface.ts:
| Interfaz | Campos |
|---|---|
QuickResponse | _id, identifier, shortcut, message, departmentIds: string[], isActive, createdAt?, updatedAt? |
QuickResponseForm | shortcut, message, departmentIds, isActive |
QuickResponseFilter | Partial<Omit<QuickResponseForm, 'departmentIds'>> más search?, ticketId? |
QuickResponsePaginatedData | results, meta { total, page, limit } |
Constantes QUICK_RESPONSE_VALIDATIONS (core/helpers/global/quick-response.constant.ts): SHORTCUT 1 a 50 caracteres, MESSAGE 1 a 1000.
Listado y filtros
Mismo patrón que Contactos: currentPage, currentLimit (10), currentFilter; ordenamiento local. Columnas: atajo (título móvil, ordenable), mensaje (subtítulo móvil; se muestra truncado a 25 con los pipes truncate y whatsappMarkup vía innerHTML), activo (insignia sí / no), creado y acciones. QuickResponsesFilterComponent guarda { shortcut, isActive } y emite el filtro con los mismos recortes y conversión a booleano que las demás features.
Formulario (QuickResponseModalFormComponent)
| Campo | Validadores |
|---|---|
shortcut | required, minLength(1), maxLength(50) |
message | required, minLength(1), maxLength(1000); editor app-rich-text-editor (5 filas) con maxLength 1000 |
departmentIds | required; ng-select múltiple con los departamentos (bindLabel name, bindValue _id) |
isActive | por defecto true |
En VIEW se deshabilita. En edición se llama update(quickResponse.identifier, payload).
Flujo
- Abrir
quick-responses: el guard compruebaQUICK_RESPONSE_READy se pide la primera página. - Filtrar: se vuelve a pedir la página 1.
- Alta o edición: modal →
create/update→closed('saved')→ recarga. - Activar o desactivar: el botón de la fila llama
toggleActive(identifier)y recarga la tabla (a diferencia de contactos o usuarios, que parchean la fila).
Estado, sockets y utilidades
No usa NgRx ni sockets. Reutiliza DEFAULT_NGX_DATATABLE_CONFIG, BUTTON_ACTIONS, FORMAT_FOR_DATES, shared-tables-ngx-datatable, app-rich-text-editor y los pipes truncate y whatsappMarkup (ver Compartido).
Pruebas
Tres specs de Karma/Jasmine: quick-response-modal-form.component.spec.ts (ADD, EDIT y VIEW: título, validación, llamadas a create / update), quick-responses-filter.component.spec.ts (armado del filtro y reinicio) y quick-responses-table.component.spec.ts (carga, filtros, paginación, onToggleActive, apertura del modal). No hay spec de QuickResponseService ni de la página.
Gotchas e inconsistencias
- Se usa
identifierenupdateytoggleActive, mientras que contactos, etiquetas y usuarios usan_id; la fila lleva ambos. - El mensaje es texto plano con marcas de WhatsApp:
app-rich-text-editorusa marcadores como*y_y resalta conhighlightWhatsappMarkup. La tabla lo renderiza conwhatsappMarkupy el compositor del chat lo copia tal cual (qr.message). - El modal no controla
isSubmitting; un doble clic puede duplicar la petición. - El botón de activar no pide confirmación.
QuickResponseService.removey el permisoQUICK_RESPONSE_DELETEexisten sin botón en la UI.departmentIdses obligatorio en el formulario: una plantilla sin departamento no se puede guardar desde esta pantalla.