feat: complete session — catalog, marketplace, WhatsApp, peer-to-peer, install scripts

Major features:
- Pixel-Perfect glassmorphism design (landing + POS + public catalog)
- OEM/Local catalog toggle with Nexpart taxonomy (14 groups, 108 subgroups, 558 part types)
- Marketplace B2B Phase 1 (bodegas, POs, status machine, WA+email notifications)
- Peer-to-peer inventory (multi-instance, LAN discovery)
- WhatsApp: photo→Vision AI, voice→Whisper, conversational quotations
- Smart unified search (VIN/plate/part_number/keyword auto-detect)
- Shop Supplies tab (vehicle-independent parts)
- Chatbot AI fallback chain (5 models) + response cache
- CSV inventory import tool + setup_instance.sh installer
- Tablet-responsive CSS + sidebar toggle
- Filters, export CSV, employee edit, business data save
- Quotation system (WA→POS) with auto-print on confirmation
- Live stats on landing page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 05:35:53 +00:00
parent 6b097614a0
commit e95f7cf684
54 changed files with 11226 additions and 1422 deletions

149
sql/marketplace_schema.sql Normal file
View File

@@ -0,0 +1,149 @@
-- ═══════════════════════════════════════════════════════════════════════
-- Nexus Marketplace B2B — Phase 1 schema
-- Target: nexus_autoparts (master DB)
-- Date: 2026-04-09
-- ═══════════════════════════════════════════════════════════════════════
--
-- This migration is idempotent. Running it multiple times has no effect.
-- All new tables use IF NOT EXISTS and ALTERs use IF NOT EXISTS on columns.
--
-- Tables:
-- 1. bodegas — warehouse registry in the Nexus network
-- 2. purchase_orders — PO headers
-- 3. purchase_order_items — PO line items
-- 4. po_status_history — audit trail for PO state changes
--
-- Altered tables:
-- - warehouse_inventory (add bodega_id, currency, updated_at)
--
-- NOTE: the `users` table alter for marketplace_role + bodega_id lives in
-- the TENANT databases (each refaccionaria), not the master DB. That
-- migration is applied separately per-tenant via tenant_migrations.
-- ═══════════════════════════════════════════════════════════════════════
-- 1. BODEGAS — warehouse registry
-- ═══════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS bodegas (
id_bodega SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
owner_name VARCHAR(200),
whatsapp_phone VARCHAR(20) NOT NULL,
email VARCHAR(200),
city VARCHAR(100),
state VARCHAR(50),
address TEXT,
verified BOOLEAN NOT NULL DEFAULT FALSE,
verified_at TIMESTAMP,
commission_pct NUMERIC(5, 2) NOT NULL DEFAULT 0, -- reserved for Phase 3
notes TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_bodegas_verified ON bodegas (verified) WHERE verified = TRUE;
CREATE INDEX IF NOT EXISTS idx_bodegas_city ON bodegas (city);
-- ═══════════════════════════════════════════════════════════════════════
-- 2. WAREHOUSE_INVENTORY — extend existing table
-- ═══════════════════════════════════════════════════════════════════════
-- Existing schema has: id_inventory, user_id, part_id, price, stock_quantity,
-- min_order_quantity, warehouse_location, updated_at.
-- We add bodega_id (FK to the new table) and currency, keeping user_id as
-- the legacy owner pointer.
ALTER TABLE warehouse_inventory
ADD COLUMN IF NOT EXISTS bodega_id INTEGER REFERENCES bodegas(id_bodega),
ADD COLUMN IF NOT EXISTS currency VARCHAR(3) NOT NULL DEFAULT 'MXN';
CREATE INDEX IF NOT EXISTS idx_wi_bodega ON warehouse_inventory (bodega_id);
-- ═══════════════════════════════════════════════════════════════════════
-- 3. PURCHASE_ORDERS — PO headers
-- ═══════════════════════════════════════════════════════════════════════
-- Status state machine:
-- draft → submitted → confirmed → ready → delivered → closed
-- ↘ rejected (terminal)
CREATE TABLE IF NOT EXISTS purchase_orders (
id_po SERIAL PRIMARY KEY,
buyer_tenant_id INTEGER NOT NULL,
buyer_user_id INTEGER NOT NULL,
buyer_phone VARCHAR(20),
buyer_email VARCHAR(200),
buyer_display_name VARCHAR(200),
bodega_id INTEGER NOT NULL REFERENCES bodegas(id_bodega),
status VARCHAR(20) NOT NULL DEFAULT 'draft',
total_amount NUMERIC(12, 2),
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
buyer_notes TEXT,
seller_notes TEXT,
delivery_method VARCHAR(50), -- 'pickup' | 'delivery'
delivery_address TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
submitted_at TIMESTAMP,
confirmed_at TIMESTAMP,
ready_at TIMESTAMP,
delivered_at TIMESTAMP,
closed_at TIMESTAMP,
CONSTRAINT chk_po_status CHECK (
status IN ('draft', 'submitted', 'confirmed', 'rejected', 'ready', 'delivered', 'closed')
)
);
CREATE INDEX IF NOT EXISTS idx_po_buyer ON purchase_orders (buyer_tenant_id, buyer_user_id);
CREATE INDEX IF NOT EXISTS idx_po_bodega ON purchase_orders (bodega_id, status);
CREATE INDEX IF NOT EXISTS idx_po_status ON purchase_orders (status);
CREATE INDEX IF NOT EXISTS idx_po_created ON purchase_orders (created_at DESC);
-- ═══════════════════════════════════════════════════════════════════════
-- 4. PURCHASE_ORDER_ITEMS — line items
-- ═══════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS purchase_order_items (
id_po_item SERIAL PRIMARY KEY,
po_id INTEGER NOT NULL REFERENCES purchase_orders(id_po) ON DELETE CASCADE,
part_id INTEGER NOT NULL REFERENCES parts(id_part),
oem_part_number VARCHAR(100),
part_name VARCHAR(300),
manufacturer VARCHAR(200),
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(12, 2),
subtotal NUMERIC(12, 2),
confirmed_qty INTEGER, -- bodega may adjust after confirmation
notes TEXT
);
CREATE INDEX IF NOT EXISTS idx_po_items_po ON purchase_order_items (po_id);
CREATE INDEX IF NOT EXISTS idx_po_items_part ON purchase_order_items (part_id);
-- ═══════════════════════════════════════════════════════════════════════
-- 5. PO_STATUS_HISTORY — audit trail
-- ═══════════════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS po_status_history (
id_history SERIAL PRIMARY KEY,
po_id INTEGER NOT NULL REFERENCES purchase_orders(id_po) ON DELETE CASCADE,
from_status VARCHAR(20),
to_status VARCHAR(20) NOT NULL,
actor_user_id INTEGER,
actor_kind VARCHAR(20), -- 'buyer' | 'seller' | 'system' | 'admin'
note TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_po_history_po ON po_status_history (po_id, created_at);
-- ═══════════════════════════════════════════════════════════════════════
-- Verification queries
-- ═══════════════════════════════════════════════════════════════════════
-- Run after applying to check everything landed:
-- SELECT COUNT(*) FROM bodegas;
-- SELECT COUNT(*) FROM purchase_orders;
-- SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'warehouse_inventory' AND column_name IN ('bodega_id', 'currency');