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

@@ -13,7 +13,7 @@ fleet_bp = Blueprint('fleet', __name__, url_prefix='/pos/api/fleet')
# ─── Vehicles CRUD ─────────────────────────────
@fleet_bp.route('/vehicles', methods=['GET'])
@require_auth()
@require_auth('fleet.view')
def list_vehicles():
"""List fleet vehicles with pagination and search.
@@ -83,7 +83,7 @@ def list_vehicles():
@fleet_bp.route('/vehicles/<int:vehicle_id>', methods=['GET'])
@require_auth()
@require_auth('fleet.view')
def get_vehicle(vehicle_id):
"""Vehicle detail with maintenance schedules and recent logs."""
conn = get_tenant_conn(g.tenant_id)
@@ -155,8 +155,117 @@ def get_vehicle(vehicle_id):
return jsonify(vehicle)
@fleet_bp.route('/vehicles/schedules', methods=['GET'])
@require_auth('fleet.view')
def list_all_schedules():
"""Return all active maintenance schedules joined with vehicle data.
Replaces the N+1 pattern of fetching schedules per vehicle.
"""
conn = get_tenant_conn(g.tenant_id)
cur = conn.cursor()
where_clauses = ["v.is_active = true", "s.is_active = true"]
params = []
if g.branch_id:
where_clauses.append("v.branch_id = %s")
params.append(g.branch_id)
vehicle_ids = request.args.get('vehicle_ids', '').strip()
if vehicle_ids:
ids = [int(x) for x in vehicle_ids.split(',') if x.strip().isdigit()]
if ids:
where_clauses.append("v.id = ANY(%s)")
params.append(ids)
where = " AND ".join(where_clauses)
cur.execute(f"""
SELECT s.id, s.vehicle_id, s.maintenance_type, s.interval_km,
s.interval_months, s.last_done_at, s.last_done_km,
s.next_due_at, s.next_due_km, s.notes,
v.plate, v.make, v.model, v.current_mileage, v.color
FROM fleet_maintenance_schedules s
JOIN fleet_vehicles v ON v.id = s.vehicle_id
WHERE {where}
ORDER BY v.plate, s.next_due_at NULLS LAST, s.next_due_km NULLS LAST
""", params)
schedules = []
for r in cur.fetchall():
schedules.append({
'id': r[0], 'vehicle_id': r[1], 'maintenance_type': r[2],
'interval_km': r[3], 'interval_months': r[4],
'last_done_at': str(r[5]) if r[5] else None, 'last_done_km': r[6],
'next_due_at': str(r[7]) if r[7] else None, 'next_due_km': r[8],
'notes': r[9],
'vehicle': {
'plate': r[10], 'make': r[11], 'model': r[12],
'current_mileage': r[13], 'color': r[14]
}
})
cur.close(); conn.close()
return jsonify({'data': schedules})
@fleet_bp.route('/vehicles/history', methods=['GET'])
@require_auth('fleet.view')
def list_all_history():
"""Return recent maintenance logs for all vehicles joined with vehicle data.
Replaces the N+1 pattern of fetching logs per vehicle.
"""
conn = get_tenant_conn(g.tenant_id)
cur = conn.cursor()
limit = min(int(request.args.get('limit', 200)), 500)
where_clauses = ["v.is_active = true"]
params = [limit]
if g.branch_id:
where_clauses.append("v.branch_id = %s")
params.append(g.branch_id)
vehicle_ids = request.args.get('vehicle_ids', '').strip()
if vehicle_ids:
ids = [int(x) for x in vehicle_ids.split(',') if x.strip().isdigit()]
if ids:
where_clauses.append("v.id = ANY(%s)")
params.append(ids)
where = " AND ".join(where_clauses)
cur.execute(f"""
SELECT l.id, l.vehicle_id, l.schedule_id, l.maintenance_type,
l.mileage_at, l.cost, l.parts_used, l.notes, l.created_at,
e.name as employee_name,
v.plate, v.make, v.model, v.color
FROM fleet_maintenance_logs l
JOIN fleet_vehicles v ON v.id = l.vehicle_id
LEFT JOIN employees e ON l.employee_id = e.id
WHERE {where}
ORDER BY l.created_at DESC
LIMIT %s
""", params)
logs = []
for r in cur.fetchall():
logs.append({
'id': r[0], 'vehicle_id': r[1], 'schedule_id': r[2],
'maintenance_type': r[3], 'mileage_at': r[4],
'cost': float(r[5]) if r[5] else 0, 'parts_used': r[6],
'notes': r[7], 'created_at': str(r[8]) if r[8] else None,
'employee_name': r[9],
'vehicle': {'plate': r[10], 'make': r[11], 'model': r[12], 'color': r[13]}
})
cur.close(); conn.close()
return jsonify({'data': logs})
@fleet_bp.route('/vehicles', methods=['POST'])
@require_auth()
@require_auth('fleet.create')
def create_vehicle():
"""Create a fleet vehicle.
@@ -201,7 +310,7 @@ def create_vehicle():
@fleet_bp.route('/vehicles/<int:vehicle_id>', methods=['PUT'])
@require_auth()
@require_auth('fleet.edit')
def update_vehicle(vehicle_id):
"""Update vehicle fields including mileage.
@@ -245,7 +354,7 @@ def update_vehicle(vehicle_id):
@fleet_bp.route('/vehicles/<int:vehicle_id>', methods=['DELETE'])
@require_auth()
@require_auth('fleet.delete')
def deactivate_vehicle(vehicle_id):
"""Soft-delete: set is_active = false."""
conn = get_tenant_conn(g.tenant_id)
@@ -266,7 +375,7 @@ def deactivate_vehicle(vehicle_id):
# ─── Maintenance Schedules ─────────────────────────────
@fleet_bp.route('/vehicles/<int:vehicle_id>/schedules', methods=['GET'])
@require_auth()
@require_auth('fleet.view')
def list_schedules(vehicle_id):
"""Maintenance schedules for a vehicle."""
conn = get_tenant_conn(g.tenant_id)
@@ -299,7 +408,7 @@ def list_schedules(vehicle_id):
@fleet_bp.route('/vehicles/<int:vehicle_id>/schedules', methods=['POST'])
@require_auth()
@require_auth('fleet.create')
def create_schedule(vehicle_id):
"""Create maintenance schedule for a vehicle.
@@ -344,7 +453,7 @@ def create_schedule(vehicle_id):
# ─── Maintenance Logs ─────────────────────────────
@fleet_bp.route('/vehicles/<int:vehicle_id>/log', methods=['POST'])
@require_auth()
@require_auth('fleet.create')
def record_maintenance(vehicle_id):
"""Record maintenance done. Updates schedule next_due if schedule_id provided.
@@ -427,7 +536,7 @@ def record_maintenance(vehicle_id):
# ─── Alerts ─────────────────────────────
@fleet_bp.route('/alerts', methods=['GET'])
@require_auth()
@require_auth('fleet.view')
def fleet_alerts():
"""Vehicles with overdue maintenance (next_due_at < NOW() or next_due_km < current_mileage)."""
conn = get_tenant_conn(g.tenant_id)
@@ -472,7 +581,7 @@ def fleet_alerts():
# ─── Stats ─────────────────────────────
@fleet_bp.route('/stats', methods=['GET'])
@require_auth()
@require_auth('fleet.view')
def fleet_stats():
"""Fleet summary: total vehicles, overdue count, upcoming this month, total cost this month."""
conn = get_tenant_conn(g.tenant_id)