/** * Reporte para asesores: nodos con sus usuarios de portal (cliente y autoridad). * Solo admin. NO incluye contraseñas: portal_users.password_hash es bcrypt * (irreversible) y las credenciales de cliente son confidenciales. * * Dos hojas: * - "Usuarios": una fila por usuario (cliente/autoridad) con su nodo. * - "Nodos": catálogo de nodos con el conteo de usuarios cliente/autoridad. */ import { listPortalUsersWithNode, listClientsCatalog } from '$lib/server/controldesk-pg'; import { getAdminFromCookies, styleHeader, styleDataRow } from '$lib/server/report-excel'; import type { RequestHandler } from './$types'; import ExcelJS from 'exceljs'; // is_authority_client == 1 → Autoridad; cualquier otro valor → Cliente (paridad con la UI). function tipoUsuario(clienteAutoridad: unknown): 'Autoridad' | 'Cliente' { return Number(clienteAutoridad) === 1 ? 'Autoridad' : 'Cliente'; } export const GET: RequestHandler = async ({ cookies }) => { const admin = await getAdminFromCookies(cookies); if (!admin) { return new Response(JSON.stringify({ error: 'No autorizado: requiere sesión de administrador.' }), { status: 403, headers: { 'Content-Type': 'application/json' } }); } let usuarios: any[]; let nodos: any[]; try { [usuarios, nodos] = await Promise.all([listPortalUsersWithNode(), listClientsCatalog()]); } catch (e: any) { return new Response(JSON.stringify({ error: `Error obteniendo usuarios/nodos: ${e.message}` }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } const workbook = new ExcelJS.Workbook(); workbook.creator = 'TransmitirAS - Panel CPANEL'; workbook.created = new Date(); workbook.modified = new Date(); // ── Hoja: Usuarios (cliente/autoridad) ───────────────────────────────── const wsUsuarios = workbook.addWorksheet('Usuarios', { views: [{ state: 'frozen', ySplit: 1 }] }); wsUsuarios.columns = [ { header: 'Nodo/SubNodo', key: 'NodoSubNodo', width: 22 }, { header: 'Cliente', key: 'Cliente', width: 32 }, { header: 'Tipo', key: 'Tipo', width: 12 }, { header: 'Usuario', key: 'Usuario', width: 24 }, { header: 'Nombre', key: 'Nombre', width: 30 }, { header: 'BD Shelter', key: 'BD_Shelter', width: 20 }, { header: 'Base de Datos', key: 'BDName', width: 22 } ]; styleHeader(wsUsuarios.getRow(1)); usuarios.forEach((u, i) => { const row = wsUsuarios.addRow({ NodoSubNodo: u.NodoSubNodo ?? '—', Cliente: u.Cliente ?? '', Tipo: tipoUsuario(u.ClienteAutoridad), Usuario: u.Usuario ?? '', Nombre: u.Nombre ?? '', BD_Shelter: u.BD_Shelter ?? '', BDName: u.BDName ?? '' }); styleDataRow(row, i); }); wsUsuarios.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: 7 } }; // ── Hoja: Nodos (con conteo de usuarios por tipo) ─────────────────────── const conteo = new Map(); for (const u of usuarios) { // Agrupar por nodo usando la clave de nodo (los usuarios sin nodo quedan fuera del conteo por nodo). const key = String(u.NodoSubNodo ?? ''); if (!key) continue; const acc = conteo.get(key) ?? { cliente: 0, autoridad: 0 }; if (tipoUsuario(u.ClienteAutoridad) === 'Autoridad') acc.autoridad += 1; else acc.cliente += 1; conteo.set(key, acc); } nodos.sort((a, b) => String(a.NodoSubNodo ?? '').localeCompare(String(b.NodoSubNodo ?? ''))); const wsNodos = workbook.addWorksheet('Nodos', { views: [{ state: 'frozen', ySplit: 1 }] }); wsNodos.columns = [ { header: 'Nodo/SubNodo', key: 'NodoSubNodo', width: 22 }, { header: 'Cliente', key: 'Nombre', width: 36 }, { header: 'Base de Datos', key: 'BDName', width: 24 }, { header: 'Estatus', key: 'Estatus', width: 12 }, { header: 'Usuarios Cliente', key: 'UsuariosCliente', width: 16 }, { header: 'Usuarios Autoridad', key: 'UsuariosAutoridad', width: 18 } ]; styleHeader(wsNodos.getRow(1)); nodos.forEach((n, i) => { const c = conteo.get(String(n.NodoSubNodo ?? '')) ?? { cliente: 0, autoridad: 0 }; const row = wsNodos.addRow({ NodoSubNodo: n.NodoSubNodo ?? '', Nombre: n.Nombre ?? '', BDName: n.BDName ?? '', Estatus: Number(n.Activo) === 1 || n.Activo === true ? 'Activo' : 'Inactivo', UsuariosCliente: c.cliente, UsuariosAutoridad: c.autoridad }); styleDataRow(row, i); }); wsNodos.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: 6 } }; const buffer = await workbook.xlsx.writeBuffer(); const fechaHoy = new Date().toISOString().slice(0, 10); return new Response(buffer as unknown as BodyInit, { headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Disposition': `attachment; filename="reporte_nodos_usuarios_${fechaHoy}.xlsx"`, 'Cache-Control': 'no-store' } }); };