feat(export): add Excel export for CFDIs and reports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-01-22 02:58:19 +00:00
parent b25816df20
commit 6d59c8d842
5 changed files with 174 additions and 1 deletions

View File

@@ -0,0 +1,114 @@
import ExcelJS from 'exceljs';
import { prisma } from '../config/database.js';
export async function exportCfdisToExcel(
schema: string,
filters: { tipo?: string; estado?: string; fechaInicio?: string; fechaFin?: string }
): Promise<Buffer> {
let whereClause = 'WHERE 1=1';
const params: any[] = [];
let paramIndex = 1;
if (filters.tipo) {
whereClause += ` AND tipo = $${paramIndex++}`;
params.push(filters.tipo);
}
if (filters.estado) {
whereClause += ` AND estado = $${paramIndex++}`;
params.push(filters.estado);
}
if (filters.fechaInicio) {
whereClause += ` AND fecha_emision >= $${paramIndex++}`;
params.push(filters.fechaInicio);
}
if (filters.fechaFin) {
whereClause += ` AND fecha_emision <= $${paramIndex++}`;
params.push(filters.fechaFin);
}
const cfdis = await prisma.$queryRawUnsafe<any[]>(`
SELECT uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado,
rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor,
subtotal, descuento, iva, isr_retenido, iva_retenido, total,
moneda, metodo_pago, forma_pago, uso_cfdi, estado
FROM "${schema}".cfdis
${whereClause}
ORDER BY fecha_emision DESC
`, ...params);
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('CFDIs');
sheet.columns = [
{ header: 'UUID', key: 'uuid_fiscal', width: 40 },
{ header: 'Tipo', key: 'tipo', width: 10 },
{ header: 'Serie', key: 'serie', width: 10 },
{ header: 'Folio', key: 'folio', width: 10 },
{ header: 'Fecha Emisión', key: 'fecha_emision', width: 15 },
{ header: 'RFC Emisor', key: 'rfc_emisor', width: 15 },
{ header: 'Nombre Emisor', key: 'nombre_emisor', width: 30 },
{ header: 'RFC Receptor', key: 'rfc_receptor', width: 15 },
{ header: 'Nombre Receptor', key: 'nombre_receptor', width: 30 },
{ header: 'Subtotal', key: 'subtotal', width: 15 },
{ header: 'IVA', key: 'iva', width: 15 },
{ header: 'Total', key: 'total', width: 15 },
{ header: 'Estado', key: 'estado', width: 12 },
];
sheet.getRow(1).font = { bold: true };
sheet.getRow(1).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF4472C4' },
};
sheet.getRow(1).font = { bold: true, color: { argb: 'FFFFFFFF' } };
cfdis.forEach(cfdi => {
sheet.addRow({
...cfdi,
fecha_emision: new Date(cfdi.fecha_emision).toLocaleDateString('es-MX'),
subtotal: Number(cfdi.subtotal),
iva: Number(cfdi.iva),
total: Number(cfdi.total),
});
});
const buffer = await workbook.xlsx.writeBuffer();
return Buffer.from(buffer);
}
export async function exportReporteToExcel(
schema: string,
tipo: 'estado-resultados' | 'flujo-efectivo',
fechaInicio: string,
fechaFin: string
): Promise<Buffer> {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet(tipo === 'estado-resultados' ? 'Estado de Resultados' : 'Flujo de Efectivo');
if (tipo === 'estado-resultados') {
const [totales] = await prisma.$queryRawUnsafe<[{ ingresos: number; egresos: number }]>(`
SELECT
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN subtotal ELSE 0 END), 0) as ingresos,
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN subtotal ELSE 0 END), 0) as egresos
FROM "${schema}".cfdis
WHERE estado = 'vigente' AND fecha_emision BETWEEN $1 AND $2
`, fechaInicio, fechaFin);
sheet.columns = [
{ header: 'Concepto', key: 'concepto', width: 40 },
{ header: 'Monto', key: 'monto', width: 20 },
];
sheet.addRow({ concepto: 'INGRESOS', monto: '' });
sheet.addRow({ concepto: 'Total Ingresos', monto: Number(totales?.ingresos || 0) });
sheet.addRow({ concepto: '', monto: '' });
sheet.addRow({ concepto: 'EGRESOS', monto: '' });
sheet.addRow({ concepto: 'Total Egresos', monto: Number(totales?.egresos || 0) });
sheet.addRow({ concepto: '', monto: '' });
sheet.addRow({ concepto: 'UTILIDAD NETA', monto: Number(totales?.ingresos || 0) - Number(totales?.egresos || 0) });
}
const buffer = await workbook.xlsx.writeBuffer();
return Buffer.from(buffer);
}