# Arquitectura - Nexus Autoparts ## Vista General del Sistema ``` +-------------------+ +-------------------+ +-------------------+ | | | | | | | TecDoc (Apify) | | NHTSA VIN API | | CSV/Excel | | Actor API | | vpic.nhtsa.gov | | (Bodega upload) | | | | | | | +--------+----------+ +--------+----------+ +--------+----------+ | | | | download/import | decode | upload | | | v v v +--------+---------------------------+---------------------------+----------+ | | | Flask Server (server.py) | | Puerto 5000 | | | | +-------------+ +-------------+ +-------------+ +-----------------+ | | | Auth Module | | Catalog API | | Inventory | | Admin CRUD | | | | (auth.py) | | (publico) | | (BODEGA) | | (import/export) | | | | JWT + bcrypt | | | | CSV mapping | | | | | +------+------+ +------+------+ +------+------+ +--------+--------+ | | | | | | | +---------+----------------+----------------+-------------------+-----------+ | | | | v v v v +-------------------------------------------------------------------------+ | | | PostgreSQL (nexus_autoparts) | | SQLAlchemy text() raw SQL | | | | +----------+ +----------+ +----------+ +----------+ +---------------+ | | | brands | | models | | years | | engines | | fuel_type | | | +----+-----+ +----+-----+ +----+-----+ +----+-----+ | drivetrain | | | | | | | | transmission | | | +------+------+------------+------------+ +---------------+ | | | | | v | | +-----------------------+ | | | model_year_engine | (MYE - tabla central de vehiculos) | | | PK: id_mye | | | | UNIQUE(model,year, | | | | engine,trim_level) | | | +----------+------------+ | | | | | | 1:N | | v | | +---------------------+ +---------------------+ | | | vehicle_parts | | vehicle_diagrams | | | | (12B+ rows) | | | | | | mye_id + part_id | | mye_id + diagram_id | | | +----------+----------+ +----------+----------+ | | | | | | v v | | +---------------------+ +---------------------+ | | | parts (1.4M+ OEM) | | diagrams | | | | oem_part_number | | image_path | | | | group_id -> groups | | group_id -> groups | | | +---+------+----------+ +-----+---------------+ | | | | | | | | v v | | | +--------------------+ +---------------------+ | | | | part_groups | | diagram_hotspots | | | | | category_id | | part_id, coords | | | | +--------+-----------+ +---------------------+ | | | | | | | v | | | +--------------------+ | | | | part_categories | | | | | (arbol jerarquico) | | | | | parent_id -> self | | | | +--------------------+ | | | | | +----------+-----------+ | | | | | | v v | | +---------------------+ +------------------------+ | | | aftermarket_parts | | part_cross_references | | | | (300K+) | | (13M+) | | | | oem_part_id -> parts| | part_id -> parts | | | | manufacturer_id | | reference_type | | | +----------+----------+ +------------------------+ | | | | | v | | +---------------------+ | | | manufacturers | | | | type, quality_tier | | | +---------------------+ | | | | === SaaS Tables === | | | | +----------+ +----------+ +---------------------+ +-------------+ | | | users | | roles | | warehouse_inventory | | sessions | | | | id_user | | ADMIN | | user_id + part_id | | refresh | | | | id_rol | | OWNER | | price, stock | | token | | | | email | | TALLER | | location | | expires_at | | | | pass | | BODEGA | +---------------------+ +-------------+ | | +----------+ +----------+ | | +------------------------+ | | | inventory_uploads | | | | inventory_col_mappings | | | +------------------------+ | | | | === Lookup Tables === | | countries, materials, shapes, position_part, reference_type, | | manufacture_type, quality_tier | | | +-------------------------------------------------------------------------+ | | | | v v v v +-------------------+ +----------+ +-------------+ +----------------+ | login.html | | demo.html| | admin.html | | bodega.html | | tienda.html | | (catalog)| | (CRUD + | | (inventory | | pos.html | | | | users) | | upload/manage) | | cuentas.html | | | | | | | | captura.html | | | | | | | +-------------------+ +----------+ +-------------+ +----------------+ Frontend: HTML/CSS/JS vanilla (sin framework) Shared: nav.js, shared.css ``` --- ## Database Schema Overview ### Tablas principales y sus relaciones **Vehiculos (jerarquia):** ``` brands (id_brand, name_brand, country, region) | +--< models (id_model, brand_id, name_model, body_type) | +--< model_year_engine (id_mye, model_id, year_id, engine_id, trim_level) | | | | | +-- engines (id_engine, name_engine, displacement_cc, cylinders, power_hp) | +-------- years (id_year, year_car) | +--< vehicle_parts (mye_id, part_id, quantity, position) +--< vehicle_diagrams (mye_id, diagram_id) ``` **Partes (jerarquia):** ``` part_categories (id, name, parent_id) <-- arbol recursivo | +--< part_groups (id, category_id, name) | +--< parts (id_part, oem_part_number, name, group_id) | +--< aftermarket_parts (oem_part_id -> parts, manufacturer_id, part_number) +--< part_cross_references (part_id -> parts, cross_reference_number, type) +--< vehicle_parts (part_id -> parts, mye_id) ``` **SaaS / Multi-tenant:** ``` roles (id_rol: 1=ADMIN, 2=OWNER, 3=TALLER, 4=BODEGA) | +--< users (id_user, email, pass, id_rol, business_name, is_active) | +--< sessions (refresh_token, expires_at) +--< warehouse_inventory (user_id, part_id, price, stock, location) +--< inventory_uploads (user_id, filename, status, rows_imported) +--< inventory_column_mappings (user_id, mapping JSON) ``` ### Convenciones de nombres en PostgreSQL | Tabla | PK | Naming pattern | |-------|-----|---------------| | brands | `id_brand` | `name_brand` | | models | `id_model` | `name_model` | | years | `id_year` | `year_car` | | engines | `id_engine` | `name_engine` | | model_year_engine | `id_mye` | Tabla pivote central | | parts | `id_part` | `name_part`, `oem_part_number` | | part_categories | `id_part_category` | `name_part_category` | | part_groups | `id_part_group` | `name_part_group` | | manufacturers | `id_manufacture` | `name_manufacture` | --- ## TecDoc Data Pipeline Pipeline de 3 fases para importar datos de TecDoc (el catalogo europeo de autopartes). ``` Fase 1: DOWNLOAD Fase 2: IMPORT Fase 3: LINK (import_tecdoc.py download) (import_tecdoc.py import) (link_vehicle_parts.py) +------------------+ +------------------+ +------------------+ | Apify TecDoc | | JSON files | | parts + | | Actor API | | data/tecdoc/ | | model_year_engine| | $69/month | | | | | | HTTP 201 = run | | manufacturers/ | | Genera links | | | --------> | models/ | ---------> | masivos en | | typeId=1 (cars) | JSON | vehicles/ | INSERT | vehicle_parts | | langId=4 (EN) | files | | partes + | (12B+ filas) | | countryId=153(MX)| | Resumable: | vehiculos | | +------------------+ | skips existing | +------------------+ +------------------+ ``` ### Scripts adicionales - **`import_tecdoc_parts.py`**: Importa partes OEM y aftermarket desde TecDoc - **`import_live.py`**: Importa datos en tiempo real (sin bajar a JSON primero) - **`migrate_aftermarket.py`**: Normaliza datos aftermarket a la estructura con `quality_tier`, `manufacturers` ### Filtros de importacion - Se descartan variantes regionales (e.g., "TOYOTA (GAC)") - `countryFilterId=153` limita a vehiculos vendidos en Mexico - El script es resumable: si un JSON ya existe en `data/tecdoc/`, se salta --- ## Auth Flow Flujo de autenticacion basado en JWT con refresh tokens. ``` Cliente Servidor PostgreSQL | | | | POST /api/auth/register | | | {name, email, pass, role} | | |------------------------------>| bcrypt.hash(pass) | | |----------------------------->| | | INSERT users (is_active=F) | | 201 "pending activation" | | |<------------------------------| | | | | | (Admin activa la cuenta) | | | | | | POST /api/auth/login | | | {email, password} | | |------------------------------>| SELECT users WHERE email | | |<-----------------------------| | | bcrypt.check(pass) | | | Verify is_active=true | | | | | | jwt.encode(user_id, role, | | | business_name, exp=15min) | | | | | | secrets.token_urlsafe(48) | | | INSERT sessions | | |----------------------------->| | | | | {access_token, refresh_token,| | | user: {id, name, role}} | | |<------------------------------| | | | | | GET /api/... (protected) | | | Authorization: Bearer | | |------------------------------>| jwt.decode(AT) | | | Check role in allowed_roles | | | Set g.user = {user_id, role}| | 200 {data} | | |<------------------------------| | | | | | (Access token expires) | | | | | | POST /api/auth/refresh | | | {refresh_token} | | |------------------------------>| SELECT sessions | | |<-----------------------------| | | Check expires_at > now() | | | jwt.encode(new access_token)| | {access_token} | | |<------------------------------| | ``` ### Roles y permisos | Rol | ID | Permisos | |-----|----|----------| | `ADMIN` | 1 | Todo: gestionar usuarios, catalogo, inventario | | `OWNER` | 2 | Gestionar usuarios, ver estadisticas | | `TALLER` | 3 | Consultar catalogo, ver disponibilidad en bodegas | | `BODEGA` | 4 | Gestionar inventario propio, subir CSV/Excel | ### JWT Token payload ```json { "user_id": 1, "role": "TALLER", "business_name": "Taller Perez", "type": "access", "exp": 1742300000, "iat": 1742299100 } ``` --- ## Inventory Flow (Bodega) Flujo para que una bodega suba su inventario via CSV/Excel. ``` Bodega (Browser) Servidor PostgreSQL | | | | 1. PUT /api/inventory/mapping| | | {part_number: "NUM_PARTE", | | | price: "PRECIO", | | | stock: "EXISTENCIA", | UPSERT inventory_col_map | | location: "ALMACEN"} |----------------------------->| |<------------------------------| | | | | | 2. POST /api/inventory/upload| | | multipart/form-data: file | | |------------------------------>| | | | a) Read mapping | | |<-----------------------------| | | | | | b) Parse CSV/Excel | | | (openpyxl or csv module) | | | | | | c) For each row: | | | - Extract part_number | | | using mapping | | | - Find part by OEM # | | | - If not found, try | | | aftermarket_parts | | | - UPSERT warehouse_inv | | |----------------------------->| | | | | {imported: 450, errors: 12} | d) Update inventory_uploads | |<------------------------------|----------------------------->| ``` ### Tabla `warehouse_inventory` ```sql warehouse_inventory ( id_inventory BIGSERIAL PRIMARY KEY, user_id INTEGER REFERENCES users, part_id INTEGER REFERENCES parts, price NUMERIC(12,2), stock_quantity INTEGER DEFAULT 0, warehouse_location VARCHAR(100) DEFAULT 'Principal', updated_at TIMESTAMP, UNIQUE(user_id, part_id, warehouse_location) ) ``` El UNIQUE constraint permite que una bodega tenga la misma parte en multiples ubicaciones (e.g., "Principal", "Sucursal Norte"). --- ## Aftermarket Linking Como se relacionan partes OEM con alternativas aftermarket y cross-references. ``` +--------------------+ | parts | | (OEM catalog) | | id_part: 500 | | oem_part_number: | | "04152-YZZA1" | +--------+-----------+ | +--------------+--------------+ | | v v +----------------------------+ +----------------------------+ | aftermarket_parts | | part_cross_references | | (alternativas) | | (numeros alternos) | | | | | | oem_part_id: 500 | | part_id: 500 | | manufacturer_id: -> DENSO | | cross_reference_number: | | part_number: "DXP-1234" | | "90915-YZZF1" | | quality_tier: "premium" | | reference_type: | | price_usd: 12.50 | | "oem_alternate" | | warranty_months: 24 | | source: "TecDoc" | +----------------------------+ +----------------------------+ ``` ### Tipos de cross-reference | Tipo | Descripcion | |------|-------------| | `oem_alternate` | Otro numero OEM para la misma parte (mismo fabricante) | | `supersession` | La parte fue reemplazada por un numero nuevo | | `interchange` | Numero de un competidor que es equivalente | | `competitor` | Referencia cruzada a otra marca | ### Flujo de busqueda por numero de parte Cuando un taller busca un numero de parte, el sistema busca en 3 lugares: 1. **`parts.oem_part_number`** - Match directo OEM 2. **`aftermarket_parts.part_number`** - Match en parte aftermarket, retorna el OEM original 3. **`part_cross_references.cross_reference_number`** - Match en cross-ref, retorna el OEM original Esto permite que el taller encuentre la parte sin importar que numero use (OEM, aftermarket, o numero alterno). ### Calidad (quality_tier) | Tier | Descripcion | Ejemplo | |------|-------------|---------| | `economy` | Precio bajo, calidad basica | Marcas genericas | | `standard` | Calidad media, buen balance | Monroe, Moog | | `premium` | Calidad alta, cercana a OEM | Denso, Bosch | --- ## Stack Frontend El frontend es HTML/CSS/JS vanilla sin framework. Cada pagina es independiente. ``` dashboard/ +-- shared.css # Estilos compartidos (colores, layout, cards) +-- nav.js # Navegacion compartida (inyecta sidebar/header) +-- login.html + .css + .js +-- demo.html # Catalogo publico +-- admin.html + .js # Panel admin (CRUD, users, import/export) +-- bodega.html + .css + .js # Inventory management +-- tienda.html + .css + .js # Store view para talleres +-- pos.html + .css + .js # Point of sale +-- captura.html + .css + .js # Captura de partes +-- cuentas.html + .css + .js # Gestion de cuentas +-- dashboard.js # Logica del catalogo principal +-- enhanced-search.js # Busqueda avanzada ``` ### Patron de comunicacion con API Todas las paginas usan `fetch()` con el patron: ```javascript const token = localStorage.getItem('access_token'); const res = await fetch('/api/...', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); ``` ### Nota sobre NUMERIC de PostgreSQL PostgreSQL retorna valores `NUMERIC` como strings. Todas las funciones de formato en JS usan `parseFloat()` para convertir antes de mostrar.