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

@@ -30,10 +30,24 @@ def list_customers():
per_page = min(int(request.args.get('per_page', 50)), 200)
search = request.args.get('q', '').strip()
branch_id = request.args.get('branch_id')
price_tier = request.args.get('price_tier', '').strip()
status = request.args.get('status', '').strip().lower()
where_clauses = ["c.is_active = true"]
where_clauses = []
params = []
if status == 'inactive':
where_clauses.append("c.is_active = false")
elif status == 'overdue':
where_clauses.append(
"c.is_active = true AND c.credit_limit > 0 AND c.credit_balance > c.credit_limit"
)
elif status == 'all':
pass # no is_active filter
else:
# Default to active customers for backwards compatibility
where_clauses.append("c.is_active = true")
if branch_id:
where_clauses.append("c.branch_id = %s")
params.append(int(branch_id))
@@ -42,8 +56,20 @@ def list_customers():
"(c.name ILIKE %s OR c.rfc ILIKE %s OR c.phone ILIKE %s OR c.razon_social ILIKE %s)"
)
params.extend([f'%{search}%'] * 4)
if price_tier:
# Support numeric tier or Spanish labels
tier_map = {'taller': 2, 'mostrador': 1, 'mayoreo': 3}
tier_val = tier_map.get(price_tier.lower())
if tier_val is None:
try:
tier_val = int(price_tier)
except ValueError:
tier_val = None
if tier_val in (1, 2, 3):
where_clauses.append("c.price_tier = %s")
params.append(tier_val)
where = " AND ".join(where_clauses)
where = " AND ".join(where_clauses) if where_clauses else "true"
# Count
cur.execute(f"SELECT count(*) FROM customers c WHERE {where}", params)
@@ -54,7 +80,7 @@ def list_customers():
SELECT c.id, c.name, c.rfc, c.razon_social, c.phone, c.email,
c.address, c.cp,
c.price_tier, c.credit_limit, c.credit_balance, c.vehicle_info,
c.branch_id
c.branch_id, c.is_active, c.created_at, c.last_purchase
FROM customers c
WHERE {where}
ORDER BY c.name
@@ -71,6 +97,9 @@ def list_customers():
'credit_balance': float(r[10]) if r[10] else 0,
'vehicle_info': r[11],
'branch_id': r[12],
'is_active': r[13],
'created_at': str(r[14]) if r[14] else None,
'last_purchase': str(r[15]) if r[15] else None,
})
cur.close()
@@ -472,6 +501,32 @@ def record_customer_payment(customer_id):
UPDATE customers SET credit_balance = %s WHERE id = %s
""", (new_balance, customer_id))
# Allocate the customer payment to oldest unpaid credit sales so the
# aging report and per-sale balances reflect the remaining debt.
remaining = amount
cur.execute("""
SELECT s.id, s.total - COALESCE(SUM(sp.amount), 0) as balance
FROM sales s
LEFT JOIN sale_payments sp ON sp.sale_id = s.id
WHERE s.customer_id = %s
AND s.sale_type = 'credit'
AND s.status = 'completed'
GROUP BY s.id, s.total, s.created_at
HAVING s.total - COALESCE(SUM(sp.amount), 0) > 0
ORDER BY s.created_at
""", (customer_id,))
for sale_id, balance in cur.fetchall():
if remaining <= 0:
break
pay = round(min(remaining, float(balance)), 2)
cur.execute("""
INSERT INTO sale_payments
(sale_id, register_id, method, amount, reference)
VALUES (%s, %s, %s, %s, %s)
""", (sale_id, register_id, payment_method, pay,
f'Abono cliente #{customer_id}'))
remaining = round(remaining - pay, 2)
# Record cash movement on register if cash payment
if register_id and payment_method == 'efectivo':
cur.execute("""