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

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Bulk-sync parts from PostgreSQL master DB into Meilisearch.
Usage:
python3 scripts/sync_meilisearch.py [--clear]
Requires environment variables:
MASTER_DB_URL=postgresql://user:pass@localhost/nexus_autoparts
"""
import os
import sys
import argparse
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'pos'))
import psycopg2
from services.meili_search import ensure_index, index_parts_bulk, clear_index, health_check
def fetch_parts(conn, batch_size=5000):
"""Yield parts from PostgreSQL as dicts."""
cur = conn.cursor(name='parts_cursor')
cur.execute("""
SELECT id_part, oem_part_number, name_part, name_es,
description, description_es, image_url, group_id
FROM parts
ORDER BY id_part
""")
while True:
rows = cur.fetchmany(batch_size)
if not rows:
break
for row in rows:
yield {
'id_part': row[0],
'oem_part_number': row[1],
'name_part': row[2],
'name_es': row[3] or row[2],
'description': row[4] or '',
'description_es': row[5] or '',
'image_url': row[6] or '',
'group_id': row[7],
}
cur.close()
def main():
parser = argparse.ArgumentParser(description='Sync parts to Meilisearch')
parser.add_argument('--clear', action='store_true', help='Clear index before sync')
parser.add_argument('--batch-size', type=int, default=5000, help='PostgreSQL fetch batch size')
parser.add_argument('--index-batch', type=int, default=1000, help='Meilisearch upload batch size')
args = parser.parse_args()
print("Meilisearch Sync")
print("=" * 50)
if not health_check():
print("ERROR: Meilisearch is not reachable.")
print(f" URL: {os.environ.get('MEILI_URL', 'http://localhost:7700')}")
sys.exit(1)
master_db_url = os.environ.get('MASTER_DB_URL')
if not master_db_url:
print("ERROR: MASTER_DB_URL environment variable is required.")
sys.exit(1)
ensure_index()
if args.clear:
print("Clearing existing index...")
clear_index()
print(f"Connecting to PostgreSQL...")
conn = psycopg2.connect(master_db_url)
# Count total
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM parts")
total_rows = cur.fetchone()[0]
cur.close()
print(f"Parts to index: {total_rows}")
print("Indexing...")
indexed = index_parts_bulk(fetch_parts(conn, args.batch_size), batch_size=args.index_batch)
conn.close()
print(f"Done. Indexed {indexed} documents.")
if __name__ == '__main__':
main()