Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- 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
This commit is contained in:
2026-10-12 00:57:31 +00:00
parent f2d4558b80
commit a386ca5b4a
165 changed files with 31313 additions and 2203 deletions

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Idempotent master-database schema for the B2B yonke marketplace.
Creates/ensures, in the master DB (nexus_autoparts):
- b2b_listings (used-part wholesale catalog published by sellers)
- marketplace_orders (cross-tenant order header; from v1.6_marketplace.sql)
- marketplace_order_items (order lines; + listing_id link column)
- tenants.is_seller (seller opt-in flag)
and activates is_seller=true for the demo sellers (47 Yonke Demo, 45 Capi Autopartes).
Safe to run repeatedly: every statement is CREATE ... IF NOT EXISTS / ADD COLUMN
IF NOT EXISTS / UPDATE (idempotent). It never drops or rewrites data.
Run:
cd /home/Autopartes && python3 scripts/b2b_marketplace_schema.py
# or point at another master DB:
MASTER_DB_URL=postgresql://user@host/nexus_autoparts python3 scripts/b2b_marketplace_schema.py
"""
import os
import sys
import psycopg2
MASTER_DB_URL = (
os.environ.get('MASTER_DB_URL')
or 'postgresql://postgres@localhost/nexus_autoparts'
)
# Demo sellers that must appear in the marketplace so it is never empty.
DEMO_SELLER_TENANT_IDS = (47, 45)
B2B_LISTINGS_DDL = """
CREATE TABLE IF NOT EXISTS b2b_listings (
id SERIAL PRIMARY KEY,
seller_tenant_id INTEGER NOT NULL,
seller_inventory_id INTEGER NOT NULL,
part_number VARCHAR(100),
name VARCHAR(300),
condition VARCHAR(20) DEFAULT 'usado',
condition_grade VARCHAR(2),
price_wholesale NUMERIC(12,2) NOT NULL DEFAULT 0,
donor_make VARCHAR(60),
donor_model VARCHAR(60),
donor_year INTEGER,
image_url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'available',
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (seller_tenant_id, seller_inventory_id)
)
"""
# Mirror of pos/migrations/v1.6_marketplace.sql (master DB), kept here so this
# script can repair a master DB where the migration was skipped.
MARKETPLACE_ORDERS_DDL = """
CREATE TABLE IF NOT EXISTS marketplace_orders (
id SERIAL PRIMARY KEY,
buyer_tenant_id INTEGER NOT NULL,
seller_tenant_id INTEGER NOT NULL,
buyer_name VARCHAR(200),
seller_name VARCHAR(200),
total NUMERIC(12,2),
status VARCHAR(20) DEFAULT 'pending',
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)
"""
MARKETPLACE_ORDER_ITEMS_DDL = """
CREATE TABLE IF NOT EXISTS marketplace_order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES marketplace_orders(id),
part_number VARCHAR(100),
part_name VARCHAR(300),
quantity INTEGER,
unit_price NUMERIC(12,2),
subtotal NUMERIC(12,2)
)
"""
STATEMENTS = [
('b2b_listings', B2B_LISTINGS_DDL),
('marketplace_orders', MARKETPLACE_ORDERS_DDL),
('marketplace_order_items', MARKETPLACE_ORDER_ITEMS_DDL),
# Extensions to tables that may already exist.
('marketplace_order_items.listing_id',
"ALTER TABLE marketplace_order_items ADD COLUMN IF NOT EXISTS listing_id INTEGER"),
('tenants.is_seller',
"ALTER TABLE tenants ADD COLUMN IF NOT EXISTS is_seller BOOLEAN DEFAULT FALSE"),
# Indexes.
('idx_b2b_listings_seller',
"CREATE INDEX IF NOT EXISTS idx_b2b_listings_seller ON b2b_listings(seller_tenant_id)"),
('idx_b2b_listings_status',
"CREATE INDEX IF NOT EXISTS idx_b2b_listings_status ON b2b_listings(status)"),
('idx_b2b_listings_catalog',
"CREATE INDEX IF NOT EXISTS idx_b2b_listings_catalog ON b2b_listings(status, condition_grade)"),
('idx_mp_orders_buyer',
"CREATE INDEX IF NOT EXISTS idx_mp_orders_buyer ON marketplace_orders(buyer_tenant_id)"),
('idx_mp_orders_seller',
"CREATE INDEX IF NOT EXISTS idx_mp_orders_seller ON marketplace_orders(seller_tenant_id)"),
('idx_mp_orders_status',
"CREATE INDEX IF NOT EXISTS idx_mp_orders_status ON marketplace_orders(status)"),
('idx_mp_order_items_order',
"CREATE INDEX IF NOT EXISTS idx_mp_order_items_order ON marketplace_order_items(order_id)"),
('idx_mp_order_items_listing',
"CREATE INDEX IF NOT EXISTS idx_mp_order_items_listing ON marketplace_order_items(listing_id)"),
]
def log(msg):
print(f" -> {msg}")
def ensure_schema(conn):
cur = conn.cursor()
for label, sql in STATEMENTS:
cur.execute(sql)
log(f"ok: {label}")
conn.commit()
cur.close()
def verify(conn):
cur = conn.cursor()
cur.execute("""
SELECT to_regclass('b2b_listings'),
to_regclass('marketplace_orders'),
to_regclass('marketplace_order_items')
""")
row = cur.fetchone()
cur.close()
return row
def activate_demo_sellers(conn):
cur = conn.cursor()
cur.execute(
"SELECT id, name, COALESCE(is_seller, false) FROM tenants WHERE id = ANY(%s) ORDER BY id",
(list(DEMO_SELLER_TENANT_IDS),),
)
found = cur.fetchall()
if not found:
log("no demo tenants found (47, 45) — skipping is_seller activation")
cur.close()
return
cur.execute(
"UPDATE tenants SET is_seller = TRUE WHERE id = ANY(%s)",
(list(DEMO_SELLER_TENANT_IDS),),
)
conn.commit()
for tenant_id, name, was_seller in found:
state = "already seller" if was_seller else "activated"
log(f"tenant {tenant_id} ({name}): {state}")
cur.close()
def main():
print(f"B2B marketplace master schema -> {MASTER_DB_URL}")
conn = psycopg2.connect(MASTER_DB_URL)
try:
ensure_schema(conn)
activate_demo_sellers(conn)
b2b, orders, items = verify(conn)
print("Verification:")
log(f"b2b_listings={b2b}")
log(f"marketplace_orders={orders}")
log(f"marketplace_order_items={items}")
finally:
conn.close()
print("Done.")
return 0
if __name__ == '__main__':
sys.exit(main())