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
264 lines
9.4 KiB
PL/PgSQL
264 lines
9.4 KiB
PL/PgSQL
-- Nexus Autoparts — Master Database Schema (PostgreSQL)
|
|
-- Adapted from SQLite vehicle_database/sql/schema.sql
|
|
-- NO TecDoc data included — tables are empty and ready for manual population.
|
|
|
|
-- =====================================================
|
|
-- VEHICLES
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS brands (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
country TEXT,
|
|
founded_year INTEGER,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS engines (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
displacement_cc REAL,
|
|
cylinders INTEGER,
|
|
fuel_type TEXT CHECK(fuel_type IN ('gasoline', 'diesel', 'electric', 'hybrid', 'other')),
|
|
power_hp INTEGER,
|
|
torque_nm INTEGER,
|
|
engine_code TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS models (
|
|
id SERIAL PRIMARY KEY,
|
|
brand_id INTEGER NOT NULL REFERENCES brands(id),
|
|
name TEXT NOT NULL,
|
|
body_type TEXT CHECK(body_type IN ('sedan', 'hatchback', 'suv', 'truck', 'coupe', 'convertible', 'wagon', 'van', 'other')),
|
|
generation TEXT,
|
|
production_start_year INTEGER,
|
|
production_end_year INTEGER,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(brand_id, name, generation)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS years (
|
|
id SERIAL PRIMARY KEY,
|
|
year INTEGER NOT NULL UNIQUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS model_year_engine (
|
|
id SERIAL PRIMARY KEY,
|
|
model_id INTEGER NOT NULL REFERENCES models(id),
|
|
year_id INTEGER NOT NULL REFERENCES years(id),
|
|
engine_id INTEGER NOT NULL REFERENCES engines(id),
|
|
trim_level TEXT,
|
|
drivetrain TEXT CHECK(drivetrain IN ('FWD', 'RWD', 'AWD', '4WD', 'other')),
|
|
transmission TEXT CHECK(transmission IN ('manual', 'automatic', 'CVT', 'other')),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(model_id, year_id, engine_id, trim_level)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_models_brand ON models(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_model_year_engine_model ON model_year_engine(model_id);
|
|
CREATE INDEX IF NOT EXISTS idx_model_year_engine_year ON model_year_engine(year_id);
|
|
CREATE INDEX IF NOT EXISTS idx_model_year_engine_engine ON model_year_engine(engine_id);
|
|
|
|
-- =====================================================
|
|
-- PARTS CATALOG
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS part_categories (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
name_es TEXT,
|
|
parent_id INTEGER REFERENCES part_categories(id),
|
|
slug TEXT UNIQUE,
|
|
icon_name TEXT,
|
|
display_order INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS part_groups (
|
|
id SERIAL PRIMARY KEY,
|
|
category_id INTEGER NOT NULL REFERENCES part_categories(id),
|
|
name TEXT NOT NULL,
|
|
name_es TEXT,
|
|
slug TEXT,
|
|
display_order INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS parts (
|
|
id SERIAL PRIMARY KEY,
|
|
oem_part_number TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_es TEXT,
|
|
group_id INTEGER REFERENCES part_groups(id),
|
|
description TEXT,
|
|
description_es TEXT,
|
|
weight_kg REAL,
|
|
material TEXT,
|
|
is_discontinued BOOLEAN DEFAULT FALSE,
|
|
superseded_by_id INTEGER REFERENCES parts(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS vehicle_parts (
|
|
id SERIAL PRIMARY KEY,
|
|
model_year_engine_id INTEGER NOT NULL REFERENCES model_year_engine(id),
|
|
part_id INTEGER NOT NULL REFERENCES parts(id),
|
|
quantity_required INTEGER DEFAULT 1,
|
|
position TEXT,
|
|
fitment_notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(model_year_engine_id, part_id, position)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_part_categories_parent ON part_categories(parent_id);
|
|
CREATE INDEX IF NOT EXISTS idx_part_categories_slug ON part_categories(slug);
|
|
CREATE INDEX IF NOT EXISTS idx_part_groups_category ON part_groups(category_id);
|
|
CREATE INDEX IF NOT EXISTS idx_parts_oem ON parts(oem_part_number);
|
|
CREATE INDEX IF NOT EXISTS idx_parts_group ON parts(group_id);
|
|
CREATE INDEX IF NOT EXISTS idx_vehicle_parts_mye ON vehicle_parts(model_year_engine_id);
|
|
CREATE INDEX IF NOT EXISTS idx_vehicle_parts_part ON vehicle_parts(part_id);
|
|
|
|
-- =====================================================
|
|
-- AFTERMARKET & CROSS-REFERENCES
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS manufacturers (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
type TEXT CHECK(type IN ('oem', 'aftermarket', 'remanufactured')),
|
|
quality_tier TEXT CHECK(quality_tier IN ('economy', 'standard', 'premium', 'oem')),
|
|
country TEXT,
|
|
logo_url TEXT,
|
|
website TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS aftermarket_parts (
|
|
id SERIAL PRIMARY KEY,
|
|
oem_part_id INTEGER NOT NULL REFERENCES parts(id),
|
|
manufacturer_id INTEGER NOT NULL REFERENCES manufacturers(id),
|
|
part_number TEXT NOT NULL,
|
|
name TEXT,
|
|
name_es TEXT,
|
|
quality_tier TEXT CHECK(quality_tier IN ('economy', 'standard', 'premium')),
|
|
price_usd REAL,
|
|
warranty_months INTEGER,
|
|
in_stock BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS part_cross_references (
|
|
id SERIAL PRIMARY KEY,
|
|
part_id INTEGER NOT NULL REFERENCES parts(id),
|
|
cross_reference_number TEXT NOT NULL,
|
|
reference_type TEXT CHECK(reference_type IN ('oem_alternate', 'supersession', 'interchange', 'competitor')),
|
|
source TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_aftermarket_oem ON aftermarket_parts(oem_part_id);
|
|
CREATE INDEX IF NOT EXISTS idx_aftermarket_manufacturer ON aftermarket_parts(manufacturer_id);
|
|
CREATE INDEX IF NOT EXISTS idx_aftermarket_part_number ON aftermarket_parts(part_number);
|
|
CREATE INDEX IF NOT EXISTS idx_cross_ref_part ON part_cross_references(part_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cross_ref_number ON part_cross_references(cross_reference_number);
|
|
|
|
-- =====================================================
|
|
-- DIAGRAMAS EXPLOSIONADOS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS diagrams (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
name_es TEXT,
|
|
group_id INTEGER NOT NULL REFERENCES part_groups(id),
|
|
image_path TEXT NOT NULL,
|
|
thumbnail_path TEXT,
|
|
svg_content TEXT,
|
|
width INTEGER,
|
|
height INTEGER,
|
|
display_order INTEGER DEFAULT 0,
|
|
source TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS vehicle_diagrams (
|
|
id SERIAL PRIMARY KEY,
|
|
diagram_id INTEGER NOT NULL REFERENCES diagrams(id),
|
|
model_year_engine_id INTEGER NOT NULL REFERENCES model_year_engine(id),
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(diagram_id, model_year_engine_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS diagram_hotspots (
|
|
id SERIAL PRIMARY KEY,
|
|
diagram_id INTEGER NOT NULL REFERENCES diagrams(id),
|
|
part_id INTEGER REFERENCES parts(id),
|
|
callout_number INTEGER,
|
|
label TEXT,
|
|
shape TEXT DEFAULT 'rect' CHECK(shape IN ('rect', 'circle', 'poly')),
|
|
coords TEXT NOT NULL,
|
|
color TEXT DEFAULT '#e74c3c',
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_diagrams_group ON diagrams(group_id);
|
|
CREATE INDEX IF NOT EXISTS idx_vehicle_diagrams_diagram ON vehicle_diagrams(diagram_id);
|
|
CREATE INDEX IF NOT EXISTS idx_vehicle_diagrams_mye ON vehicle_diagrams(model_year_engine_id);
|
|
CREATE INDEX IF NOT EXISTS idx_hotspots_diagram ON diagram_hotspots(diagram_id);
|
|
CREATE INDEX IF NOT EXISTS idx_hotspots_part ON diagram_hotspots(part_id);
|
|
|
|
-- =====================================================
|
|
-- FULL-TEXT SEARCH (PostgreSQL tsvector)
|
|
-- =====================================================
|
|
|
|
-- Add tsvector column to parts for full-text search
|
|
ALTER TABLE parts ADD COLUMN IF NOT EXISTS search_vector tsvector;
|
|
|
|
-- Create GIN index for fast full-text search
|
|
CREATE INDEX IF NOT EXISTS idx_parts_search ON parts USING GIN(search_vector);
|
|
|
|
-- Update function to populate search_vector
|
|
CREATE OR REPLACE FUNCTION parts_search_update() RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.search_vector :=
|
|
setweight(to_tsvector('spanish', COALESCE(NEW.name, '')), 'A') ||
|
|
setweight(to_tsvector('spanish', COALESCE(NEW.name_es, '')), 'A') ||
|
|
setweight(to_tsvector('spanish', COALESCE(NEW.oem_part_number, '')), 'B') ||
|
|
setweight(to_tsvector('spanish', COALESCE(NEW.description, '')), 'C') ||
|
|
setweight(to_tsvector('spanish', COALESCE(NEW.description_es, '')), 'C');
|
|
RETURN NEW;
|
|
END
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger to auto-update search_vector
|
|
DROP TRIGGER IF EXISTS parts_search_trigger ON parts;
|
|
CREATE TRIGGER parts_search_trigger
|
|
BEFORE INSERT OR UPDATE ON parts
|
|
FOR EACH ROW EXECUTE FUNCTION parts_search_update();
|
|
|
|
-- =====================================================
|
|
-- VIN CACHE
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS vin_cache (
|
|
id SERIAL PRIMARY KEY,
|
|
vin TEXT NOT NULL UNIQUE,
|
|
decoded_data TEXT NOT NULL,
|
|
make TEXT,
|
|
model TEXT,
|
|
year INTEGER,
|
|
engine_info TEXT,
|
|
body_class TEXT,
|
|
drive_type TEXT,
|
|
model_year_engine_id INTEGER REFERENCES model_year_engine(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
expires_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_vin_cache_vin ON vin_cache(vin);
|
|
CREATE INDEX IF NOT EXISTS idx_vin_cache_make_model ON vin_cache(make, model, year);
|