- Migrate from SQLite to PostgreSQL with normalized schema - Add 11 lookup tables (fuel_type, body_type, drivetrain, transmission, materials, position_part, manufacture_type, quality_tier, countries, reference_type, shapes) - Rewrite dashboard/server.py (76 routes) using SQLAlchemy text() queries - Rewrite console/db.py (27 methods) using SQLAlchemy ORM - Add models.py with 27 SQLAlchemy model definitions - Add config.py for centralized DB_URL configuration - Add migrate_to_postgres.py migration script - Add docs/METABASE_GUIDE.md with complete data entry guide - Rebrand from "AUTOPARTS DB" to "NEXUS AUTOPARTS" - Fill vehicle data gaps via NHTSA API + heuristics: engines (cylinders, power, torque), brands (country, founded_year), models (body_type, production years), MYE (drivetrain, transmission, trim) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20 KiB
Guía Metabase — Nexus Autoparts
1. Conexión a la Base de Datos
Datos de conexión PostgreSQL
| Campo | Valor |
|---|---|
| Host | localhost (o la IP del servidor: 192.168.10.198) |
| Puerto | 5432 |
| Base de datos | nexus_autoparts |
| Usuario | nexus |
| Contraseña | nexus_autoparts_2026 |
| SSL | No requerido (conexión local) |
Pasos en Metabase
- Ir a Admin → Databases → Add database
- Seleccionar PostgreSQL
- Llenar los campos con los datos anteriores
- Click Save
- Metabase sincronizará las tablas automáticamente (~30 segundos)
Tip: Si Metabase está en otro servidor, usar la IP
192.168.10.198en lugar delocalhost.
2. Estructura de la Base de Datos
Diagrama de relaciones simplificado
brands ──→ models ──→ model_year_engine (MYE) ←── years
↑ ↑ ↑
engines │ vehicle_parts ──→ parts
│ ↑
vehicle_diagrams part_cross_references
↓ aftermarket_parts
diagrams diagram_hotspots
Tablas principales (con datos)
| Tabla | Registros | Descripción |
|---|---|---|
brands |
102 | Marcas de vehículos |
models |
4,031 | Modelos por marca |
years |
80 | Años (1946-2026) |
engines |
13,430 | Motores con specs |
model_year_engine |
47,858 | Combinación marca-modelo-año-motor |
Tablas de piezas (vacías — para llenar)
| Tabla | Descripción |
|---|---|
parts |
Piezas OEM (número de parte, nombre, grupo) |
vehicle_parts |
Relación pieza ↔ vehículo (fitments) |
aftermarket_parts |
Piezas aftermarket por fabricante |
part_cross_references |
Intercambios y referencias cruzadas |
manufacturers |
47 fabricantes ya cargados |
Tablas lookup (catálogos de referencia)
| Tabla | Valores actuales |
|---|---|
part_categories |
12: Body, Brake, Cooling, Drivetrain, Electrical, Engine, Exhaust, Fuel, HVAC, Steering, Suspension, Transmission |
part_groups |
63 grupos dentro de las categorías |
quality_tier |
economy, standard, oem, premium |
reference_type |
competitor, interchange, oem_alternate, supersession |
position_part |
front, rear |
manufacture_type |
aftermarket, oem |
materials |
(vacía — agregar según se necesite) |
3. Alta de Piezas OEM
Orden obligatorio de carga
1. materials (si aplica)
2. parts ← PRIMERO las piezas
3. vehicle_parts ← DESPUÉS los fitments
4. aftermarket_parts
5. part_cross_references
IMPORTANTE: Respetar este orden.
vehicle_partsyaftermarket_partsrequieren que la pieza ya exista enparts.
3.1 Agregar una pieza OEM (parts)
En Metabase: click + New → SQL query → ejecutar:
INSERT INTO parts (oem_part_number, name_part, name_es, group_id, description, description_es, weight_kg, id_material)
VALUES (
'04465-06090', -- Número OEM (obligatorio, único por grupo)
'Front Brake Pad Set', -- Nombre en inglés (obligatorio)
'Juego de Balatas Delanteras', -- Nombre en español (opcional)
16, -- group_id: 16 = Brake Pads (ver tabla abajo)
'Ceramic brake pad set for front axle', -- Descripción EN (opcional)
'Juego de balatas cerámicas para eje delantero', -- Descripción ES (opcional)
1.2, -- Peso en kg (opcional)
NULL -- id_material (opcional, ver tabla materials)
);
Carga masiva de piezas
INSERT INTO parts (oem_part_number, name_part, name_es, group_id, description) VALUES
('04465-06090', 'Front Brake Pad Set', 'Juego Balatas Delanteras', 16, 'Ceramic front pads'),
('04465-33471', 'Front Brake Pad Set', 'Juego Balatas Delanteras', 16, 'Semi-metallic front pads'),
('43512-06150', 'Front Brake Rotor', 'Disco de Freno Delantero', 17, 'Vented front rotor 296mm'),
('19101-28491', 'Radiator', 'Radiador', 31, 'Aluminum core radiator'),
('16400-28531', 'Cooling Fan Assembly', 'Ensamble Ventilador', 35, 'Electric cooling fan')
ON CONFLICT DO NOTHING;
Referencia de group_id (grupos de piezas)
| group_id | Grupo | Categoría |
|---|---|---|
| Frenos y Ruedas | ||
| 16 | Brake Pads | Brake & Wheel Hub |
| 17 | Brake Rotors | Brake & Wheel Hub |
| 20 | Brake Calipers | Brake & Wheel Hub |
| 27 | Wheel Bearings | Brake & Wheel Hub |
| 28 | Wheel Hubs | Brake & Wheel Hub |
| Motor | ||
| 70 | Oil Filters | Engine |
| 71 | Air Filters | Engine |
| 72 | Spark Plugs | Engine |
| 73 | Belts | Engine |
| 76 | Timing Components | Engine |
| 91 | Engine Mounts | Engine |
| Enfriamiento | ||
| 31 | Radiators | Cooling System |
| 33 | Water Pumps | Cooling System |
| 34 | Thermostats | Cooling System |
| 35 | Cooling Fans | Cooling System |
| Eléctrico | ||
| 55 | Alternators | Electrical & Lighting |
| 56 | Starters | Electrical & Lighting |
| 57 | Ignition Coils | Electrical & Lighting |
| 65 | Sensors | Electrical & Lighting |
| Combustible | ||
| 110 | Fuel Pumps | Fuel & Air |
| 111 | Fuel Filters | Fuel & Air |
| 112 | Fuel Injectors | Fuel & Air |
| Escape | ||
| 99 | Catalytic Converters | Exhaust |
| 100 | Mufflers | Exhaust |
| 108 | Headers | Exhaust |
| Dirección | ||
| 141 | Power Steering Pumps | Steering |
| 144 | Steering Racks | Steering |
| 145 | Steering Gearboxes | Steering |
| 146 | Tie Rods | Steering |
| 147 | Tie Rod Ends | Steering |
| 148 | Inner Tie Rods | Steering |
| 151 | Pitman Arms | Steering |
| 152 | Idler Arms | Steering |
| 153 | Center Links | Steering |
| 154 | Drag Links | Steering |
| 155 | Steering Knuckles | Steering |
| 191 | Steering Dampers | Steering |
| Suspensión | ||
| 156 | Shocks | Suspension |
| 157 | Struts | Suspension |
| 158 | Strut Mounts | Suspension |
| 159 | Coil Springs | Suspension |
| 160 | Leaf Springs | Suspension |
| 161 | Control Arms | Suspension |
| 164 | Ball Joints | Suspension |
| 165 | Bushings | Suspension |
| 167 | Sway Bar Links | Suspension |
| 168 | Sway Bar Bushings | Suspension |
| 169 | Torsion Bars | Suspension |
| 170 | Trailing Arms | Suspension |
| Transmisión | ||
| 175 | Transmission Filters | Transmission |
| 185 | Transmission Mounts | Transmission |
| A/C y Calefacción | ||
| 127 | AC Compressors | Heat & Air Conditioning |
| 135 | Blower Motors | Heat & Air Conditioning |
| 138 | Cabin Air Filters | Heat & Air Conditioning |
| Tren Motriz | ||
| 44 | CV Axles | Drivetrain |
| 45 | CV Joints | Drivetrain |
| 50 | Axle Shafts | Drivetrain |
| Carrocería | ||
| 15 | Moldings & Trim | Body & Lamp Assembly |
4. Alta de Fitments (Pieza ↔ Vehículo)
Un fitment vincula una pieza con un vehículo específico (combinación modelo-año-motor).
4.1 Encontrar el id_mye del vehículo
-- Buscar el id_mye para un vehículo específico
SELECT mye.id_mye, b.name_brand, m.name_model, y.year_car, e.name_engine
FROM model_year_engine mye
JOIN models m ON mye.model_id = m.id_model
JOIN brands b ON m.brand_id = b.id_brand
JOIN years y ON mye.year_id = y.id_year
JOIN engines e ON mye.engine_id = e.id_engine
WHERE b.name_brand ILIKE 'Toyota'
AND m.name_model ILIKE 'Camry'
AND y.year_car = 2020
ORDER BY e.name_engine;
4.2 Encontrar el id_part de la pieza
-- Buscar pieza por número OEM
SELECT id_part, oem_part_number, name_part FROM parts
WHERE oem_part_number = '04465-06090';
4.3 Crear el fitment (vehicle_parts)
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, id_position_part, fitment_notes)
VALUES (
12345, -- id_mye del vehículo (del paso 4.1)
67890, -- id_part de la pieza (del paso 4.2)
1, -- Cantidad requerida (1 juego)
1, -- Posición: 1 = front, 2 = rear (ver position_part)
'Fits all trim levels' -- Notas de compatibilidad (opcional)
);
4.4 Fitment masivo (misma pieza en varios vehículos)
-- Ejemplo: Balata 04465-06090 compatible con todos los Camry 2018-2023
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, id_position_part)
SELECT mye.id_mye,
(SELECT id_part FROM parts WHERE oem_part_number = '04465-06090'),
1, -- cantidad
1 -- posición: front
FROM model_year_engine mye
JOIN models m ON mye.model_id = m.id_model
JOIN brands b ON m.brand_id = b.id_brand
JOIN years y ON mye.year_id = y.id_year
WHERE b.name_brand = 'TOYOTA'
AND m.name_model = 'Camry'
AND y.year_car BETWEEN 2018 AND 2023
ON CONFLICT DO NOTHING;
Valores de position_part
| id_position_part | Nombre |
|---|---|
| 1 | front |
| 2 | rear |
Para agregar más posiciones (left, right, upper, lower):
INSERT INTO position_part (name_position_part) VALUES ('left'), ('right'), ('upper'), ('lower');
5. Alta de Piezas Aftermarket
Vincula una pieza aftermarket con su equivalente OEM y el fabricante.
5.1 Crear pieza aftermarket (aftermarket_parts)
INSERT INTO aftermarket_parts (
oem_part_id, manufacturer_id, part_number,
name_aftermarket_parts, name_es,
id_quality_tier, price_usd, warranty_months
) VALUES (
67890, -- id_part de la pieza OEM equivalente (de tabla parts)
3, -- id_manufacture del fabricante (ver manufacturers)
'CXD1293', -- Número de parte aftermarket
'Ceramic Brake Pad Set', -- Nombre EN
'Juego Balatas Cerámicas', -- Nombre ES
3, -- Calidad: 1=economy, 2=oem, 3=premium, 4=standard
45.99, -- Precio USD
24 -- Garantía en meses
);
5.2 Carga masiva aftermarket
INSERT INTO aftermarket_parts (oem_part_id, manufacturer_id, part_number, name_aftermarket_parts, id_quality_tier, price_usd, warranty_months) VALUES
(67890, 3, 'CXD1293', 'Premium Ceramic Brake Pad', 3, 45.99, 24),
(67890, 5, 'MKD1293', 'Economy Semi-Metallic Brake Pad', 1, 22.50, 12),
(67890, 8, 'PBR1293', 'OEM Replacement Brake Pad', 2, 38.00, 18)
ON CONFLICT DO NOTHING;
Referencia de quality_tier
| id_quality_tier | Nombre | Descripción |
|---|---|---|
| 1 | economy | Económica, calidad básica |
| 2 | oem | Calidad equivalente al original |
| 3 | premium | Calidad superior al original |
| 4 | standard | Calidad estándar del mercado |
Consultar fabricantes existentes
SELECT m.id_manufacture, m.name_manufacture, mt.name_type_manu AS tipo,
qt.name_quality AS calidad, c.name_country AS pais
FROM manufacturers m
LEFT JOIN manufacture_type mt ON m.id_type_manu = mt.id_type_manu
LEFT JOIN quality_tier qt ON m.id_quality_tier = qt.id_quality_tier
LEFT JOIN countries c ON m.id_country = c.id_country
ORDER BY m.name_manufacture;
Agregar un nuevo fabricante
INSERT INTO manufacturers (name_manufacture, id_type_manu, id_quality_tier, id_country, website)
VALUES (
'BREMBO',
1, -- 1=aftermarket, 2=oem
3, -- 1=economy, 2=oem, 3=premium, 4=standard
NULL, -- id_country (buscar en tabla countries o agregar)
'https://www.brembo.com'
);
6. Alta de Referencias Cruzadas / Intercambios
Las cross-references vinculan una pieza OEM con números de parte alternativos.
6.1 Crear referencia cruzada (part_cross_references)
INSERT INTO part_cross_references (part_id, cross_reference_number, id_ref_type, source_ref, notes)
VALUES (
67890, -- id_part de la pieza OEM
'D1293', -- Número de referencia cruzada
2, -- Tipo: 2 = interchange (ver tabla abajo)
'Wagner Catalog', -- Fuente de la referencia (opcional)
'Direct replacement' -- Notas (opcional)
);
6.2 Carga masiva de cross-references
INSERT INTO part_cross_references (part_id, cross_reference_number, id_ref_type, source_ref) VALUES
(67890, 'D1293', 2, 'Wagner'), -- interchange
(67890, '04465-06100', 3, 'Toyota'), -- oem_alternate (número OEM alterno)
(67890, 'BC1293', 1, 'Akebono'), -- competitor
(67890, '04465-06080', 4, 'Toyota') -- supersession (reemplaza a esta)
ON CONFLICT DO NOTHING;
Valores de reference_type
| id_ref_type | Nombre | Uso |
|---|---|---|
| 1 | competitor | Número equivalente de competidor |
| 2 | interchange | Intercambio directo compatible |
| 3 | oem_alternate | Número OEM alterno del mismo fabricante |
| 4 | supersession | El número OEM que esta pieza reemplaza |
7. Alta de Materiales
Si necesitas especificar el material de una pieza:
-- Agregar materiales
INSERT INTO materials (name_material) VALUES
('Steel'),
('Aluminum'),
('Ceramic'),
('Rubber'),
('Cast Iron'),
('Stainless Steel'),
('Copper'),
('Plastic')
ON CONFLICT (name_material) DO NOTHING;
-- Luego, al crear una pieza, usar el id_material correspondiente:
-- SELECT id_material FROM materials WHERE name_material = 'Ceramic';
8. Queries Útiles para Metabase (Dashboards)
Vista completa de una pieza con todos sus datos
SELECT
p.oem_part_number AS "Número OEM",
p.name_part AS "Nombre EN",
p.name_es AS "Nombre ES",
pg.name_part_group AS "Grupo",
pc.name_part_category AS "Categoría",
p.weight_kg AS "Peso (kg)",
mat.name_material AS "Material",
COUNT(DISTINCT vp.model_year_engine_id) AS "Vehículos compatibles",
COUNT(DISTINCT ap.id_aftermarket_parts) AS "Opciones aftermarket",
COUNT(DISTINCT pcr.id_part_cross_ref) AS "Cross-references"
FROM parts p
JOIN part_groups pg ON p.group_id = pg.id_part_group
JOIN part_categories pc ON pg.category_id = pc.id_part_category
LEFT JOIN materials mat ON p.id_material = mat.id_material
LEFT JOIN vehicle_parts vp ON vp.part_id = p.id_part
LEFT JOIN aftermarket_parts ap ON ap.oem_part_id = p.id_part
LEFT JOIN part_cross_references pcr ON pcr.part_id = p.id_part
GROUP BY p.id_part, p.oem_part_number, p.name_part, p.name_es,
pg.name_part_group, pc.name_part_category, p.weight_kg, mat.name_material
ORDER BY pc.name_part_category, pg.name_part_group, p.name_part;
Catálogo de piezas por vehículo
SELECT
b.name_brand AS "Marca",
m.name_model AS "Modelo",
y.year_car AS "Año",
e.name_engine AS "Motor",
pc.name_part_category AS "Categoría",
pg.name_part_group AS "Grupo",
p.oem_part_number AS "# OEM",
p.name_part AS "Pieza",
vp.quantity_required AS "Cantidad",
pp.name_position_part AS "Posición"
FROM vehicle_parts vp
JOIN model_year_engine mye ON vp.model_year_engine_id = mye.id_mye
JOIN models m ON mye.model_id = m.id_model
JOIN brands b ON m.brand_id = b.id_brand
JOIN years y ON mye.year_id = y.id_year
JOIN engines e ON mye.engine_id = e.id_engine
JOIN parts p ON vp.part_id = p.id_part
JOIN part_groups pg ON p.group_id = pg.id_part_group
JOIN part_categories pc ON pg.category_id = pc.id_part_category
LEFT JOIN position_part pp ON vp.id_position_part = pp.id_position_part
WHERE b.name_brand ILIKE '{{marca}}'
AND m.name_model ILIKE '{{modelo}}'
ORDER BY pc.display_order, pg.display_order, p.name_part;
En Metabase,
{{marca}}y{{modelo}}se convierten en filtros interactivos.
Piezas aftermarket con precios
SELECT
p.oem_part_number AS "OEM #",
p.name_part AS "Pieza OEM",
mfr.name_manufacture AS "Fabricante",
ap.part_number AS "# Aftermarket",
ap.name_aftermarket_parts AS "Nombre",
qt.name_quality AS "Calidad",
ap.price_usd AS "Precio USD",
ap.warranty_months AS "Garantía (meses)"
FROM aftermarket_parts ap
JOIN parts p ON ap.oem_part_id = p.id_part
JOIN manufacturers mfr ON ap.manufacturer_id = mfr.id_manufacture
LEFT JOIN quality_tier qt ON ap.id_quality_tier = qt.id_quality_tier
ORDER BY p.oem_part_number, qt.name_quality DESC, ap.price_usd;
Cross-references de una pieza
SELECT
p.oem_part_number AS "OEM #",
p.name_part AS "Pieza",
pcr.cross_reference_number AS "# Referencia",
rt.name_ref_type AS "Tipo",
pcr.source_ref AS "Fuente",
pcr.notes AS "Notas"
FROM part_cross_references pcr
JOIN parts p ON pcr.part_id = p.id_part
LEFT JOIN reference_type rt ON pcr.id_ref_type = rt.id_ref_type
WHERE p.oem_part_number = '{{numero_oem}}'
ORDER BY rt.name_ref_type, pcr.cross_reference_number;
Estadísticas generales
SELECT
(SELECT COUNT(*) FROM parts) AS "Total piezas",
(SELECT COUNT(*) FROM vehicle_parts) AS "Total fitments",
(SELECT COUNT(*) FROM aftermarket_parts) AS "Total aftermarket",
(SELECT COUNT(*) FROM part_cross_references) AS "Total cross-refs",
(SELECT COUNT(*) FROM manufacturers) AS "Total fabricantes",
(SELECT COUNT(DISTINCT model_year_engine_id) FROM vehicle_parts) AS "Vehículos con piezas";
9. Flujo Completo: Ejemplo Paso a Paso
Dar de alta "Balata delantera Toyota Camry 2020"
Paso 1: Agregar la pieza OEM
INSERT INTO parts (oem_part_number, name_part, name_es, group_id)
VALUES ('04465-06090', 'Front Brake Pad Set', 'Juego Balatas Delanteras', 16)
RETURNING id_part;
-- Resultado: id_part = 1 (anotar este ID)
Paso 2: Buscar los vehículos compatibles
SELECT mye.id_mye, y.year_car, e.name_engine
FROM model_year_engine mye
JOIN models m ON mye.model_id = m.id_model
JOIN brands b ON m.brand_id = b.id_brand
JOIN years y ON mye.year_id = y.id_year
JOIN engines e ON mye.engine_id = e.id_engine
WHERE b.name_brand = 'TOYOTA' AND m.name_model = 'Camry'
AND y.year_car BETWEEN 2018 AND 2023;
-- Resultado: lista de id_mye para cada configuración
Paso 3: Crear los fitments
INSERT INTO vehicle_parts (model_year_engine_id, part_id, quantity_required, id_position_part)
SELECT mye.id_mye, 1, 1, 1 -- id_part=1, qty=1, position=front
FROM model_year_engine mye
JOIN models m ON mye.model_id = m.id_model
JOIN brands b ON m.brand_id = b.id_brand
JOIN years y ON mye.year_id = y.id_year
WHERE b.name_brand = 'TOYOTA' AND m.name_model = 'Camry'
AND y.year_car BETWEEN 2018 AND 2023
ON CONFLICT DO NOTHING;
Paso 4: Agregar opciones aftermarket
INSERT INTO aftermarket_parts (oem_part_id, manufacturer_id, part_number, name_aftermarket_parts, id_quality_tier, price_usd, warranty_months) VALUES
(1, 3, 'CXD1293', 'Premium Ceramic Pad', 3, 45.99, 24),
(1, 5, 'MKD1293', 'Economy Brake Pad', 1, 22.50, 12);
Paso 5: Agregar cross-references
INSERT INTO part_cross_references (part_id, cross_reference_number, id_ref_type, source_ref) VALUES
(1, 'D1293', 2, 'Wagner'),
(1, 'BC1293', 1, 'Akebono'),
(1, '04465-06100', 3, 'Toyota OEM Alternate');
10. Notas Importantes
Restricciones únicas (evitan duplicados)
parts: No tiene constraint único enoem_part_number(puede haber mismo OEM en diferentes grupos)vehicle_parts: Único por(model_year_engine_id, part_id, id_position_part)vehicle_diagrams: Único por(diagram_id, model_year_engine_id)model_year_engine: Único por(model_id, year_id, engine_id, trim_level)
Búsqueda full-text
La tabla parts tiene un trigger que auto-genera search_vector al insertar/actualizar. Esto permite búsquedas rápidas en español:
SELECT * FROM parts
WHERE search_vector @@ plainto_tsquery('spanish', 'balata delantera');
Para agregar nuevas categorías o grupos
-- Nueva categoría
INSERT INTO part_categories (name_part_category, name_es, display_order)
VALUES ('Interior', 'Interior', 13);
-- Nuevo grupo dentro de una categoría
INSERT INTO part_groups (category_id, name_part_group, name_es, display_order)
VALUES (1, 'Headlights', 'Faros Delanteros', 10);
-- (category_id 1 = Body & Lamp Assembly)
Para agregar un país (para fabricantes)
INSERT INTO countries (name_country) VALUES ('Italy')
ON CONFLICT (name_country) DO NOTHING;