- Introduced new functions to download Excel reports for clients and users. - Added UI elements for administrative users to trigger report downloads. - Implemented error handling for report generation failures. - Enhanced the existing portal user listing with node data for better reporting. This update improves the reporting capabilities within the application, allowing for easier access to client and user data.
122 lines
5.3 KiB
TypeScript
122 lines
5.3 KiB
TypeScript
/**
|
|
* 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<string, { cliente: number; autoridad: number }>();
|
|
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'
|
|
}
|
|
});
|
|
};
|