FASE 4-5-6: Infraestructura, CRM, Service Orders, Notificaciones, Ahorro, Logistica, API Publica
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
This commit is contained in:
263
sql/schema_master_postgres.sql
Normal file
263
sql/schema_master_postgres.sql
Normal file
@@ -0,0 +1,263 @@
|
||||
-- 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);
|
||||
Reference in New Issue
Block a user