- 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
68 lines
2.8 KiB
PL/PgSQL
68 lines
2.8 KiB
PL/PgSQL
-- FASE 2: DB Performance Optimizations
|
|
-- Applies to: master DB (nexus_autoparts) and all tenant DBs
|
|
|
|
-- ─── MASTER DB ─────────────────────────────────
|
|
-- These indexes only exist in the master DB (nexus_autoparts). On tenant DBs
|
|
-- (the `parts`/`warehouse_inventory` OEM tables live only in master) they are
|
|
-- skipped via existence guards so this migration can run against tenants too.
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF to_regclass('public.parts') IS NOT NULL THEN
|
|
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_parts_name_part ON parts(name_part)';
|
|
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_parts_name_part_pattern ON parts(name_part text_pattern_ops)';
|
|
END IF;
|
|
IF to_regclass('public.warehouse_inventory') IS NOT NULL THEN
|
|
EXECUTE 'CREATE INDEX IF NOT EXISTS idx_wi_part_stock_positive ON warehouse_inventory(part_id) WHERE stock_quantity > 0';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ─── TENANT DB (run on each tenant) ────────────
|
|
|
|
-- O(1) stock summary table with trigger-based updates
|
|
CREATE TABLE IF NOT EXISTS inventory_stock_summary (
|
|
inventory_id INT PRIMARY KEY REFERENCES inventory(id) ON DELETE CASCADE,
|
|
branch_id INT REFERENCES branches(id),
|
|
stock INT NOT NULL DEFAULT 0,
|
|
last_updated TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_iss_branch ON inventory_stock_summary(branch_id);
|
|
|
|
-- Trigger function: recalculates stock for an item after every operation insert
|
|
CREATE OR REPLACE FUNCTION update_stock_summary()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
INSERT INTO inventory_stock_summary (inventory_id, branch_id, stock)
|
|
SELECT i.id, i.branch_id, COALESCE(SUM(io.quantity), 0)
|
|
FROM inventory i
|
|
LEFT JOIN inventory_operations io ON io.inventory_id = i.id
|
|
WHERE i.id = NEW.inventory_id
|
|
GROUP BY i.id, i.branch_id
|
|
ON CONFLICT (inventory_id) DO UPDATE SET
|
|
stock = EXCLUDED.stock,
|
|
branch_id = EXCLUDED.branch_id,
|
|
last_updated = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_update_stock_summary ON inventory_operations;
|
|
CREATE TRIGGER trg_update_stock_summary
|
|
AFTER INSERT ON inventory_operations
|
|
FOR EACH ROW EXECUTE FUNCTION update_stock_summary();
|
|
|
|
-- Composite index for inventory_operations (used by stock + history queries)
|
|
CREATE INDEX IF NOT EXISTS idx_inv_ops_inventory_branch_created
|
|
ON inventory_operations(inventory_id, branch_id, created_at DESC);
|
|
|
|
-- Initial population of summary table (idempotent)
|
|
INSERT INTO inventory_stock_summary (inventory_id, branch_id, stock)
|
|
SELECT i.id, i.branch_id, COALESCE(SUM(io.quantity), 0)
|
|
FROM inventory i
|
|
LEFT JOIN inventory_operations io ON io.inventory_id = i.id
|
|
GROUP BY i.id, i.branch_id
|
|
ON CONFLICT (inventory_id) DO UPDATE SET
|
|
stock = EXCLUDED.stock,
|
|
branch_id = EXCLUDED.branch_id,
|
|
last_updated = NOW();
|