Usuarios (features/users)
CRUD de usuarios (agentes y administradores) del tenant: listado paginado en servidor con filtros, alta, edición, vista de solo lectura y activación/desactivación. Contraparte del backend: User. Los roles que se asignan se gestionan en Control de acceso.
Ruta
UsersModule se carga en shared/routes/content.routes.ts bajo users (PAGE_URLS.USERS) con canActivate: [AuthGuard, permissionGuard] y data: { permission: 'USER_READ' }. UsersRoutingModule repite el permissionGuard con el mismo permiso en la ruta vacía. Entrada de menú: sidebarMenu.users, con permission: 'USER_READ'.
Archivos
| Archivo | Rol |
|---|---|
users.module.ts / users-routing.module.ts | Declaran los cuatro componentes; importan ReactiveFormsModule, FormsModule, NgbModalModule, NgbTooltipModule, NgSelectModule, SharedModule |
pages/users-page/users-page.component.* | Solo compone shared-breadcrumb (users.titleSidebar, ruta padre tickets.titleSidebar) y users-users-table |
components/users-table/users-table.component.* | Tabla, paginación, filtros aplicados, conmutador de activo y apertura de modales; hospeda el filtro |
components/users-filter/users-filter.component.* | Panel de filtros (shared-accordions-toogle-panel) |
components/user-modal-form/user-modal-form.component.* | Modal de alta, edición y vista |
Permisos
| Acrónimo | Efecto |
|---|---|
USER_READ | Ruta y menú; también necesario en el backend para get-all y get-one |
USER_CREATE | Botón "agregar" (users.modal.add) |
USER_UPDATE | Botón "editar" por fila y conmutador de estado activo |
El botón "ver" no tiene restricción adicional. No hay botón de eliminar en la UI, aunque el backend expone DELETE remove/:id con USER_DELETE (verificado en el controlador del backend).
Servicios y endpoints
CrmUserService (core/services/api/crm-user.service.ts, providedIn: 'root') usa environment.apiUrl + API_URLS.USERS (/users) + API_ENDPOINTS.
| Método | Endpoint | Detalle |
|---|---|---|
findUsersPaginated(page, limit, filter?) | GET /users/get-all | Query page, limit más los filtros; devuelve { results, total } leyendo data.results y data.meta.total |
createUser(data) | POST /users/create | Muestra toast de éxito con res.message (título users.title) |
updateUser(id, data) | PATCH /users/update/:id | id es el _id del usuario; mismo toast |
toggleActive(id) | PATCH /users/toggle-active/:id | Body vacío; devuelve el usuario actualizado |
Además, CrmRoleService.getAll() (GET /roles/get-all) alimenta el selector de roles. Los endpoints de sesión (get-me, me/signature, me/avatar) viven en UserService y los usa Perfil y Auth.
:::note Rutas del backend
User lista rutas REST simplificadas (GET /users/:id, POST /users). El frontend usa las rutas reales por CRUD_ROUTES (get-all, create, update/:id, toggle-active/:id); ver la nota de Endpoints.
:::
Modelos
core/interfaces/api/crm-user.interface.ts:
| Interfaz | Campos |
|---|---|
CrmUser | _id, identifier, tenantId, username, email, roleId?: string | { _id, name, acronym }, isActive, firstname, secondFirstname?, lastName, secondLastName?, userType, createdAt?, updatedAt? |
CrmUserForm | username, email, password?, roleId, firstname, secondFirstname?, lastName, secondLastName?, userType, isActive? |
CrmUserFilter | username?, email?, firstname?, lastName?, userType?, isActive?: boolean |
CrmUserPaginatedData | results, meta { total, page, limit } |
Constantes en core/helpers/global/crm-user.constant.ts: USER_TYPE (admin, agent), USER_TYPE_OPTIONS (etiquetas users.userTypes.admin = "Administrador" y users.userTypes.agent = "Operador") y CRM_USER_VALIDATIONS.
Listado y filtros
UsersTableComponent guarda currentPage, currentLimit (10 por defecto) y currentFilter; fetchPage pide al servidor y fija count, page y limit en tableConfig (el ordenamiento es local, externalSorting: false). onPageChange y onLimitChange (este vuelve a la página 1) vuelven a consultar. Recargar, aplicar o limpiar filtros reinician en la página 1.
Columnas: usuario (username, título móvil), nombre, apellido (subtítulo móvil), email (no ordenable), tipo (badge con users.userTypes.<tipo>), activo, creado y acciones. La celda "activo" es un app-switch visible solo con USER_UPDATE; onToggleActive guarda togglingId para deshabilitarlo durante la petición y, en éxito, copia isActive del usuario devuelto a la fila (no recarga la tabla).
UsersFilterComponent usa un objeto plano con ngModel (username, email, firstname, lastName, userType, isActive) y emite filterApply / filterReset. onApply recorta espacios, omite campos vacíos y convierte isActive de 'true'/'false' a booleano; calcula activeFiltersCount. Los botones aplicar/limpiar los dispara el panel compartido mediante los atributos data-filter-action, detectados por los selectores de core/helpers/ui/toogle-panel.constant.ts.
Formulario (UserModalFormComponent)
Recibe action (ADD, EDIT, VIEW) y user. Carga los roles en ngOnInit. En VIEW el formulario queda deshabilitado, se oculta el campo contraseña y el pie.
| Campo | Validadores |
|---|---|
username | required, minLength(2), maxLength(50) |
email | required, email, maxLength(150) |
password | en alta: required, minLength(8); en edición opcional (pista users.fields.passwordHint); maxlength HTML 100 |
roleId | required; select nativo con todos los roles (CrmRole), opción vacía "—" |
userType | required; por defecto admin |
firstname, lastName | required, maxLength(80) |
secondFirstname, secondLastName | maxLength(80) |
isActive | por defecto true (checkbox) |
_buildPayload recorta contraseña y segundos nombres y los omite si están vacíos; en edición, si no se escribió contraseña se elimina del payload. roleId se normaliza con typeof roleId === 'string' ? roleId : roleId?._id. Los errores de campo muestran words.fieldRequired, words.invalidEmail o words.minLength (con min).
Flujo
- Abrir
users: el guard compruebaUSER_READy la tabla pide la primera página. - Filtrar: el panel emite un
CrmUserFiltery se vuelve a pedir la página 1. - Alta: modal →
createUser→ toast con el mensaje del backend →closed('saved')→ recarga. - Edición: modal con datos poblados →
updateUser(_id, payload)→ recarga. - Activar o desactivar: switch en la fila →
toggleActive(_id)→ la fila se actualiza.
Estado, sockets y utilidades
No usa NgRx ni sockets. El estado vive en los componentes. Reutiliza DEFAULT_NGX_DATATABLE_CONFIG, BUTTON_ACTIONS, FORMAT_FOR_DATES, app-switch, shared-tables-ngx-datatable y shared-accordions-toogle-panel (ver Compartido).
Pruebas
Esta feature no tiene specs. CrmUserService tampoco.
Gotchas e inconsistencias
- Dos servicios de usuario:
CrmUserService(CRUD,/users/...) yUserService(get-me, firma, avatar).UserService.API_URLapunta a/user(singular) pero no se usa; sus llamadas reales van a/users/.... userTypelimitado a dos valores en la UI (admin,agent), mientras queTYPES_USER_PERMISSIONS(helpers/global/user-management.constant.ts) enumera cinco (ver Auth, interceptores y RBAC).- El modal no tiene control de
isSubmitting: el botón guardar solo se deshabilita porinvalid, de modo que un doble clic rápido puede enviar dos peticiones (los modales de access-control sí protegen esto). updateUserytoggleActivereciben_id, mientras que roles, permisos y módulos usanidentifieren sus URLs.- No hay eliminación de usuarios en la UI; se desactivan.
- El rol y el
userTypeson independientes: el primero decide permisos, el segundo solo el tipo (por ejemplo, la directivaappHideForUserTypesyAdminGuard).