fix(audit): corrige errores criticos y mayores, mejora UX/accesibilidad y optimiza rendimiento
- 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:
@@ -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">🚚</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() {
|
||||
' ' + 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; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user