# 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

1. Ir a **Admin** → **Databases** → **Add database**
2. Seleccionar **PostgreSQL**
3. Llenar los campos con los datos anteriores
4. Click **Save**
5. Metabase sincronizará las tablas automáticamente (~30 segundos)

> **Tip:** Si Metabase está en otro servidor, usar la IP `192.168.10.198` en lugar de `localhost`.

---

## 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_parts` y `aftermarket_parts` requieren que la pieza ya exista en `parts`.

---

### 3.1 Agregar una pieza OEM (`parts`)

En Metabase: click **+ New** → **SQL query** → ejecutar:

```sql
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

```sql
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

```sql
-- 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

```sql
-- 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`)

```sql
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)

```sql
-- 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):
> ```sql
> 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`)

```sql
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

```sql
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

```sql
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

```sql
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`)

```sql
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

```sql
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:

```sql
-- 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

```sql
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

```sql
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

```sql
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

```sql
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

```sql
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
```sql
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
```sql
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
```sql
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
```sql
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
```sql
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 en `oem_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:
```sql
SELECT * FROM parts
WHERE search_vector @@ plainto_tsquery('spanish', 'balata delantera');
```

### Para agregar nuevas categorías o grupos
```sql
-- 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)
```sql
INSERT INTO countries (name_country) VALUES ('Italy')
ON CONFLICT (name_country) DO NOTHING;
```
