feat(export): botón Exportar (Excel + PDF) en Usuarios y Registro de Transacciones

- Usuarios y permisos: exporta los rows visibles (respeta filtros
  de rol, activos y búsqueda). Columnas: Email, Nombre, Rol,
  Activo, Último login
- Registro de transacciones: exporta las entradas cargadas en
  pantalla (respeta filtros de fecha/usuario/módulo/acción/texto).
  Excel incluye Email e IP; el PDF los omite para que quepa
- Mismo módulo compartido DonnaExport — UI idéntica a los otros
  módulos
This commit is contained in:
Soporte ZH Consultores
2026-06-04 22:38:03 -05:00
parent 0a8dc6fc46
commit b8f5d83fee
+72
View File
@@ -170,6 +170,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
border-radius:6px; color:var(--gray-700); transition:all .15s; }
.tb-quick-log:hover { background:var(--navy); color:#fff; border-color:var(--navy); }
</style>
<link rel="stylesheet" href="/assets/donna-export.css">
</head>
<body>
@@ -223,6 +224,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<option value="0">Solo inactivos</option>
</select>
<button class="btn-primary" onclick="openCreate()">+ Nuevo usuario</button>
<div id="export-mount-users"></div>
</div>
<div class="tbl-card">
@@ -285,6 +287,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<button class="tb-quick-log" onclick="setLogQuick('month')">Mes</button>
<button class="tb-quick-log" onclick="setLogQuick('all')">Todo</button>
<button class="btn-primary" onclick="loadLog()" style="padding:7px 14px;">Aplicar</button>
<div id="export-mount-log"></div>
</div>
</div>
</div>
@@ -354,6 +357,8 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<div class="toast" id="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 ROLES = ['admin','socio','abogado','usuario'];
const ROLE_LABEL = { admin:'Admin', socio:'Socio', abogado:'Abogado', usuario:'Usuario' };
@@ -644,7 +649,9 @@ function populateLogDropdowns(d){
(d.acciones||[]).map(a => `<option value="${esc(a)}">${esc(ACCION_LABEL[a] || a)}</option>`).join('');
aSel.value = aPrev;
}
let lastLogEntries = [];
function renderLog(entries){
lastLogEntries = entries || [];
const tbody = $('log-tbody');
const empty = $('log-empty');
const info = $('log-rowinfo');
@@ -677,6 +684,71 @@ $('log-search').addEventListener('input', () => {
window._logSearchTimer = setTimeout(loadLog, 350);
});
// ── EXPORT (Usuarios) ────────────────────────────────────────────────────
const USERS_HEADERS = ['Email','Nombre completo','Rol','Activo','Último login'];
function usersExportRows(){
return visibleUsers().map(u => [
u.email,
u.name || '',
ROLE_LABEL[u.role] || u.role,
u.activo ? 'Sí' : 'No',
u.last_login_at ? fmtDate(u.last_login_at) : 'Nunca',
]);
}
function usersExportExcel(){
DonnaExport.toExcel({
filename: 'Usuarios_Donna_AALN', sheetName: 'Usuarios',
headers: USERS_HEADERS, rows: usersExportRows(),
columnWidths: [32,28,12,10,20],
onEmpty: () => toast('Sin usuarios para exportar','error'),
});
}
function usersExportPDF(){
DonnaExport.toPDF({
filename: 'Usuarios_Donna_AALN',
title: 'Usuarios y permisos',
orientation: 'portrait',
headers: USERS_HEADERS, rows: usersExportRows(),
onEmpty: () => toast('Sin usuarios para exportar','error'),
});
}
document.getElementById('export-mount-users').innerHTML = DonnaExport.buttonHtml('export-wrap-users');
DonnaExport.attach('export-wrap-users', { onExcel: usersExportExcel, onPDF: usersExportPDF });
// ── EXPORT (Registro de transacciones) ───────────────────────────────────
const LOG_HEADERS = ['Fecha · Hora','Usuario','Email','Módulo','Acción','Entidad','Detalles','IP'];
function logExportRows(){
return (lastLogEntries||[]).map(e => [
fmtLogDate(e.fecha),
e.user_name || '',
e.user_email || '',
MOD_LABEL[e.modulo] || e.modulo,
ACCION_LABEL[e.accion] || e.accion,
(e.entity_label || '') + (e.entity_type ? ' ('+e.entity_type+')' : ''),
e.detalles || '',
e.ip_addr || '',
]);
}
function logExportExcel(){
DonnaExport.toExcel({
filename: 'Registro_Transacciones_AALN', sheetName: 'Auditoría',
headers: LOG_HEADERS, rows: logExportRows(),
columnWidths: [18,22,28,14,18,28,50,14],
onEmpty: () => toast('No hay transacciones para exportar — aplica filtros primero','error'),
});
}
function logExportPDF(){
DonnaExport.toPDF({
filename: 'Registro_Transacciones_AALN',
title: 'Registro de Transacciones',
headers: LOG_HEADERS, rows: logExportRows(),
pdfCols: [0,1,3,4,5,6], // omite Email e IP para que quepa
onEmpty: () => toast('No hay transacciones para exportar — aplica filtros primero','error'),
});
}
document.getElementById('export-mount-log').innerHTML = DonnaExport.buttonHtml('export-wrap-log');
DonnaExport.attach('export-wrap-log', { onExcel: logExportExcel, onPDF: logExportPDF });
loadUsers();
</script>
</body>