Configuración (features/settings)
Pantalla de configuración del tenant, solo para administradores. Dos pestañas: General (idioma y zona horaria) y Atención (horario de atención, resolución automática y encuesta CSAT, en tres paneles de acordeón). Cada sección se guarda por separado contra su propio endpoint PATCH. El módulo de backend (TenantSettingsController, settings/) no tiene página propia en esta documentación; el servicio de configuración lo usa la ingesta de mensajes descrita en Message, y el tenant al que pertenece se describe en Tenant.
Ruta
SettingsModule se carga en shared/routes/content.routes.ts bajo settings (PAGE_URLS.SETTINGS) con canActivate: [AuthGuard, permissionGuard] y data: { permission: 'SETTINGS_READ' }. SettingsRoutingModule declara en la ruta vacía canActivate: [AuthGuard, AdminGuard, permissionGuard] con el mismo permiso: además del permiso exige userType === 'admin' (si no, AdminGuard redirige a dashboard). Entrada de menú: sidebarMenu.settings, con permission: 'SETTINGS_READ' (el menú no comprueba el tipo de usuario).
Archivos
| Archivo | Rol |
|---|---|
settings.module.ts / settings-routing.module.ts | Declaran la página y los cuatro componentes; importan ReactiveFormsModule, FormsModule, NgbAccordionModule, SharedModule |
pages/settings-page/settings-page.component.* | Contenedor: carga la configuración, maneja pestañas y reemplaza cada sección al guardarse |
components/general-settings/general-settings.component.* | Formulario de idioma y zona horaria (settings-general) |
components/business-hours-panel/business-hours-panel.component.* | Horario de atención (settings-business-hours-panel) |
components/auto-resolution-panel/auto-resolution-panel.component.* (+ .scss) | Resolución automática (settings-auto-resolution-panel) |
components/csat-panel/csat-panel.component.* | Encuesta de satisfacción (settings-csat-panel) |
settings-routing.module.spec.ts y specs de componentes | Ver Pruebas |
Fuera de la feature: core/services/api/settings.service.ts, core/interfaces/api/settings.interface.ts, core/states/settings/* y shared/validators/notifyBeforeResolve.ts.
Permisos
| Acrónimo | Efecto |
|---|---|
SETTINGS_READ | Ruta y menú (más AdminGuard) |
SETTINGS_UPDATE | Botón "guardar" de cada panel (*appShowWithPermission). "Cancelar" siempre se ve |
Servicios y endpoints
SettingsService (providedIn: 'root') usa API_URLS.SETTINGS (/settings). Rutas verificadas contra tenant-settings.controller.ts y SETTINGS_ROUTES:
| Método | Endpoint | Permiso en el backend | Uso |
|---|---|---|---|
getSettings() | GET /settings/get-all | SETTINGS_READ | Devuelve data.results[0] (un único documento por tenant) |
updateGeneral(payload) | PATCH /settings/general | SETTINGS_UPDATE | Pestaña General |
updateSchedule(payload) | PATCH /settings/attention/schedule | SETTINGS_UPDATE | Horario de atención |
updateAutoResolution(payload) | PATCH /settings/attention/auto-resolution | SETTINGS_UPDATE | Resolución automática |
updateCsat(payload) | PATCH /settings/attention/csat | SETTINGS_UPDATE | CSAT |
Cada PATCH devuelve el TenantSettings completo y el panel usa solo su sección. El backend tiene además PATCH /settings/attention/scheduled-messages, sin método en el servicio ni panel en esta rama.
Modelos
core/interfaces/api/settings.interface.ts:
| Interfaz | Campos |
|---|---|
GeneralSettings | language, timezone |
BusinessHoursSettings | enabled, sendAfterChatbot, useWabot, absenceMessage, days: BusinessHoursDay[] |
BusinessHoursDay | day: WeekDay, enabled, startTime, endTime (WEEK_DAYS, lunes a domingo) |
AutoResolutionSettings | enabled, sendFarewellMessage, resolveInMinutes, notifyInMinutes, notificationMessage, resolveInactiveWabot |
CsatSettings | enabled, sendOnAutoResolve, sendAsInteractiveList, requestMessage, thankYouMessage, requestComment, perRatingMessage: PerRatingMessage (rating1 a rating5) |
ScheduledMessagesSettings | enabled, allowNotes, minLeadTimeMinutes, maxHorizonDays, maxPendingPerTicket, maxPendingPerAgent, onOwnerInactive, onTicketResolved, cancelledSystemMessage (declarada para uso futuro; la pantalla no la edita) |
TenantSettings | _id?, tenantId?, las cuatro secciones, scheduledMessages? |
SettingsState | settings, loading, error |
Página (SettingsPageComponent)
En ngOnInit llama getSettings() y apaga isLoading (también en error). activeTab es 'general' o 'attention' (SettingsTab); onTabChange lo cambia. La pestaña Atención usa un ngbAccordion con tres ítems (business-hours abierto por defecto, auto-resolution, csat); el contenido va en ng-template para que los paneles se creen al expandirse. Cada panel emite saved con su sección actualizada y la página la reemplaza en settings (onGeneralSaved, onBusinessHoursSaved, onAutoResolutionSaved, onCsatSaved).
Paneles y formularios
Patrón común: un setter @Input() settings toma una copia (_savedSnapshot) y vuelca el valor al formulario; onSave evita doble envío (isSaving) y formularios inválidos, llama al servicio, y en éxito actualiza la copia y el formulario con lo devuelto, muestra el toast settings.toast.updated (título settings.title) y emite saved; onCancel restaura la copia guardada. Los mensajes usan app-rich-text-editor con RICH_TEXT_VALIDATIONS.MESSAGE.MAX_LENGTH (1000) y app-switch para los booleanos.
General (GeneralSettingsComponent): language (required, por defecto es; opciones AVAILABLE_LANGUAGES es y en) y timezone (required; select con moment.tz.names() de moment-timezone). Tras guardar despacha change({ language }) del slice de idioma, de modo que la interfaz cambia de idioma de inmediato (ver i18n y tema); si la petición falla no se despacha.
Horario de atención (BusinessHoursPanelComponent): enabled, sendAfterChatbot y useWabot (las dos últimas con la etiqueta "próximamente", words.comingSoon), absenceMessage y un FormArray de siete días con enabled, startTime y endTime (input type="time"). Si la configuración no trae días se generan siete por defecto, deshabilitados, de 09:00 a 18:00. No hay validadores.
Resolución automática (AutoResolutionPanelComponent):
| Campo | Validadores |
|---|---|
enabled | — |
sendFarewellMessage | por defecto true |
resolveInMinutes | required, min(1), por defecto 5 |
notifyInMinutes | min(1), por defecto 1 |
notificationMessage | máximo 1000 (editor) |
resolveInactiveWabot | "próximamente" |
El grupo tiene el validador notifyBeforeResolveValidator: el aviso (notifyInMinutes) debe ser estrictamente menor que resolveInMinutes (error notifyNotBeforeResolve, mensaje settings.attention.autoResolution.notifyLessThanResolve); no se evalúa con la resolución apagada. Con enabled en falso, sendFarewellMessage, resolveInMinutes, notifyInMinutes y notificationMessage se deshabilitan (_syncEnabledState), y el payload usa getRawValue() para incluirlos igualmente. Las mismas reglas aplican a la resolución por departamento, que sobreescribe a la del tenant (ver Departamentos).
CSAT (CsatPanelComponent): enabled, sendOnAutoResolve, sendAsInteractiveList (con "próximamente"), requestComment, requestMessage y thankYouMessage (editor, máximo 1000) y perRatingMessage (FormGroup con cinco input type="text", rating1 a rating5). Sin validadores.
Estado NgRx
Existe un slice settings (core/states/settings/, registrado en app.state.ts junto con SettingsEffects): acciones loadSettings, loadSettingsSuccess y loadSettingsFailure; el efecto llama getSettings() y mapea el error a settings.toast.error; selectores selectTenantSettings, selectSettingsLoading y selectSettingsError. Ningún código dentro de src/app despacha loadSettings, y la página de configuración carga por HTTP directo, no por el store. El slice se creó para que el compositor leyera scheduledMessages.enabled, pero ese dato hoy sale del perfil de usuario (user.featureFlags.scheduledMessagesEnabled, selector selectScheduledMessagesEnabled en auth.selectors.ts) porque los agentes sin SETTINGS_READ no pueden consultar este endpoint. Ver Estado.
No usa sockets.
Flujo
- Abrir
settings:AuthGuard,AdminGuardypermissionGuard(SETTINGS_READ); se pideGET /settings/get-all. - Editar un panel y pulsar guardar (visible con
SETTINGS_UPDATE):PATCHde esa sección → toast → el panel y la página se actualizan. - En General, además cambia el idioma de la interfaz.
- Cancelar restaura el último estado guardado del panel.
Pruebas
Seis specs de Karma/Jasmine, todas dentro de la feature más la del slice:
settings-routing.module.spec.ts: la ruta llevaSETTINGS_READy contienepermissionGuard,AuthGuardyAdminGuard.settings-page.component.spec.ts: carga congetSettings, pestaña inicialgeneral, cambio de pestaña,isLoadingy reemplazo de la sección general al guardar.general-settings.component.spec.ts: población del formulario,updateGeneral, emisión desaved, despacho dechangesolo en éxito, cancelar yisSavingante error.business-hours-panel.component.spec.ts:FormArrayde siete días, campos de primer nivel,updateSchedule,saved, cancelar y error.auto-resolution-panel.component.spec.ts: población,updateAutoResolution(sin controlfarewellMessage),saved, cancelar, validadores (obligatorio, mínimo 1, aviso anterior a la resolución, sin error cruzado si está apagada) y habilitar o deshabilitar dependientes.csat-panel.component.spec.ts: población conperRatingMessageanidado,updateCsat,saved, cancelar y error.
El slice tiene settings.effects.spec.ts y settings.reducers.spec.ts en core/states/settings/. No hay spec de SettingsService.
Gotchas e inconsistencias
- Doble criterio de acceso: la entrada de menú solo declara
permission: 'SETTINGS_READ', mientras que la ruta exige ademásuserTypeadministrador; un usuario con el permiso pero de otro tipo es redirigido porAdminGuardal dashboard. - El slice
settingsno se alimenta:loadSettingsno se despacha en ningún sitio;selectTenantSettingsqueda siempre ennull(ver arriba). - Hay opciones visibles marcadas "próximamente" (
sendAfterChatbot,useWabot,resolveInactiveWabot,sendAsInteractiveList) que sí viajan en el payload. - Los paneles inician con los datos de la primera carga; si otro administrador cambia la configuración, esta pantalla no se entera hasta recargar.
- El backend permite configurar
scheduledMessages(PATCH attention/scheduled-messages), pero la UI no lo expone, por lo que solo se puede cambiar por API. getSettingsasumeresults[0]: si el tenant no tuviera documento de configuración, la página queda sin pestañas (se oculta todo cuandosettingses nulo).