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

@@ -5,6 +5,7 @@ import { prisma } from './prisma.js'
import { config } from '../config/index.js'
import { modulosDeRol, type Modulo } from '../config/roles.js'
import { AppError } from '../utils/errors.js'
import * as configService from './config.service.js'
export interface UsuarioPublico {
nombre: string
@@ -14,6 +15,8 @@ export interface UsuarioPublico {
mondayUserId: number | null
// Módulos del ecosistema visibles para el rol (matriz de config/roles.ts)
modulos: Modulo[]
// Acciones granulares del rol (catálogo de config/acciones.ts)
acciones: string[]
}
export interface LoginResult {
@@ -34,13 +37,23 @@ export function normalizarUsername(nombre: string): string {
.replace(/\s+/g, ' ')
}
export function aPublico(usuario: Usuario): UsuarioPublico {
export async function aPublico(usuario: Usuario): Promise<UsuarioPublico> {
let acciones: string[] = []
try {
const acc = await configService.obtenerAcciones(usuario.rol)
acciones = Object.entries(acc)
.filter(([, v]) => v)
.map(([k]) => k)
} catch {
// Si falla la lectura de config, devuelve acciones vacías (no rompe)
}
return {
nombre: usuario.nombre,
rol: usuario.rol,
asignacion: usuario.asignacion,
mondayUserId: usuario.mondayUserId,
modulos: modulosDeRol(usuario.rol),
modulos: await modulosDeRol(usuario.rol),
acciones,
}
}
@@ -64,7 +77,7 @@ export async function login(username: string, password: string): Promise<LoginRe
config.auth.jwtSecret,
{ expiresIn: config.auth.jwtExpiresIn } as jwt.SignOptions,
)
return { token, usuario: aPublico(usuario) }
return { token, usuario: await aPublico(usuario) }
}
/** Payload del JWT emitido por login() */