feat(export): UI unificada de Excel + PDF en los 4 módulos

- Nuevos assets compartidos /assets/donna-export.{css,js}: botón
  "Exportar ▾" + menú dropdown Excel/PDF y helpers DonnaExport.toExcel
  / .toPDF / .attach / .buttonHtml
- escrituras: refactor de exportExcel/exportPDF para usar el módulo
  compartido (sin cambio de comportamiento)
- tramites: nuevo botón en la toolbar de cada tab (adm/corp/mig/lit),
  exporta los rows filtrados de ese tab; PDF excluye Involucrados
- calendario: nuevo botón al final de la toolbar, exporta visibleEvents()
  (respeta chips de origen y búsqueda)
- horas: reemplaza CSV server-side por XLSX client-side + agrega PDF;
  removida la acción ?action=export del api.php
This commit is contained in:
Soporte ZH Consultores
2026-06-04 22:30:37 -05:00
parent 012c4e1c66
commit eff590af47
7 changed files with 377 additions and 236 deletions
+50
View File
@@ -140,6 +140,7 @@ tr.expanded>td{background:#fffdf5;}
.toast.show{transform:translateY(0);opacity:1;}
.toast.error{border-left-color:#ef5350;}
</style>
<link rel="stylesheet" href="/assets/donna-export.css">
</head>
<body>
@@ -213,6 +214,8 @@ tr.expanded>td{background:#fffdf5;}
<div id="toast" class="toast"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="/assets/donna-export.js"></script>
<script>
const ABOGADOS = (window.DONNA && window.DONNA.abogados) || ['Arias','Abrego','Lopez','Altafulla','Álvarez','Gutiérrez','Cedeño','Mariscal','Ávila','Agudo','Martínez','De Gracia','Góndola','González','Valdés'];
const USERS = (window.DONNA && window.DONNA.users) || [];
@@ -391,6 +394,8 @@ function renderPanel(dept){
const activos=rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length;
const culminados=rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length;
window.__tramFiltered = window.__tramFiltered || {};
window.__tramFiltered[dept] = filtered;
panel.innerHTML=`
<div class="counts">
<div class="count-card"><b>${total}</b>Total</div>
@@ -412,6 +417,7 @@ function renderPanel(dept){
<option${fEsfera==='Civil'?' selected':''}>Civil</option><option${fEsfera==='Penal'?' selected':''}>Penal</option>
</select>`:''}
${CAN_CREATE ? `<button class="btn btn-primary" onclick="openModal('${dept}',null)">+ Nuevo</button>` : ''}
${DonnaExport.buttonHtml('export-wrap-'+dept)}
</div>
<div class="tbl-wrap">
<table>
@@ -421,6 +427,50 @@ function renderPanel(dept){
</tbody>
</table>
</div>`;
DonnaExport.attach('export-wrap-'+dept, {
onExcel: () => tramExportExcel(dept),
onPDF: () => tramExportPDF(dept),
});
}
// ── EXPORT (Trámites) ────────────────────────────────────────────────────
const TRAM_HEADERS = {
adm: ['Código','Institución','Nombre Trámite','Cliente','Abogado','Involucrados','Estatus','Fecha Solicitud','Horas'],
corp: ['Código','Tipo Documento','Nombre Trámite','Cliente','Abogado','Involucrados','Estatus','Fecha Solicitud','Horas'],
mig: ['Código','Tipo Trámite','Cliente','Abogado','Involucrados','Doc. Completa','Permiso Trabajo','Dependientes','Estatus','Fecha Solicitud','Vencimiento','Horas'],
lit: ['Código','Proceso','Juzgado','Cliente','Abogado','Involucrados','Esfera','Instancia','Doc. Completa','Incidentes','Amparo','Estatus','Fecha Solicitud','Término','Horas'],
};
function fmtDateOnly(s){ return s ? String(s).substring(0,10) : ''; }
function invsCSV(invs){ return (invs||[]).map(u => u.name || u.email).join(', '); }
function tramRowToArray(dept, r){
const inv = invsCSV(r.involucrados);
if (dept === 'adm') return [r.codigo, r.institucion||'', r.nombre||'', r.cliente||'', r.abogado||'', inv, r.estatus||'', fmtDateOnly(r.fecha_solicitud), totalHours(r)];
if (dept === 'corp')return [r.codigo, r.tipo_documento||'', r.nombre||'', r.cliente||'', r.abogado||'', inv, r.estatus||'', fmtDateOnly(r.fecha_solicitud), totalHours(r)];
if (dept === 'mig') return [r.codigo, r.tipo_tramite||'', r.cliente||'', r.abogado||'', inv, r.doc_completa||'', r.permiso_trabajo||'', r.dependientes||'', r.estatus||'', fmtDateOnly(r.fecha_solicitud), fmtDateOnly(r.fecha_vencimiento), totalHours(r)];
if (dept === 'lit') return [r.codigo, r.tipo_proceso||'', r.juzgado||'', r.cliente||'', r.abogado||'', inv, r.esfera||'', r.instancia||'', r.doc_completa||'', r.incidentes||'', r.amparo||'', r.estatus||'', fmtDateOnly(r.fecha_solicitud), fmtDateOnly(r.fecha_termino), totalHours(r)];
}
function tramExportExcel(dept){
const rows = (window.__tramFiltered && window.__tramFiltered[dept]) || data[dept] || [];
DonnaExport.toExcel({
filename: 'Tramites_'+dept.toUpperCase()+'_AALN',
sheetName: DEPT_LABELS[dept] || dept,
headers: TRAM_HEADERS[dept],
rows: rows.map(r => tramRowToArray(dept, r)),
onEmpty: () => toast('No hay datos para exportar','error'),
});
}
function tramExportPDF(dept){
const rows = (window.__tramFiltered && window.__tramFiltered[dept]) || data[dept] || [];
// En PDF excluye Involucrados (lista larga) para que quepa
const cols = TRAM_HEADERS[dept].map((_,i) => i).filter(i => TRAM_HEADERS[dept][i] !== 'Involucrados');
DonnaExport.toPDF({
filename: 'Tramites_'+dept.toUpperCase()+'_AALN',
title: 'Control de Trámites Legales · ' + (DEPT_LABELS[dept] || dept),
headers: TRAM_HEADERS[dept],
rows: rows.map(r => tramRowToArray(dept, r)),
pdfCols: cols,
onEmpty: () => toast('No hay datos para exportar','error'),
});
}
function buildHeaders(dept){