fix(audit): corrige errores criticos y mayores, mejora UX/accesibilidad y optimiza rendimiento
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- Arregla @require_auth, permisos, race conditions, locks de caja/stock
- Elimina N+1 en layaway, flotilla, dashboard y global_invoice
- Asegura folios atomicos para CFDI, ordenes de servicio y polizas
- Protege client_secret de MercadoLibre en backend
- Conecta botones/filtros de config, customers, accounting e invoicing
- Mejora accesibilidad (labels/aria-label) y estados de carga/vacio
- Limpia accounting.js obsoleto y consolida accounting.v9.js
- Actualiza cache busting a v32 y Service Worker a v32
- Documenta todo en docs/AUDIT_Y_MEJORAS_2026-06-15.md

Tests: 35 passed
This commit is contained in:
2026-06-29 23:54:58 +00:00
parent 59a4893e84
commit 2bdeb2973a
61 changed files with 2879 additions and 706 deletions

View File

@@ -12,6 +12,16 @@ var Fleet = (function() {
var currentPage = 1;
var searchTimeout = null;
var user = window.POS_USER || {};
var role = (user.role || '').toLowerCase();
var perms = user.permissions || [];
function hasPerm(p) {
return role === 'owner' || perms.indexOf(p) !== -1;
}
var canCreate = hasPerm('fleet.create');
var canEdit = hasPerm('fleet.edit');
var canDelete = hasPerm('fleet.delete');
// ─── Helpers ───
function headers() {
@@ -75,9 +85,16 @@ var Fleet = (function() {
}
// New vehicle button
document.getElementById('btnNewVehicle').addEventListener('click', function() {
openVehicleModal();
});
var btnNewVehicle = document.getElementById('btnNewVehicle');
if (btnNewVehicle) {
if (!canCreate) {
btnNewVehicle.style.display = 'none';
} else {
btnNewVehicle.addEventListener('click', function() {
openVehicleModal();
});
}
}
// Load initial data
loadStats();
@@ -105,6 +122,8 @@ var Fleet = (function() {
var url = API + '/vehicles?page=' + currentPage + '&per_page=50';
if (q) url += '&q=' + encodeURIComponent(q);
document.getElementById('vehicleGrid').innerHTML = renderLoadingState({ message: 'Cargando vehiculos...' });
fetch(url, {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(d) {
@@ -115,7 +134,7 @@ var Fleet = (function() {
})
.catch(function() {
document.getElementById('vehicleGrid').innerHTML =
'<div class="empty-state"><div class="empty-state__text">Error al cargar vehiculos</div></div>';
renderEmptyState({ title: 'Error', subtitle: 'No se pudieron cargar los vehiculos.' });
});
}
@@ -125,7 +144,7 @@ var Fleet = (function() {
grid.innerHTML = '<div class="empty-state">' +
'<div class="empty-state__icon">&#x1F69A;</div>' +
'<div class="empty-state__text">No hay vehiculos registrados</div>' +
'<button class="btn btn--primary" onclick="Fleet.openVehicleModal()">+ Agregar Vehiculo</button>' +
(canCreate ? '<button class="btn btn--primary" onclick="Fleet.openVehicleModal()">+ Agregar Vehiculo</button>' : '') +
'</div>';
return;
}
@@ -135,7 +154,7 @@ var Fleet = (function() {
var label = (v.make || '') + ' ' + (v.model || '');
if (v.year) label += ' ' + v.year;
html += '<div class="vehicle-card" onclick="Fleet.viewVehicle(' + v.id + ')">' +
html += '<div class="vehicle-card" ' + (canEdit ? 'onclick="Fleet.viewVehicle(' + v.id + ')" style="cursor:pointer;"' : '') + '>' +
'<div class="vehicle-card__header">' +
'<span class="vehicle-card__plate">' + esc(v.plate || 'SIN PLACA') + '</span>' +
'<span class="badge ' + (v.is_active ? 'badge--active' : 'badge--inactive') + '">' +
@@ -200,9 +219,11 @@ var Fleet = (function() {
function openVehicleModal(data) {
var modal = document.getElementById('vehicleModal');
var title = document.getElementById('vehicleModalTitle');
var saveBtn = document.getElementById('btnSaveVehicle');
if (data && data.id) {
title.textContent = 'Editar Vehiculo';
if (saveBtn) saveBtn.style.display = canEdit ? '' : 'none';
document.getElementById('vehEditId').value = data.id;
document.getElementById('vehPlate').value = data.plate || '';
document.getElementById('vehVin').value = data.vin || '';
@@ -216,6 +237,7 @@ var Fleet = (function() {
document.getElementById('vehNotes').value = data.notes || '';
} else {
title.textContent = 'Nuevo Vehiculo';
if (saveBtn) saveBtn.style.display = canCreate ? '' : 'none';
document.getElementById('vehEditId').value = '';
['vehPlate','vehVin','vehMake','vehModel','vehYear','vehColor','vehOwner','vehNotes']
.forEach(function(id) { document.getElementById(id).value = ''; });
@@ -232,6 +254,8 @@ var Fleet = (function() {
function saveVehicle() {
var editId = document.getElementById('vehEditId').value;
if (editId && !canEdit) { alert('No tienes permiso para editar vehiculos'); return; }
if (!editId && !canCreate) { alert('No tienes permiso para crear vehiculos'); return; }
var payload = {
plate: document.getElementById('vehPlate').value.trim(),
vin: document.getElementById('vehVin').value.trim(),
@@ -275,26 +299,36 @@ var Fleet = (function() {
// ─── Maintenance Tab ───
function loadMaintenance() {
// Load all vehicles with their schedules
fetch(API + '/vehicles?per_page=200', {headers: headers()})
// Single bulk endpoint replaces N+1 per-vehicle schedule requests
document.getElementById('maintBody').innerHTML =
'<tr><td colspan="7" style="padding:var(--space-6);">' + renderLoadingState({ message: 'Cargando programas...' }) + '</td></tr>';
fetch(API + '/vehicles/schedules', {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(d) {
var allVehicles = d.data || [];
var promises = allVehicles.map(function(v) {
return fetch(API + '/vehicles/' + v.id + '/schedules', {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(s) {
return {vehicle: v, schedules: s.data || []};
});
var schedules = d.data || [];
var results = schedules.map(function(s) {
return {
vehicle: s.vehicle || {},
schedules: [{
id: s.id,
maintenance_type: s.maintenance_type,
interval_km: s.interval_km,
interval_months: s.interval_months,
last_done_at: s.last_done_at,
last_done_km: s.last_done_km,
next_due_at: s.next_due_at,
next_due_km: s.next_due_km,
notes: s.notes
}]
};
});
return Promise.all(promises);
})
.then(function(results) {
renderMaintenance(results);
})
.catch(function() {
.catch(function(e) {
console.error('loadMaintenance error:', e);
document.getElementById('maintBody').innerHTML =
'<tr><td colspan="7" style="text-align:center;color:var(--color-text-muted);">Error al cargar</td></tr>';
'<tr><td colspan="7" style="padding:var(--space-6);">' + renderEmptyState({ title: 'Error', subtitle: 'No se pudieron cargar los programas.' }) + '</td></tr>';
});
}
@@ -326,7 +360,7 @@ var Fleet = (function() {
'<td>' + (next || '—') + '</td>' +
'<td><span class="badge ' + (isOverdue ? 'badge--overdue' : 'badge--active') + '">' +
(isOverdue ? 'Vencido' : 'Al dia') + '</span></td>' +
'<td><button class="btn btn--sm btn--ghost" onclick="Fleet.openLogModalFor(' + v.id + ',' + s.id + ',\'' + esc(s.maintenance_type) + '\')">Registrar</button></td>' +
'<td>' + (canCreate ? '<button class="btn btn--sm btn--ghost" onclick="Fleet.openLogModalFor(' + v.id + ',' + s.id + ',\'' + esc(s.maintenance_type) + '\')">Registrar</button>' : '') + '</td>' +
'</tr>';
}
@@ -344,7 +378,9 @@ var Fleet = (function() {
if (!items.length) {
body.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">' +
'No hay programas de mantenimiento.<br><button class="btn btn--primary btn--sm" style="margin-top:var(--space-3);" onclick="Fleet.openScheduleModal()">+ Crear Programa</button></td></tr>';
'No hay programas de mantenimiento.<br>' +
(canCreate ? '<button class="btn btn--primary btn--sm" style="margin-top:var(--space-3);" onclick="Fleet.openScheduleModal()">+ Crear Programa</button>' : '') +
'</td></tr>';
return;
}
@@ -363,25 +399,25 @@ var Fleet = (function() {
// ─── History Tab ───
function loadHistory() {
fetch(API + '/vehicles?per_page=200', {headers: headers()})
// Single bulk endpoint replaces N+1 per-vehicle detail requests
document.getElementById('historyBody').innerHTML =
'<tr><td colspan="7" style="padding:var(--space-6);">' + renderLoadingState({ message: 'Cargando historial...' }) + '</td></tr>';
fetch(API + '/vehicles/history', {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(d) {
var allVehicles = d.data || [];
var promises = allVehicles.map(function(v) {
return fetch(API + '/vehicles/' + v.id, {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(detail) {
return {vehicle: v, logs: detail.recent_logs || []};
});
var logs = d.data || [];
var results = logs.map(function(l) {
l._plate = (l.vehicle && l.vehicle.plate) || 'S/P';
l._make = ((l.vehicle && l.vehicle.make) || '') + ' ' + ((l.vehicle && l.vehicle.model) || '');
return {vehicle: l.vehicle || {}, logs: [l]};
});
return Promise.all(promises);
})
.then(function(results) {
renderHistory(results);
})
.catch(function() {
.catch(function(e) {
console.error('loadHistory error:', e);
document.getElementById('historyBody').innerHTML =
'<tr><td colspan="7" style="text-align:center;color:var(--color-text-muted);">Error al cargar</td></tr>';
'<tr><td colspan="7" style="padding:var(--space-6);">' + renderEmptyState({ title: 'Error', subtitle: 'No se pudo cargar el historial.' }) + '</td></tr>';
});
}
@@ -437,6 +473,8 @@ var Fleet = (function() {
// ─── Alerts Tab ───
function loadAlerts() {
document.getElementById('alertsList').innerHTML = renderLoadingState({ message: 'Cargando alertas...' });
fetch(API + '/alerts', {headers: headers()})
.then(function(r) { return r.json(); })
.then(function(d) {
@@ -444,7 +482,7 @@ var Fleet = (function() {
})
.catch(function() {
document.getElementById('alertsList').innerHTML =
'<div class="empty-state"><div class="empty-state__text">Error al cargar alertas</div></div>';
renderEmptyState({ title: 'Error', subtitle: 'No se pudieron cargar las alertas.' });
});
}
@@ -475,7 +513,7 @@ var Fleet = (function() {
' &nbsp; ' + detail +
'</div>' +
'</div>' +
'<button class="btn btn--sm btn--primary" onclick="Fleet.openLogModalFor(' + a.vehicle_id + ',' + a.schedule_id + ',\'' + esc(a.maintenance_type) + '\')">Registrar Mant.</button>' +
(canCreate ? '<button class="btn btn--sm btn--primary" onclick="Fleet.openLogModalFor(' + a.vehicle_id + ',' + a.schedule_id + ',\'' + esc(a.maintenance_type) + '\')">Registrar Mant.</button>' : '') +
'</div>';
});
el.innerHTML = html;
@@ -504,6 +542,7 @@ var Fleet = (function() {
}
function saveSchedule() {
if (!canCreate) { alert('No tienes permiso para crear programas'); return; }
var vehicleId = document.getElementById('schedVehicleSelect').value;
if (!vehicleId) { alert('Seleccione un vehiculo'); return; }
@@ -574,6 +613,7 @@ var Fleet = (function() {
}
function saveLog() {
if (!canCreate) { alert('No tienes permiso para registrar mantenimiento'); return; }
var vehicleId = document.getElementById('logVehicleSelect').value;
if (!vehicleId) { alert('Seleccione un vehiculo'); return; }