import type { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { prisma } from '../config/database.js'; import { isPlatformStaff } from '../utils/platform-admin.js'; import { AppError } from '../middlewares/error.middleware.js'; import { auditFromReq } from '../utils/audit.js'; async function requireStaff(req: Request) { if (!req.user?.userId) throw new AppError(401, 'No autenticado'); const isStaff = await isPlatformStaff(req.user.userId); if (!isStaff) throw new AppError(403, 'Acceso restringido a staff de plataforma'); } const updateSchema = z.object({ nombre: z.string().min(1).max(200).optional(), precio: z.number().nonnegative().optional(), active: z.boolean().optional(), }); /** Lista todo el catálogo de add-ons (incluye inactivos). */ export async function listCatalogo(req: Request, res: Response, next: NextFunction) { try { await requireStaff(req); const items = await prisma.planAddonCatalogo.findMany({ orderBy: { codename: 'asc' }, include: { _count: { select: { subscriptionAddons: { where: { status: { in: ['authorized', 'pending'] } } } } }, }, }); return res.json({ data: items.map(i => ({ id: i.id, codename: i.codename, nombre: i.nombre, verticalProfile: i.verticalProfile, precio: Number(i.precio), frecuencia: i.frecuencia, active: i.active, delta: i.delta, createdAt: i.createdAt.toISOString(), suscripcionesActivas: i._count.subscriptionAddons, })), }); } catch (err) { return next(err); } } export async function updateCatalogoItem(req: Request, res: Response, next: NextFunction) { try { await requireStaff(req); const id = String(req.params.id); const data = updateSchema.parse(req.body); const before = await prisma.planAddonCatalogo.findUnique({ where: { id } }); if (!before) throw new AppError(404, 'Add-on no encontrado'); const updated = await prisma.planAddonCatalogo.update({ where: { id }, data: { ...(data.nombre !== undefined ? { nombre: data.nombre } : {}), ...(data.precio !== undefined ? { precio: data.precio } : {}), ...(data.active !== undefined ? { active: data.active } : {}), }, }); auditFromReq(req, 'addon.catalogo_updated', { entityType: 'PlanAddonCatalogo', entityId: id, metadata: { codename: before.codename, before: { nombre: before.nombre, precio: Number(before.precio), active: before.active }, after: { nombre: updated.nombre, precio: Number(updated.precio), active: updated.active }, }, }); return res.json({ id: updated.id, codename: updated.codename, nombre: updated.nombre, precio: Number(updated.precio), frecuencia: updated.frecuencia, active: updated.active, }); } catch (err: any) { if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); return next(err); } }