Skip to main content

Departamentos (features/departments)

Administración de los departamentos (colas de atención): nombre, color, saludo, reglas de asignación automática, usuarios que pertenecen a cada uno, exigencia de motivo de contacto y política de resolución automática. Es la única feature de este lote cuyo listado vive en NgRx, porque otras pantallas reutilizan la lista (chat, chat interno). En docs/backend/modules no existe aún una página del módulo departments; los endpoints se describen aquí según el frontend y el controlador del backend.

Ruta​

DepartmentsModule se carga bajo departments (PAGE_URLS.DEPARTMENTS) en shared/routes/content.routes.ts con canActivate: [AuthGuard, permissionGuard] y data: { permission: 'DEPARTMENT_READ' }; DepartmentsRoutingModule repite el permissionGuard con el mismo permiso. Menú: sidebarMenu.departments, permission: 'DEPARTMENT_READ'.

Archivos​

ArchivoRol
departments.module.ts, departments-routing.module.tsDeclaran los cuatro componentes (sin NgSelectModule)
pages/departments-page/*Breadcrumb (sidebarMenu.departments, padre tickets.titleSidebar) y departments-departments-table
components/departments-table/*Lista desde el store, filtro local, acciones por fila; hospeda el filtro
components/departments-filter/*Panel con un único campo name
components/department-modal/*Modal con tres pestañas (departamento, usuarios y asignación, resolución del chat)
shared/validators/notifyBeforeResolve.tsValidador del grupo autoResolution, compartido con la pantalla de Configuración
shared/components/rich-text-editorEditor del saludo (app-rich-text-editor)

Permisos​

AcrónimoEfecto en la UIEn el backend
DEPARTMENT_READRuta y menúget-all, get-one/:id, GET :id/users
DEPARTMENT_CREATEBotón "agregar"create
DEPARTMENT_UPDATEBotón "editar" por filaupdate/:id, PATCH :id/users
DEPARTMENT_DELETEBotón "eliminar" por filaremove/:id

(Columna del backend verificada en department.controller.ts con @RequirePermission.)

Servicios y endpoints​

DepartmentService (core/services/api/department.service.ts, providedIn: 'root', base /departments):

MétodoEndpointUso
getAll()GET /departments/get-allLista completa (data.results); la carga el efecto loadDepartments$
findDepartmentsForSelect(filter)GET /departments/get-all con queryOpciones { value, label, color } y totalCount para selectores de otras pantallas (por ejemplo el panel de transferencia del chat)
getById(id)GET /departments/get-one/:idDevuelve data.results[0] o null; no lo usa esta feature
create(data)POST /departments/createAlta
update(id, data)PATCH /departments/update/:idEdición (id = _id)
remove(id)DELETE /departments/remove/:idBaja
getDepartmentUsers(id)GET /departments/:id/usersUsuarios del departamento con sus marcas
updateDepartmentUsers(id, users)PATCH /departments/:id/usersBody { users: DepartmentUserEntry[] }

Modelos​

core/interfaces/api/department.interface.ts:

InterfazCampos
Department_id, tenantId, name, color, greetingMessage, autoAssignment, assignOfflineUsers, redistributeChats, contactReasonRequirement (none, optional, required), overrideGlobalResolution, autoResolution, createdAt?, updatedAt?
AutoResolutionenabled, sendFarewellMessage, resolveInMinutes, notifyInMinutes, notificationMessage
DepartmentFormname y el resto opcionales (autoResolution parcial)
DepartmentUseruserId, username, email, assignToDepartment, autoAssign
DepartmentUserEntryuserId, assignToDepartment, autoAssign
DepartmentStatedepartments, isLoading, error

Estado NgRx​

Slice departments (core/states/departments/): acciones load, create, update, delete con sus Success y Failure (DepartmentActions), reducer, selectores (selectDepartments, selectDepartmentsIsLoading, selectDepartmentsError) y DepartmentEffects, registrado en core/states/app.state.ts (ver Estado).

  • El reducer pone isLoading en load, reemplaza la lista en loadSuccess, añade en createSuccess, sustituye por _id en updateSuccess y filtra en deleteSuccess.
  • Los efectos muestran toasts (departments.toast.created|updated|deleted) y un toast de error genérico (departments.error.generic) en cualquier Failure.
  • Otras pantallas consumen el slice: ChatListComponent, CreateGroupModalComponent y NewChatModalComponent (estos dos despachan loadDepartments si la lista está vacía).

Tabla y filtro​

DepartmentsTableComponent despacha loadDepartments en ngOnInit y se suscribe a selectDepartments y selectDepartmentsIsLoading (con takeUntil). El filtro es local: _applyFilter compara name en minúsculas con includes sobre la lista del store; no hay paginación de servidor (count = tamaño filtrado). Columnas: color (cuadro con backgroundColor y tooltip del valor), nombre (ordenable), mensaje de saludo y acciones. El botón de recarga despacha loadDepartments.

DepartmentsFilterComponent emite filterApply / filterReset con { name } (recortado). Sus botones usan las claves labels.filter.apply y labels.filter.reset (copiadas de la feature de etiquetas).

Acciones: editar (DEPARTMENT_UPDATE) abre el modal con action = EDIT y department; eliminar (DEPARTMENT_DELETE) abre ConfirmDialogComponent (departments.modal.delete, departments.modal.deleteMessage, icono danger) y, si se confirma, despacha deleteDepartment({ id }). No hay botón "ver".

Se abre con backdrop: 'static', size: 'lg', centered, keyboard: false. Recibe action (ADD o EDIT) y department. Tres pestañas controladas por activeTab.

Pestaña departamento​

CampoValidadores / control
namerequired, maxLength(100)
colorinput type="color", por defecto #000000
greetingMessageapp-rich-text-editor, maxLength RICH_TEXT_VALIDATIONS.MESSAGE.MAX_LENGTH (1000)

Sobre el editor hay botones para insertar variables (GREETING_VARIABLES): {{greetings}}, {{username}}, {{fullName}}, {{phone}}, {{identifier}} y {{email}} (etiquetas en departments.variables.*). insertVariable usa greetingEditor.insertText y, sin editor, concatena al final del valor.

Pestaña usuarios y asignación​

Deshabilitada en alta (muestra departments.tabs.usersAddHint). En edición contiene tres conmutadores globales (autoAssignment, assignOfflineUsers, redistributeChats) y una tabla de usuarios cargada la primera vez que se abre la pestaña (getDepartmentUsers; _usersLoaded evita repetir la petición). Cada fila tiene dos app-switch: assignToDepartment y autoAssign (este último deshabilitado si autoAssignment está apagado). Los cambios se mantienen en memoria en departmentUsers.

Pestaña resolución del chat​

CampoControl / validadores
contactReasonRequirementselect con none, optional, required (CONTACT_REASON_OPTIONS)
overrideGlobalResolutionConmutador; si está apagado todo el grupo autoResolution queda deshabilitado
autoResolution.enabledConmutador; si está apagado se deshabilitan los dependientes
autoResolution.sendFarewellMessageConmutador, por defecto true
autoResolution.resolveInMinutesrequired, min(1), por defecto 5
autoResolution.notifyInMinutesmin(1), por defecto 1
autoResolution.notificationMessageTexto

_watchResolutionToggles / _syncResolutionState habilitan o deshabilitan los controles con emitEvent: false. El validador de grupo notifyBeforeResolveValidator marca notifyNotBeforeResolve cuando notifyInMinutes >= resolveInMinutes, salvo si enabled === false (con la resolución apagada los tiempos no aplican). _buildPayload usa getRawValue(), de modo que envía también valores de controles deshabilitados.

Guardado​

El botón guardar se deshabilita con departmentForm.invalid, isSaving o isLoadingUsers, y se oculta en la pestaña usuarios durante el alta.

  • Pestañas departamento o resolución: create (alta) o update(_id, payload) (edición) vía DepartmentService directamente; en éxito _notifySuccess (toast departments.toast.created|updated) y activeModal.close(resultado).
  • Pestaña usuarios (solo edición): update del departamento y luego updateDepartmentUsers(id, entradas) con { userId, assignToDepartment, autoAssign }; éxito → toast departments.toast.updated y cierre.

Al terminar el modal (hidden, tanto por cierre como por cancelación) la tabla despacha loadDepartments y refresca desde el servidor.

Flujo​

  1. Abrir departments: el guard comprueba DEPARTMENT_READ y la tabla despacha loadDepartments.
  2. Alta: modal (pestañas departamento y resolución) → create → toast → recarga.
  3. Edición: modal poblado desde la fila; la pestaña usuarios descarga los miembros bajo demanda.
  4. Baja: confirmación → deleteDepartment → efecto → remove → reducer elimina la fila → toast departments.toast.deleted.

Sockets y utilidades​

No usa sockets. Reutiliza shared-tables-ngx-datatable, shared-accordions-toogle-panel, ConfirmDialogComponent, app-switch y ToastrNotificationService (ver Compartido).

Pruebas​

department-modal.component.spec.ts cubre: valores por defecto en alta, relleno en edición, títulos, habilitación y deshabilitación del grupo de resolución, validación notifyBeforeResolve (menor, igual, mayor, override apagado, resolveInMinutes requerido), insertVariable, create y update con su payload (incluyendo los tiempos), cierre del modal con el resultado y carga única de usuarios al cambiar a la pestaña. No hay specs para la tabla, el filtro, el reducer ni los efectos de departamentos.

Gotchas e inconsistencias​

  • Efectos de crear y actualizar sin uso desde la UI: el modal llama a DepartmentService.create/update directamente y notifica él mismo. Las acciones createDepartment y updateDepartment y sus efectos existen, pero solo el borrado pasa por NgRx; la lista se actualiza por la recarga posterior al cerrar el modal, no por createSuccess / updateSuccess.
  • Recarga en hidden: ocurre también al cancelar, pidiendo la lista aunque no haya cambios.
  • Cambios de usuarios se pierden en la pestaña equivocada: los conmutadores por usuario se envían solo si se guarda desde la pestaña "usuarios"; guardar desde "departamento" o "resolución" envía solo el departamento (y a la inversa, guardar desde "usuarios" sí reenvía también el departamento).
  • Notificaciones repartidas: el borrado notifica desde deleteDepartmentSuccess$; el alta y la edición notifican desde el modal.
  • Etiquetas de filtro copiadas: usa labels.filter.apply y labels.filter.reset.
  • El filtro y el orden son locales; con muchos departamentos la lista completa se carga siempre.
  • Las variables del saludo ({{...}}) se guardan como texto; su sustitución ocurre en el backend (message-template.service.ts).