-- 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);