FASE 4-5-6: Infraestructura, CRM, Service Orders, Notificaciones, Ahorro, Logistica, API Publica

FASE 4:
- Redis cache de stock con fallback graceful
- Multi-moneda (MXN/USD) con contabilidad en MXN
- Proveedores y ordenes de compra completo
- Meilisearch 1.5M+ partes indexadas
- Metabase KPIs con dashboard auto-generado

FASE 5:
- CRM mejorado: activities, tags, loyalty program, analytics
- Imagenes de partes: upload, resize, thumbnails WebP
- Ordenes de servicio Kanban: received->diagnosis->repair->ready->delivered
- Garantias/RMA, alertas de reorden, multi-sucursal
- Stubs BNPL (APLAZO) y ERP Sync (Aspel/Contpaqi)

FASE 6:
- Notificaciones automaticas: push/WhatsApp/email/in-app
- Reportes de ahorro vs retail_price
- Logistica + tracking: DHL, FedEx, Estafeta, 99min, Uber
- API Publica: API keys, rate limiting, catalog search

Migraciones: v1.9-v3.0
Tests: 93/93 pasando
Backup: nexus_backup_20260427_045859.tar.gz
This commit is contained in:
Nexus Dev
2026-04-27 05:23:30 +00:00
parent b70cb3042b
commit 9ff3dc4c8b
71 changed files with 10939 additions and 420 deletions

View File

@@ -25,22 +25,34 @@ def log_action(conn, action, entity_type=None, entity_id=None,
device_id, ip_address, branch_id)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (
getattr(g, 'employee_id', None),
_safe_g('employee_id'),
action,
entity_type,
entity_id,
json.dumps(old_value) if old_value else None,
json.dumps(new_value) if new_value else None,
getattr(g, 'device_id', None),
_safe_g('device_id'),
_get_client_ip(),
getattr(g, 'branch_id', None),
_safe_g('branch_id'),
))
# Don't commit here — let the caller control the transaction
def _safe_g(attr, default=None):
"""Safely read flask.g attribute outside of app context."""
try:
return getattr(g, attr, default)
except RuntimeError:
return default
def _get_client_ip():
"""Get client IP, handling proxies."""
from flask import request
if request.headers.get('X-Forwarded-For'):
return request.headers['X-Forwarded-For'].split(',')[0].strip()
return request.remote_addr
try:
from flask import request
if request.headers.get('X-Forwarded-For'):
return request.headers['X-Forwarded-For'].split(',')[0].strip()
return request.remote_addr
except RuntimeError:
return None