Files
Autoparts-DB/docs/plans/2026-10-11-plan-maestro-mejoras.md
consultoria-as a386ca5b4a
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled
Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos
- F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado
- F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment
- F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings)
- F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant
- Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas
- Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant)
- Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant
- Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
2026-10-12 00:57:31 +00:00

200 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan Maestro de Mejoras — Nexus Autoparts
> **Fecha:** 11 Oct 2026 · **Base:** auditoría completa del sistema (6 investigaciones con evidencia en archivo:línea, SQL y curl)
> **Énfasis:** MercadoLibre (eje comercial) + apertura del mercado de **partes usadas** (yonkes/deshuesaderos)
---
## 0. Resumen ejecutivo
El sistema es un **esqueleto potente con el ciclo comercial roto en los últimos metros**. Lo que funciona bien es la base (motor de ventas con locks y partida doble, aislamiento multi-tenant, compatibilidad vehicular, OAuth de MeLi con refresh persistido, WA CRM recién instalado). Lo que está roto es justo lo que genera ingresos:
| # | Bloqueante | Impacto | Evidencia |
|---|---|---|---|
| 1 | **Backups caídos desde el 1-oct** | Sin respaldo fresco de ningún tenant (riesgo existencial) | `scripts/backup_selective.sh:299-303` — `((deleted++))` devuelve 1 y mata el script bajo `set -euo pipefail` |
| 2 | **Alta de tenants falla** | No se puede onboardear ningún cliente nuevo | `tenant_manager.py:201-211` aplica `v1.2_subdomain.sql` (migración de *master*) al tenant → rollback |
| 3 | **CFDI 100% roto** | Ninguna factura se timbra | `invoicing_bp.py:65` — `SELECT … codigo_postal FROM branches` (la columna es `cp`) |
| 4 | **Órdenes MeLi nunca se convierten a venta** | 31/31 órdenes de Rached sin vender en el POS | `marketplace_external_service.py:1619` — `inventory_record_sale(..., reference=)` no existe en la firma |
| 5 | **Publicar en MeLi desde el POS está muerto** | Validar/Publicar no hacen nada (ReferenceError) | `inventory.js:2069,2135` llaman `_collectMeliCustomData()`, que no existe |
| 6 | **Sync de stock MeLi falla 52/52** | Envía `price:0` a ML cada 15 min (0 éxitos desde junio) | `tasks.py:135-145` — listings con `inventory_id NULL` |
| 7 | **Webhook MeLi nunca procesa** | Sin notificaciones en tiempo real, público y sin firma | `marketplace_external_bp.py:775-790` — consulta `tenant_config` en la BD maestra (no existe ahí) |
| 8 | **Catálogo público con 500s** | El visitante nuevo ve error en buscador, categorías y detalle | `dashboard/server.py:977,1281,1994` contra tablas TecDoc retiradas |
| 9 | **Plan `demo` → fallback `free`** | Estrada (157,243 productos) bloqueada en 100 productos | `billing.py:25-28` + `tenants.plan='demo'` |
| 10 | **`/api/admin/tenants*` sin autenticación** | Cualquiera en internet lista y modifica módulos de tenants | `dashboard/server.py:4660,4674,4691` |
**Y el hallazgo estratégico:** el negocio de **partes usadas es hoy imposible de operar** en el sistema (no existe condición, ni pieza única, ni vehículo donador, ni multi-foto) — y MeLi publica todo como `"condition":"new"` hardcodeado. Abrir ese mercado requiere un cambio de modelo de datos, no cosmético.
---
## 1. MercadoLibre — cerrar el ciclo antes de soñar en grande
### 1.1 Fase A — Desbloquear lo que ya está construido (1 semana)
| Tarea | Detalle | Esfuerzo |
|---|---|---|
| A1 | Implementar `_collectMeliCustomData()`, `_pollMeliAsync()`, `_renderPublishResult()` en `inventory.js` (los inputs de título/precio/stock y atributos **ya se generan pero nunca se envían**) y mandar `custom_data` al backend (`marketplace_external_service.py:669-673`) | M |
| A2 | Corregir `convert_order_to_sale`: usar `record_sale(conn, inventory_id, branch_id, quantity, sale_id=…)` — hoy `TypeError` → rollback → 31 órdenes sin convertir (`service.py:1619`, `inventory_engine.py:199`) | S |
| A3 | `import_existing_listings`: poblar `inventory_id` por `seller_custom_field`/`part_number`/alias y **saltar el push cuando `price<=0`**; el `ON CONFLICT` no aplica con NULL (`service.py:1785`) | S |
| A4 | Webhook: resolver tenant por `meli_user_id` consultando cada `tenant_config` de tenant (no la maestra), validar `X-Signature` con `validate_webhook_signature` (ya existe, nunca se llama) y procesar el `resource` correcto (no un sync completo) | M |
| A5 | Normalizar `status` de preguntas (`UPPER`) — hoy 4 preguntas reales no se pueden responder por comparar `'unanswered'` vs `UNANSWERED` (`marketplace_external.js:516`) | S |
| A6 | Pasar `tenant_conn` a `_get_meli_service` en `sync_meli_stock_price_task` (`tasks.py:112`) para no perder el refresh token rotado | S |
| A7 | Pausar/reactivar automáticamente por stock (0 → pausar, >0 → reactivar) en la cola `meli_sync_queue` + drenarla por timer | M |
| A8 | Sincronizar órdenes en todos los estados (`cancelled`, `payment_required`…) y corregir `buyer_nickname` (ML ya no manda `from.nickname`) | S |
| A9 | Corregir el **estimador de comisiones por artículo** (busca `#meliFee-<id>` en un DOM que nunca lo crea, `inventory.js:2295`) | S |
| A10 | Definir/otorgar el permiso `marketplace.manage` (no existe en ningún tenant) y proteger los GET del módulo | S |
**Resultado:** el ciclo MeLi→POS (publicar → vender → convertir → facturar) cierra por primera vez.
### 1.2 Fase B — Publicar en volumen (2-3 semanas) ← el verdadero cuello de botella
| Tarea | Detalle | Esfuerzo |
|---|---|---|
| B1 | **Pipeline masivo de imágenes** — el bloqueo real: 0 imágenes en 6,851 SKUs de Rached, 0 en 14,568 de Capi, 1 en 157,243 de Estrada. ML exige imagen. Usar los servicios de scraping existentes (`services/image_scraper.py`, `image_service.py`) + una cola por lotes con revisión | L |
| B2 | **Selección y publicación por lotes real**: hoy la UI solo selecciona una página de 50 y un solo `category_id` para todo el lote. Agregar: seleccionar todo el resultado filtrado, categorización asistida por IA (nombre+marca→categoría ML con `domain_discovery`), y confirmación por grupos | M |
| B3 | **Rate-limit/backoff** en el worker de publicación (Celery) con reintentos y progreso visible (barra + reporte de fallidos por motivo) | M |
| B4 | **Ciclo export→edición→reimportación** para fitment e imágenes (hoy el export no incluye compat ni image_url) | M |
### 1.3 Fase C — Vender más (3-4 semanas)
| Oportunidad | Hoy | Qué agregar |
|---|---|---|
| **Variaciones** | Nada (0 referencias a `variations`) | Agrupar SKUs en una publicación con `variations`/`attribute_combinations` (mismo producto, varias medidas) |
| **Catálogo de MeLi** | Solo `category_id` | Detectar categorías que **exigen catálogo** (`catalog_listing`, `catalog_product_id`), avisar en la UI y usar `price_to_win` para competir por la oferta ganadora |
| **Promociones** | Nada | Oferta del día, descuentos por volumen, `sale_price` por publicación |
| **Envíos** | Solo me2/custom, sin etiquetas | Flex, generación de etiqueta, `mark_ready_to_ship` (existe y nunca se usa), tracking |
| **Preguntas** | Manual y roto | Auto-respuesta con `ai_chat.py` + plantillas por categoría + SLA visible |
| **Reputación** | Nada | Panel con `/users/{id}/reputation`: nivel, reclamos, cancelaciones, tiempo de despacho |
| **Rentabilidad por publicación** | Solo el estimado al publicar (no se guarda) | Persistir comisión + envío + costo en `marketplace_listings` y panel de margen real por SKU |
| **Facturación de ventas MeLi** | Ningún enganche a CFDI | Encolar CFDI automático al convertir orden (con RFC del comprador si lo pide) |
| **Cancelaciones/reintegros/claims** | Solo estado local | Llamar a la API de ML (cancelar, devoluciones, mediaciones) con SLA |
| **Multi-cuenta** | 1 cuenta MeLi por tenant | Varias cuentas MeLi por tenant (varias marcas) con selección por publicación |
| **Panel comercial** | Nada | Ventas MeLi vs POS, conversión, SKUs sin stock con publicación activa, top ventas, alertas de publicaciones caídas |
### 1.4 Fase D — Robustez operativa
- Onboarding asistido de credenciales MeLi (hoy es manual y el fallo típico —ACCA— es un `client_secret` regenerado que nadie detecta hasta que el cliente llama; ya mejoramos el mensaje, falta un **chequeo proactivo diario** que avise antes).
- Versionar el timer de sync en el repo (hoy vive solo en `/etc/systemd/system`).
- Pruebas automatizadas del módulo (hoy: **cero** tests de MeLi).
---
## 2. Abrir el mercado: negocios de SOLO partes usadas (yonkes/deshuesaderos)
### 2.1 Por qué hoy no se puede (evidencia)
| Requisito del yonke | Estado | Dónde se rompe |
|---|---|---|
| Pieza única (1-de-1, sin código de parte) | ❌ | `part_number` es **NOT NULL + UNIQUE global** (`v4.0_multi_branch.sql:214`); dos espejos iguales de donadores distintos **se fusionan** en el import |
| Saber de qué auto salió (donador) | ❌ | No hay `donor_vin`/`donor_mileage`; la compat existente es "sirve para", no "salió de" |
| Condición nuevo/usado/A/B/C | ❌ | **0 columnas** de condición en todo el esquema; MeLi publica `"condition":"new"` hardcodeado |
| Fotos múltiples (ángulos + daños) | ❌ | Un solo `image_url` varchar; cada subida **reemplaza** la anterior (`inventory_bp.py:995`) |
| Garantía por días (15/30 días) | ⚠️ | Solo `warranty_months` (mínimo 1 mes) |
| Buscar "todo lo que tengo de un Civic 2015" | ❌ | La navegación por vehículo exige `part_number` para poblar compat |
| Consignación / piezas de terceros | ❌ | No existe modelo de propiedad; todo el stock se asume propio |
| Valuación con costo 0 | ⚠️ | Vender funciona, pero los reportes de valoración/ABC dan $0 (`inventory_bp.py:1879`) |
### 2.2 Plan de producto: "Nexus Yonke" (5-6 semanas)
**M1 — Modelo de datos (1 semana)**
- `inventory`: `condition` (`nuevo|usado|reconstruido`), `condition_grade` (A/B/C/D), `is_unique_piece`, `serial_number`, `donor_vin`, `donor_make/model/year`, `donor_mileage_km`, `ownership` (`propio|consigna`), `acquisition_cost`.
- **Relajar unicidad**: índice único **parcial** `WHERE part_number IS NOT NULL` + auto-SKU para piezas únicas (`USADO-<VIN-corto>-<secuencia>`), manteniendo el flujo de partes nuevas intacto.
- Nueva tabla `inventory_images (inventory_id, url, order, is_primary)` con migración del `image_url` actual.
- `warranties`: `warranty_days` + política por defecto de usados en `tenant_config`.
- Nuevas tablas `consignments` / `consignment_items` (tercero, pieza, precio pactado, comisión, estado: en_consigna/vendida/devuelta/liquidada).
**M2 — Alta rápida en mostrador (1-2 semanas)**
- Flujo "Registrar pieza usada": **foto múltiple** (cámara del tablet) + **VIN del donador** (el decodificador NHTSA ya existe: `/pos/api/catalog/vin`) + pieza + precio + condición. Objetivo: **< 30 segundos por pieza**.
- **Ingreso de donador completo**: registrar el auto (VIN, km, color) y generar N piezas colgadas de ese donador con un solo flujo (hoy: pieza por pieza).
- Etiqueta propia por pieza única (código de barras ya implementado) para control físico en el patio.
**M3 — Búsqueda por donador (1 semana)**
- Filtro/búsqueda por vehículo **donador** (marca/modelo/año/color/km) sin requerir `part_number`; indexar esos campos en Meilisearch.
- Buscador "¿qué tengo de este auto?" + "¿qué tengo compatible con este auto?" (compat comprador, ya existe) como dos pestañas distintas.
**M4 — MeLi para usados (1 semana)**
- `condition` dinámico en el payload (usado/remanufacturado) + grado en el título/atributos.
- Mapeo de categorías ML para usados (ML tiene categorías de autopartes usadas con reglas propias).
- Fotos múltiples al anuncio (ML acepta hasta 10; hoy se manda 1).
**M5 — Consignación y garantía (1-2 semanas)**
- Liquidación a consignantes (reporte por periodo, comisión pactada, estado de cuenta del consignante).
- Garantía de usados por días con política por defecto y comprobante impreso.
- Reportes del segmento: rotación por donador, piezas sin foto, días en patio, margen por pieza.
**M6 — Plan y onboarding comercial (3-4 días)**
- Plan **"Yonke"** (precio menor): sin catálogo TecDoc, límites amplios, módulos ajustados, onboarding sin importación de catálogo nuevo (alta por escaneo/foto).
- Landing y material para el segmento (hoy `grep yonke|usadas` en docs/pitch → 0 resultados).
- **Marketplace B2B entre yonkes** (`is_seller`, hoy nunca activado en ningún tenant) como diferenciador: intercambio de piezas usadas entre deshuesaderos de la red.
---
## 3. Núcleo comercial y fiscal
**CFDI (crítico):**
- Corregir `codigo_postal` → `cp` (`invoicing_bp.py:65`) — desbloquea ingreso, egreso, PDF y factura global.
- **Complementos de pago (REP)** para PPD: `build_pago_payload` existe y nunca se invoca; hoy toda venta a crédito incumple SAT.
- Subir CSD por sucursal (hoy `has_csd:false` → ni timbrando funcionaría).
- Devoluciones: encolar CFDI **egreso** automático + reembolso trazado en caja.
- Moneda/tipo de cambio en facturación (`invoicing_bp.py:83` no lee `currency`).
**Contabilidad:**
- Pólizas para abonos de clientes, cortes de caja, devoluciones y apartados (4 funciones de `accounting_engine` existen y **nunca se llaman**).
- `record_return_entry` (no existe).
- Dejar de perder asientos en silencio: los hooks `try/except: pass` deben loggear (ya se perdió 1 de 18 ventas sin aviso).
- **Exportables para el contador**: pólizas, balanza, estado de resultados, DIOT (hoy solo antigüedad de saldos).
- CxP real con tabla de pagos a proveedores (`paid = 0 # TODO`).
- Caja: pagos mixtos no suman al efectivo esperado; apartados **duplican** el efectivo en el corte.
---
## 4. Catálogo, búsqueda y datos
1. **Arreglar el catálogo público** (visitante nuevo ve 500): `/api/catalog/search`, `/api/search`, `/api/categories`, modo OEM y detalle de parte. Modo `local` por defecto + degradación elegante (el POS ya tiene el guard `_oem_blocked`, el dashboard no).
2. **Conectar Meilisearch de verdad**: los índices `nexus_inventory_*` ya están poblados y actualizados a diario, **ningún código los consulta**; la búsqueda local hace seq-scan de 157k filas (175 ms).
3. **Calidad de fitment**: el fitment QWEN generó **20.5M filas ruidosas** (Corolla 2012 → 411,685 "compatibles" ≈ 40% del catálogo). Política de confianza por fuente + validación + priorizar la compat de proveedor (limpia, 29,760 MYEs). Decidir el destino del catálogo maestro (los 15 GB de JSON TecDoc siguen en disco).
4. **Imágenes masivas** (bloquea catálogo público y MeLi) — mismo pipeline de B1.
5. **Import/export round-trip** con compat, imágenes y min/max/reorder.
6. **Import con vehículo roto**: `bulk-import` devuelve 500 si el CSV trae make/model/year (consulta tablas de vehículos en la conexión del tenant) — justo el flujo de "para qué carro sirve".
7. **Multi-sucursal real**: todo el stock vive en la sucursal 1 y `inventory.branch_id` es NULL; transferencias/conteos/reorden implementados y **sin uso** (reorden además filtra por la columna equivocada y `reorder_point` es NULL al 100%).
8. **Etiquetas**: código de barras generado pero **sin endpoint de impresión de etiquetas** (solo tickets).
---
## 5. SaaS, onboarding y escala
| Tema | Estado | Acción |
|---|---|---|
| Alta de tenants | ❌ Rota (v1.2/v1.6 de master en el runner de tenants) | Marcar como master-only/mover al runner master |
| Template de tenant | Congelado en 32 tablas (le faltan ~56) | Refrescar template en cada alta o eliminarlo del flujo |
| Planes | `demo`/`standard` no existen en `PLANS` → fallback a límites de `free` | Agregar planes reales o mapear; aplicar la expiración de demos |
| Fuga de ingresos | Upgrade a `enterprise` **gratis** desde el POS + demos vencidas sin efecto + `subscriptions` desincronizada | Quitar el autoservicio, sincronizar `tenants.plan`↔`subscriptions.plan`, job diario de expiración |
| Seguridad multi-tenant | `/api/admin/tenants*` del dashboard **sin auth** | Proteger + quitar `?tenant=` de la superficie pública |
| Módulos | Dos nomenclaturas (`module_*` vs `*_enabled`) con defaults contradictorios | Unificar y sembrar en el template |
| Drift de esquema | `v3.5_whatsapp_state_machine` marcada `SKIP` → 6 tenants sin `wa_customer_links`/`wa_learning_sessions` → **el chatbot WhatsApp falla en esos tenants** | Aplicar la migración real |
| Bases huérfanas | 6 DBs `tenant_*` sin fila en `tenants` (~84 MB) | Limpiar |
| Reset de demo | No limpia clientes ni conversaciones (filtra PII entre prospectos) | Completar el truncado |
| Escala a 100 tenants | Pool 2-20 conexiones × 8 workers × 100 tenants > límite de Postgres; todo con superusuario | PgBouncer + roles por tenant + métricas por tenant |
| Secretos | `POS_JWT_SECRET`/API keys en unit files legibles por grupo | EnvironmentFile root-only + rotación |
---
## 6. Roadmap propuesto (6 sprints)
| Sprint | Foco | Entregable verificable |
|---|---|---|
| **S1 — Estabilizar (1 sem)** | Backups (fix `((deleted++))` + verificar drill), alta de tenants, auth de `/api/admin/tenants*`, plan `demo`, expiración de demos | Respaldo fresco de 9 bases verificado con restore; un tenant nuevo creado de punta a punta |
| **S2 — MeLi cierra el ciclo (1 sem)** | Fase A completa (10 tareas) | Publicar 1 SKU desde el POS → vender en ML → orden convertida a venta → CFDI encolado |
| **S3 — Volumen MeLi (2 sem)** | Imágenes masivas + lotes + rate-limit + export/import con compat | 200+ SKUs publicados sin intervención manual, con progreso y reporte de fallos |
| **S4 — Yonke MVP (2-3 sem)** | M1+M2+M3 (modelo, alta rápida por donador, búsqueda por donador) | Un deshuesadero de prueba carga 50 piezas usadas con foto y VIN, y las encuentra por vehículo |
| **S5 — Fiscal y contabilidad (2 sem)** | CFDI (cp, REP, CSD, egresos), pólizas faltantes, caja (mixtos/apartados), exportables contador | Factura timbrada real; estado de resultados cuadra; DIOT exportable |
| **S6 — Vender más + abrir mercado (2-3 sem)** | MeLi Fase C (variaciones, catálogo, reputación, rentabilidad, preguntas IA) + Yonke M4-M6 (ML usados, consignación, plan Yonke) | Panel de rentabilidad por publicación; primera pieza usada publicada en ML como `used` |
---
## 7. Métricas de éxito
- **Confiabilidad:** 30 días consecutivos con respaldo verificado; 0 tenants con esquema incompleto; 0 endpoints 500 en el barrido automatizado.
- **MercadoLibre:** conversión de órdenes → venta en el POS (hoy 0%, meta 100%); publicaciones sincronizadas sin error (hoy 0/52, meta 100%); ≥200 SKUs publicados en volumen; % de órdenes facturadas automáticamente.
- **Yonke:** tiempo de alta de una pieza usada < 30 s; 100% de piezas usadas con foto y VIN de donador; búsqueda por donador con resultados < 500 ms; 1 cliente del segmento en producción.
- **Comercial:** tenants de pago con plan correcto aplicado (hoy 3 con plan desincronizado), fin de la fuga por autoservicio, ≥1 cliente nuevo onboardeado sin intervención manual.