feat(config): modulo Configuraciones admin + permisos por rol

- Tablas ConfiguracionSistema (7 configs) + RolConfiguracion (6 roles)
- Endpoints: config/sistema GET/PATCH, config/roles GET/PATCH,
  config/usuarios GET/PATCH, config/reset-roles POST, config/acciones GET
- modulosDeRol() async con fallback a MATRIZ estatica
- auth/me devuelve acciones[] segun rol del usuario
- saludMonday.estaDisponible() lee modo_local_forzado de DB (cache 30s)
- Landing: vista Configuraciones con 4 tabs (Modo, Roles, Usuarios, Sistema)
- Mi-dia: boton Descartar se deshabilita si !descartar_lead
- 630 API tests + 24 landing tests + 20 mi-dia tests verdes

Seed: npm run seed:config
This commit is contained in:
2026-09-06 09:46:19 +00:00
parent 50e6eca7da
commit b2634f97ab
35 changed files with 2572 additions and 33 deletions

View File

@@ -35,7 +35,7 @@ const MODULOS_VENDEDOR: readonly Modulo[] = [
'chat',
]
const MATRIZ: Record<Rol, readonly Modulo[]> = {
export const MATRIZ: Record<Rol, readonly Modulo[]> = {
vendedor: MODULOS_VENDEDOR,
administracion_venta: [...MODULOS_VENDEDOR, 'cobranza'],
director_ventas: [...MODULOS_VENDEDOR, 'cobranza', 'tablero-comercial'],
@@ -52,14 +52,28 @@ const ROLES_LEGACY: Record<string, Rol> = {
}
/**
* Módulos visibles para un rol. Roles legacy ('coordinador', 'corporativo') se
* tratan como director_ventas; un rol desconocido cae a vendedor (fallback
* seguro: es el conjunto más chico de módulos operativos).
* Módulos visibles para un rol. Intenta leer de RolConfiguracion (DB) y
* hace fallback a la MATRIZ estática si no existe registro.
*
* Roles legacy ('coordinador', 'corporativo') se tratan como director_ventas;
* un rol desconocido cae a vendedor (fallback seguro: es el conjunto más chico
* de módulos operativos).
*/
export function modulosDeRol(rol: string): Modulo[] {
export async function modulosDeRol(rol: string): Promise<Modulo[]> {
const clave = rol.trim().toLowerCase()
const rolEfectivo: Rol =
(ROLES as readonly string[]).includes(clave) ? (clave as Rol) : (ROLES_LEGACY[clave] ?? 'vendedor')
try {
// Importación dinámica para evitar circular dependency con services
const { prisma } = await import('../services/prisma.js')
const row = await prisma.rolConfiguracion.findUnique({ where: { rol: clave } })
if (row) {
return JSON.parse(row.modulos) as Modulo[]
}
} catch {
// Si la tabla aún no existe o hay error de conexión, fallback silencioso
}
return [...MATRIZ[rolEfectivo]]
}