/** * Servicio de configuración del sistema. * * Lee/escribe las tablas ConfiguracionSistema y RolConfiguracion, y opera * sobre usuarios (rol, asignación, activo). Singleton con cache de 30 s en * `obtenerConfig` para no golpear la DB en cada request. */ import { prisma } from './prisma.js' import { accionesPorRol, type AccionClave } from '../config/acciones.js' import { MATRIZ } from '../config/roles.js' import type { Modulo } from '../config/roles.js' // ── Cache de 30 s para obtenerConfig ──────────────────────────────────── const CACHE_TTL_MS = 30_000 let cacheMap: Record | null = null let cacheExpiresAt = 0 function invalidarCache(): void { cacheMap = null cacheExpiresAt = 0 } // ── ConfiguracionSistema ─────────────────────────────────────────────── /** Todas las configs como Record */ export async function obtenerConfigs(): Promise> { const rows = await prisma.configuracionSistema.findMany() const result: Record = {} for (const r of rows) result[r.clave] = r.valor return result } /** Upsert parcial de configs (solo las claves enviadas) */ export async function actualizarConfigs(parcial: Record): Promise { await prisma.$transaction( Object.entries(parcial).map(([clave, valor]) => prisma.configuracionSistema.upsert({ where: { clave }, create: { clave, valor }, update: { valor }, }), ), ) await refrescarCache() } /** Una config individual (cache 30 s). Devuelve null si la clave no existe. */ export async function obtenerConfig(clave: string): Promise { const now = Date.now() if (cacheMap && now < cacheExpiresAt) return cacheMap[clave] ?? null cacheMap = await obtenerConfigs() cacheExpiresAt = now + CACHE_TTL_MS return cacheMap[clave] ?? null } /** * Versión síncrona de obtenerConfig: lee del cache en memoria. * Devuelve undefined si el cache no ha sido inicializado aún (el caller * debe usar el fallback apropiado, p. ej. process.env). * Se alimenta con `refrescarCache()` (llamar en startup y al cambiar configs). */ export function obtenerConfigSync(clave: string): string | undefined { if (!cacheMap) return undefined return cacheMap[clave] } /** Fuerza la recarga del cache desde la DB (llamar en startup y al guardar configs) */ export async function refrescarCache(): Promise { cacheMap = await obtenerConfigs() cacheExpiresAt = Date.now() + CACHE_TTL_MS } /** Resetea el cache (para tests) */ export function resetCacheForTesting(): void { cacheMap = null cacheExpiresAt = 0 } // ── RolConfiguracion ─────────────────────────────────────────────────── export interface RolConfigData { modulos: string[] acciones: Record } /** Todas las configs de rol (sin parsear JSON) */ export async function obtenerRolesConfig() { return prisma.rolConfiguracion.findMany({ orderBy: { rol: 'asc' } }) } /** Config de un rol (JSON parseado) */ export async function obtenerRolConfig(rol: string): Promise { const row = await prisma.rolConfiguracion.findUnique({ where: { rol } }) if (!row) return null return { modulos: JSON.parse(row.modulos) as string[], acciones: JSON.parse(row.acciones) as Record, } } /** Upsert de módulos y acciones de un rol */ export async function actualizarRolConfig( rol: string, data: { modulos: string[]; acciones: Record }, ): Promise { await prisma.rolConfiguracion.upsert({ where: { rol }, create: { rol, modulos: JSON.stringify(data.modulos), acciones: JSON.stringify(data.acciones), }, update: { modulos: JSON.stringify(data.modulos), acciones: JSON.stringify(data.acciones), }, }) } /** Borra todas las RolConfiguracion y re-siembra desde MATRIZ + ACCIONES_POR_ROL_DEFAULT */ export async function resetRoles(): Promise { await prisma.rolConfiguracion.deleteMany() const entries = Object.entries(MATRIZ) as [string, readonly Modulo[]][] await prisma.$transaction( entries.map(([rol, modulos]) => { const acciones = accionesPorRol(rol) return prisma.rolConfiguracion.create({ data: { rol, modulos: JSON.stringify([...modulos]), acciones: JSON.stringify(acciones), }, }) }), ) } // ── Acciones por rol ─────────────────────────────────────────────────── /** Las acciones booleanas de un rol (lee de DB; fallback a defaults) */ export async function obtenerAcciones(rol: string): Promise> { const row = await prisma.rolConfiguracion.findUnique({ where: { rol } }) if (!row) return accionesPorRol(rol) try { return JSON.parse(row.acciones) as Record } catch { return accionesPorRol(rol) } } // ── Usuarios ─────────────────────────────────────────────────────────── export interface UsuarioListado { id: string username: string nombre: string rol: string asignacion: string | null activo: boolean mondayUserId: number | null } /** Lista de usuarios sin passwordHash */ export async function obtenerUsuarios(): Promise { const rows = await prisma.usuario.findMany({ select: { id: true, username: true, nombre: true, rol: true, asignacion: true, activo: true, mondayUserId: true, }, orderBy: { nombre: 'asc' }, }) return rows } /** Actualiza rol, asignación y/o activo de un usuario */ export async function actualizarUsuario( id: string, data: { rol?: string; asignacion?: string | null; activo?: boolean }, ): Promise { // Filtra undefined para que Prisma solo actualice los campos enviados const clean: Record = {} if (data.rol !== undefined) clean.rol = data.rol if (data.asignacion !== undefined) clean.asignacion = data.asignacion if (data.activo !== undefined) clean.activo = data.activo await prisma.usuario.update({ where: { id }, data: clean as never }) }