/** * Endpoints de administración de configuración. * * Protegidos con requireAdmin (admin o manager) excepto /acciones que * cualquiera autenticado puede consultar. Las operaciones sensibles —crear * usuarios y editar permisos de roles— usan requireAdminEstricto (solo admin): * el rol manager no las puede hacer. */ import type { NextFunction, Request, Response } from 'express' import { z } from 'zod' import * as configService from '../services/config.service.js' import { esRolValido } from '../config/roles.js' import { esAdmin, esAdminOManager } from '../utils/roles.js' import { AppError, BadRequestError } from '../utils/errors.js' // ── Middleware admin ──────────────────────────────────────────────────── export function requireAdmin(req: Request, _res: Response, next: NextFunction): void { if (!esAdminOManager(req.usuarioToken?.rol)) { throw new AppError(403, 'Se requiere rol admin o manager') } next() } /** Solo admin: crear usuarios y editar permisos de roles (manager NO) */ export function requireAdminEstricto(req: Request, _res: Response, next: NextFunction): void { if (!esAdmin(req.usuarioToken?.rol)) { throw new AppError(403, 'Solo un administrador puede hacer esta operación') } next() } // ── GET /api/config/sistema ──────────────────────────────────────────── export async function obtenerConfigsEndpoint(_req: Request, res: Response): Promise { const configs = await configService.obtenerConfigs() res.json(configs) } // ── PATCH /api/config/sistema ────────────────────────────────────────── const actualizarConfigsSchema = z.record(z.string(), z.string()) export async function actualizarConfigsEndpoint(req: Request, res: Response): Promise { const parcial = actualizarConfigsSchema.parse(req.body) if (Object.keys(parcial).length === 0) throw new BadRequestError('Body vacío') await configService.actualizarConfigs(parcial) res.status(204).end() } // ── GET /api/config/roles ────────────────────────────────────────────── export async function obtenerRolesConfigEndpoint(_req: Request, res: Response): Promise { const roles = await configService.obtenerRolesConfig() // Parsear JSON para que el frontend reciba objetos res.json( roles.map((r) => ({ rol: r.rol, modulos: JSON.parse(r.modulos) as string[], acciones: JSON.parse(r.acciones) as Record, updatedAt: r.updatedAt, })), ) } // ── PATCH /api/config/roles/:rol ─────────────────────────────────────── const actualizarRolSchema = z.object({ modulos: z.array(z.string()), acciones: z.record(z.string(), z.boolean()), }) export async function actualizarRolConfigEndpoint(req: Request, res: Response): Promise { const rol = req.params.rol as string if (!rol) throw new BadRequestError('Falta el parámetro :rol') const body = actualizarRolSchema.parse(req.body) await configService.actualizarRolConfig(rol, body) res.status(204).end() } // ── GET /api/config/usuarios ─────────────────────────────────────────── export async function obtenerUsuariosEndpoint(_req: Request, res: Response): Promise { const usuarios = await configService.obtenerUsuarios() res.json(usuarios) } // ── PATCH /api/config/usuarios/:id ───────────────────────────────────── const actualizarUsuarioSchema = z.object({ rol: z.string().optional(), asignacion: z.string().nullable().optional(), activo: z.boolean().optional(), }) export async function actualizarUsuarioEndpoint(req: Request, res: Response): Promise { const id = req.params.id as string if (!id) throw new BadRequestError('Falta el parámetro :id') const parsed = actualizarUsuarioSchema.parse(req.body) // El rol es un permiso: solo el administrador puede cambiarlo (manager no) if (parsed.rol !== undefined && !esAdmin(req.usuarioToken?.rol)) { throw new AppError(403, 'Solo un administrador puede cambiar el rol de un usuario') } // Filtra undefined para enviar solo campos con valor const body: { rol?: string; asignacion?: string | null; activo?: boolean } = {} if (parsed.rol !== undefined) body.rol = parsed.rol if (parsed.asignacion !== undefined) body.asignacion = parsed.asignacion if (parsed.activo !== undefined) body.activo = parsed.activo if (Object.keys(body).length === 0) throw new BadRequestError('Body vacío') await configService.actualizarUsuario(id, body) res.status(204).end() } // ── POST /api/config/usuarios — dar de alta un usuario (solo admin) ──── const crearUsuarioSchema = z.object({ nombre: z.string().trim().min(3, 'Nombre demasiado corto').max(80), rol: z.string().min(1), asignacion: z.string().nullable().optional(), password: z.string().min(8, 'La contraseña necesita al menos 8 caracteres').max(72), }) export async function crearUsuarioEndpoint(req: Request, res: Response): Promise { const body = crearUsuarioSchema.parse(req.body) if (!esRolValido(body.rol)) throw new BadRequestError(`Rol inválido: ${body.rol}`) const usuario = await configService.crearUsuario({ nombre: body.nombre, rol: body.rol, asignacion: body.asignacion ?? null, password: body.password, }) res.status(201).json(usuario) } // ── POST /api/config/reset-roles ─────────────────────────────────────── export async function resetRolesEndpoint(_req: Request, res: Response): Promise { await configService.resetRoles() res.status(204).end() } // ── GET /api/config/acciones ─────────────────────────────────────────── export async function obtenerAccionesEndpoint(req: Request, res: Response): Promise { if (!req.usuarioToken) throw new BadRequestError('Falta el token verificado') const acciones = await configService.obtenerAcciones(req.usuarioToken.rol) res.json(acciones) }