docs: add README, API reference, and architecture documentation
- README.md: project overview, features, quick start, API overview - docs/API.md: full endpoint reference with examples - docs/ARCHITECTURE.md: system diagram, DB schema, data pipeline, auth flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
430
README.md
430
README.md
@@ -1,333 +1,205 @@
|
|||||||
# Nexus Autoparts
|
# Nexus Autoparts
|
||||||
|
|
||||||
Sistema completo de gestión de base de datos de vehículos y nexus-autoparts con dashboard web, herramientas de web scraping y múltiples interfaces de consulta.
|
**Sistema de catalogo de autopartes con navegacion jerarquica, similar a 7zap.com/RockAuto.**
|
||||||
|
|
||||||
## Descripción
|
Plataforma SaaS que conecta talleres con bodegas/distribuidores. Permite buscar partes OEM y aftermarket por vehiculo (marca, modelo, ano, motor), gestionar inventario de bodegas, y consultar disponibilidad y precios en tiempo real.
|
||||||
|
|
||||||
**Nexus Autoparts** es una solución integral para la gestión de información de vehículos que incluye:
|
## Tech Stack
|
||||||
|
|
||||||
- Base de datos SQLite normalizada con información de marcas, modelos, motores y años
|
| Componente | Tecnologia |
|
||||||
- Dashboard web moderno y responsivo para consultar y explorar datos
|
|------------|-----------|
|
||||||
- Herramientas de web scraping para recopilar datos de RockAuto.com
|
| Backend | Python 3, Flask |
|
||||||
- Interfaces de línea de comandos (CLI) y programática
|
| Base de datos | PostgreSQL |
|
||||||
- Scripts de utilidad para gestión y mantenimiento de datos
|
| ORM / SQL | SQLAlchemy (`text()` raw SQL) |
|
||||||
|
| Autenticacion | JWT (PyJWT) + bcrypt |
|
||||||
|
| Data import | TecDoc via Apify, NHTSA VIN API |
|
||||||
|
| Frontend | HTML/CSS/JS vanilla (sin framework) |
|
||||||
|
| Dependencias extra | openpyxl (Excel), csv (CSV import) |
|
||||||
|
|
||||||
## Estadísticas de la Base de Datos
|
## Estadisticas de la Base de Datos
|
||||||
|
|
||||||
| Elemento | Cantidad |
|
- **1.4M+** partes OEM
|
||||||
|----------|----------|
|
- **300K+** partes aftermarket
|
||||||
| Marcas | 12 |
|
- **13M+** cross-references (numeros alternos, supersesiones, intercambios)
|
||||||
| Modelos | 10,923 |
|
- **12B+** vehicle-part links (fitment)
|
||||||
| Motores | 10,919 |
|
- **100+** marcas, miles de modelos, anos 1956-2026
|
||||||
| Combinaciones modelo-año-motor | 12,075 |
|
|
||||||
|
|
||||||
## Tecnologías Utilizadas
|
## Features
|
||||||
|
|
||||||
### Backend
|
- **Catalogo de autopartes** con navegacion jerarquica: Marca > Modelo > Ano > Motor > Categoria > Grupo > Parte
|
||||||
- **Python 3** - Lenguaje principal
|
- **TecDoc integration** (via Apify) para importar datos OEM y aftermarket de Europa/Mexico
|
||||||
- **SQLite 3** - Base de datos
|
- **SaaS multi-tenant** con roles: `ADMIN`, `OWNER`, `TALLER`, `BODEGA`
|
||||||
- **Flask 2.3.3** - Framework web
|
- **JWT authentication** con access tokens (15 min) y refresh tokens (30 dias)
|
||||||
- **BeautifulSoup4** - Web scraping
|
- **Gestion de inventario** para bodegas con mapeo flexible de columnas CSV/Excel
|
||||||
- **requests** - HTTP client
|
- **Disponibilidad de partes** en multiples bodegas con precios comparativos
|
||||||
- **lxml** - Parser XML/HTML
|
- **Alternativas aftermarket** con cross-references por cada parte OEM
|
||||||
|
- **Panel de administracion** con gestion de usuarios, import/export CSV, CRUD de categorias/grupos/partes/fabricantes/fitment
|
||||||
|
- **Busqueda full-text** en el catalogo de partes (PostgreSQL `tsvector`)
|
||||||
|
- **Busqueda combinada** vehiculo + parte (e.g., "Toyota Corolla 2020 frenos")
|
||||||
|
- **VIN decoder** via NHTSA API con cache en base de datos
|
||||||
|
- **Diagramas explosionados** con hotspots clickeables
|
||||||
|
- **Vehicle-to-part linking** (12B+ vehicle_parts links)
|
||||||
|
|
||||||
### Frontend
|
## Quick Start
|
||||||
- **HTML5** - Estructura
|
|
||||||
- **Bootstrap 5.3.0** - Framework CSS
|
|
||||||
- **JavaScript (ES6+)** - Lógica cliente
|
|
||||||
- **Font Awesome 6.0.0** - Iconos
|
|
||||||
|
|
||||||
## Estructura del Proyecto
|
### Requisitos previos
|
||||||
|
|
||||||
```
|
- Python 3.8+
|
||||||
Autopartes/
|
- PostgreSQL con la base `nexus_autoparts`
|
||||||
├── vehicle_database/ # Sistema principal de base de datos
|
|
||||||
│ ├── sql/
|
|
||||||
│ │ └── schema.sql # Esquema de la base de datos
|
|
||||||
│ ├── scripts/
|
|
||||||
│ │ ├── database_manager.py # Gestión de la BD
|
|
||||||
│ │ ├── query_interface.py # Interfaz CLI
|
|
||||||
│ │ └── csv_importer.py # Importador CSV
|
|
||||||
│ ├── data/
|
|
||||||
│ │ ├── brands.csv # Datos de marcas
|
|
||||||
│ │ ├── engines.csv # Datos de motores
|
|
||||||
│ │ └── models.csv # Datos de modelos
|
|
||||||
│ ├── vehicle_database.db # Base de datos SQLite
|
|
||||||
│ └── setup.sh # Script de inicialización
|
|
||||||
│
|
|
||||||
├── dashboard/ # Interfaz web
|
|
||||||
│ ├── server.py # Backend Flask
|
|
||||||
│ ├── index.html # Frontend HTML
|
|
||||||
│ ├── dashboard.js # Lógica JavaScript
|
|
||||||
│ └── start_dashboard.sh # Script de inicio
|
|
||||||
│
|
|
||||||
├── console/ # Consola Pick/VT220
|
|
||||||
│ ├── main.py # Punto de entrada
|
|
||||||
│ ├── db.py # Capa de datos abstracta
|
|
||||||
│ ├── core/ # Framework (app, screens, nav, keys)
|
|
||||||
│ ├── screens/ # 14 pantallas (menú, CRUD, búsqueda)
|
|
||||||
│ ├── renderers/ # Renderer VT220 (curses)
|
|
||||||
│ ├── utils/ # Formato y API VIN
|
|
||||||
│ └── tests/ # 116 tests
|
|
||||||
│
|
|
||||||
├── vehicle_scraper/ # Herramientas de web scraping
|
|
||||||
│ ├── rockauto_scraper.py # Scraper RockAuto
|
|
||||||
│ ├── rockauto_scraper_v2.py # Scraper mejorado
|
|
||||||
│ ├── scrape_toyota.py # Scraper Toyota
|
|
||||||
│ ├── scrape_nissan_ford_chevrolet.py
|
|
||||||
│ └── manual_input.py # Ingreso manual
|
|
||||||
│
|
|
||||||
├── add_*.py # Scripts para agregar datos
|
|
||||||
├── remove_*.py # Scripts de limpieza
|
|
||||||
└── QUICK_START.sh # Guía rápida de inicio
|
|
||||||
```
|
|
||||||
|
|
||||||
## Consola Pick/VT220
|
### Instalacion
|
||||||
|
|
||||||
Interfaz de terminal inspirada en los sistemas Pick/D3, 100% operada con teclado. Estética verde sobre negro con caracteres de caja, sin dependencias externas.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m console
|
cd /home/Autopartes
|
||||||
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Funcionalidades: navegación por vehículo (marca→modelo→año→motor), búsqueda por número de parte, búsqueda full-text, decodificador VIN (NHTSA), catálogo por categorías, comparador OEM vs aftermarket, y administración CRUD completa.
|
### Ejecutar el servidor
|
||||||
|
|
||||||
116 tests automatizados. Ver [`console/README.md`](console/README.md) para documentación completa.
|
|
||||||
|
|
||||||
## Instalación
|
|
||||||
|
|
||||||
### Requisitos Previos
|
|
||||||
|
|
||||||
- Python 3.8 o superior
|
|
||||||
- pip (gestor de paquetes de Python)
|
|
||||||
|
|
||||||
### Pasos de Instalación
|
|
||||||
|
|
||||||
1. **Clonar el repositorio**
|
|
||||||
```bash
|
|
||||||
git clone https://git.consultoria-as.com/[usuario]/Nexus-Autoparts.git
|
|
||||||
cd Nexus-Autoparts
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Instalar dependencias**
|
|
||||||
```bash
|
|
||||||
pip install flask requests beautifulsoup4 lxml
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Inicializar la base de datos (opcional - ya incluye datos)**
|
|
||||||
```bash
|
|
||||||
cd vehicle_database
|
|
||||||
./setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Uso
|
|
||||||
|
|
||||||
### Iniciar el Dashboard Web
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd dashboard
|
cd /home/Autopartes/dashboard
|
||||||
python3 server.py
|
python3 server.py
|
||||||
```
|
```
|
||||||
|
|
||||||
El dashboard estará disponible en: `http://localhost:5000`
|
El servidor arranca en `http://localhost:5000`.
|
||||||
|
|
||||||
### Iniciar la Consola Pick/VT220
|
### Importar datos de TecDoc
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m console
|
# Fase 1: descargar datos de TecDoc a JSON
|
||||||
|
python3 scripts/import_tecdoc.py download
|
||||||
|
|
||||||
|
# Fase 2: importar JSON a PostgreSQL
|
||||||
|
python3 scripts/import_tecdoc.py import
|
||||||
|
|
||||||
|
# Ver progreso
|
||||||
|
python3 scripts/import_tecdoc.py status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usar la Interfaz CLI Legacy
|
### Importar partes y linkar vehiculos
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd vehicle_database/scripts
|
# Importar partes TecDoc (OEM + aftermarket)
|
||||||
python3 query_interface.py
|
python3 scripts/import_tecdoc_parts.py
|
||||||
|
|
||||||
|
# Importar datos en vivo desde TecDoc API
|
||||||
|
python3 scripts/import_live.py
|
||||||
|
|
||||||
|
# Crear links vehiculo-parte (fitment masivo)
|
||||||
|
python3 scripts/link_vehicle_parts.py
|
||||||
|
|
||||||
|
# Migrar datos aftermarket
|
||||||
|
python3 scripts/migrate_aftermarket.py
|
||||||
|
|
||||||
|
# Aplicar schema SaaS (roles, users, inventory tables)
|
||||||
|
python3 scripts/migrate_saas_schema.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ejecutar Web Scraping
|
## Paginas del Dashboard
|
||||||
|
|
||||||
```bash
|
| Ruta | Archivo | Descripcion |
|
||||||
cd vehicle_scraper
|
|------|---------|-------------|
|
||||||
python3 rockauto_scraper_v2.py
|
| `/login.html` | `login.html` | Login con JWT |
|
||||||
```
|
| `/demo.html` | `demo.html` | Catalogo publico / demo |
|
||||||
|
| `/admin` | `admin.html` | Panel de administracion (ADMIN/OWNER) |
|
||||||
|
| `/bodega.html` | `bodega.html` | Gestion de inventario para bodegas |
|
||||||
|
| `/tienda.html` | `tienda.html` | Vista de tienda/catalogo para talleres |
|
||||||
|
| `/pos.html` | `pos.html` | Punto de venta |
|
||||||
|
| `/captura.html` | `captura.html` | Captura de partes |
|
||||||
|
| `/cuentas.html` | `cuentas.html` | Gestion de cuentas |
|
||||||
|
|
||||||
### Agregar Datos Manualmente
|
## API Overview
|
||||||
|
|
||||||
```bash
|
Documentacion completa en [`docs/API.md`](docs/API.md).
|
||||||
cd vehicle_scraper
|
|
||||||
python3 manual_input.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## API REST
|
### Auth (`/api/auth/`)
|
||||||
|
- `POST /api/auth/register` - Registrar usuario (TALLER/BODEGA)
|
||||||
|
- `POST /api/auth/login` - Login, retorna access + refresh tokens
|
||||||
|
- `POST /api/auth/refresh` - Renovar access token
|
||||||
|
- `GET /api/auth/me` - Info del usuario autenticado
|
||||||
|
|
||||||
El dashboard expone los siguientes endpoints:
|
### Catalogo (`/api/`)
|
||||||
|
- `GET /api/brands` - Listar marcas
|
||||||
|
- `GET /api/models?brand=X` - Modelos por marca
|
||||||
|
- `GET /api/years?brand=X&model=Y` - Anos disponibles
|
||||||
|
- `GET /api/engines?brand=X&model=Y&year=Z` - Motores disponibles
|
||||||
|
- `GET /api/categories` - Categorias de partes (arbol jerarquico)
|
||||||
|
- `GET /api/parts?group_id=X` - Partes por grupo
|
||||||
|
- `GET /api/parts/{id}/alternatives` - Alternativas aftermarket
|
||||||
|
- `GET /api/parts/{id}/cross-references` - Cross-references
|
||||||
|
- `GET /api/search?q=...` - Busqueda combinada (vehiculos + partes + aftermarket)
|
||||||
|
|
||||||
| Endpoint | Método | Descripción |
|
### Inventario (`/api/inventory/`)
|
||||||
|----------|--------|-------------|
|
- `GET/PUT /api/inventory/mapping` - Mapeo de columnas CSV
|
||||||
| `/api/brands` | GET | Obtiene todas las marcas |
|
- `POST /api/inventory/upload` - Subir CSV/Excel de inventario
|
||||||
| `/api/models?brand=X` | GET | Obtiene modelos por marca |
|
- `GET /api/inventory/items` - Listar inventario propio
|
||||||
| `/api/years` | GET | Obtiene años disponibles |
|
- `DELETE /api/inventory/items` - Limpiar inventario
|
||||||
| `/api/engines` | GET | Obtiene motores disponibles |
|
|
||||||
| `/api/vehicles` | GET | Búsqueda con filtros |
|
|
||||||
|
|
||||||
### Ejemplo de Uso
|
### Disponibilidad y Aftermarket
|
||||||
|
- `GET /api/parts/{id}/availability` - Bodegas con stock (auth: TALLER/ADMIN/OWNER)
|
||||||
|
- `GET /api/parts/{id}/aftermarket` - Alternativas aftermarket + cross-refs (publico)
|
||||||
|
|
||||||
```bash
|
### Admin (`/api/admin/`)
|
||||||
# Obtener todas las marcas
|
- `GET /api/admin/users` - Listar usuarios (auth: ADMIN/OWNER)
|
||||||
curl http://localhost:5000/api/brands
|
- `PUT /api/admin/users/{id}/activate` - Activar/desactivar usuario
|
||||||
|
- `GET /api/admin/stats` - Estadisticas del catalogo
|
||||||
|
- CRUD completo: categories, groups, parts, manufacturers, aftermarket, crossref, fitment
|
||||||
|
- Import/Export CSV: `POST /api/admin/import/{type}`, `GET /api/admin/export/{type}`
|
||||||
|
|
||||||
# Buscar vehículos por marca y año
|
### VIN Decoder
|
||||||
curl "http://localhost:5000/api/vehicles?brand=Toyota&year=2020"
|
- `GET /api/vin/decode/{vin}` - Decodificar VIN via NHTSA API
|
||||||
```
|
- `GET /api/vin/{vin}/parts` - Partes para un VIN decodificado
|
||||||
|
- `GET /api/vin/{vin}/match?mye_id=X` - Vincular VIN manualmente a vehiculo
|
||||||
|
|
||||||
## Esquema de Base de Datos
|
## Scripts
|
||||||
|
|
||||||
### Tablas
|
| Script | Funcion |
|
||||||
|
|--------|---------|
|
||||||
|
| `import_tecdoc.py` | Descarga datos de TecDoc API (vehiculos, modelos, marcas) a JSON |
|
||||||
|
| `import_tecdoc_parts.py` | Importa partes OEM y aftermarket desde TecDoc |
|
||||||
|
| `import_live.py` | Importacion en vivo desde TecDoc API |
|
||||||
|
| `link_vehicle_parts.py` | Genera links vehiculo-parte (fitment masivo) |
|
||||||
|
| `migrate_aftermarket.py` | Migra datos aftermarket a la estructura normalizada |
|
||||||
|
| `migrate_saas_schema.py` | Crea tablas SaaS: sessions, warehouse_inventory, roles, etc. |
|
||||||
|
| `import_phase1.py` | Importacion inicial fase 1 |
|
||||||
|
| `run_all_brands.sh` | Script auxiliar para importar todas las marcas |
|
||||||
|
|
||||||
#### brands
|
## Configuracion
|
||||||
| Campo | Tipo | Descripción |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| id | INTEGER | Clave primaria |
|
|
||||||
| name | TEXT | Nombre de la marca |
|
|
||||||
| country | TEXT | País de origen |
|
|
||||||
| founded_year | INTEGER | Año de fundación |
|
|
||||||
|
|
||||||
#### models
|
Archivo principal: [`config.py`](config.py)
|
||||||
| Campo | Tipo | Descripción |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| id | INTEGER | Clave primaria |
|
|
||||||
| brand_id | INTEGER | FK a brands |
|
|
||||||
| name | TEXT | Nombre del modelo |
|
|
||||||
| body_type | TEXT | Tipo de carrocería |
|
|
||||||
| generation | TEXT | Generación |
|
|
||||||
| production_start_year | INTEGER | Año inicio producción |
|
|
||||||
| production_end_year | INTEGER | Año fin producción |
|
|
||||||
|
|
||||||
#### engines
|
| Variable | Default | Descripcion |
|
||||||
| Campo | Tipo | Descripción |
|
|----------|---------|-------------|
|
||||||
|-------|------|-------------|
|
| `DATABASE_URL` | `postgresql://nexus:...@localhost/nexus_autoparts` | PostgreSQL connection string |
|
||||||
| id | INTEGER | Clave primaria |
|
| `JWT_SECRET` | `nexus-saas-secret-change-in-prod-2026` | Secreto para firmar tokens JWT |
|
||||||
| name | TEXT | Nombre del motor |
|
| `JWT_ACCESS_EXPIRES` | `900` (15 min) | Duracion del access token en segundos |
|
||||||
| displacement_cc | INTEGER | Cilindrada en cc |
|
| `JWT_REFRESH_EXPIRES` | `2592000` (30 dias) | Duracion del refresh token en segundos |
|
||||||
| cylinders | INTEGER | Número de cilindros |
|
|
||||||
| fuel_type | TEXT | Tipo de combustible |
|
|
||||||
| power_hp | INTEGER | Potencia en HP |
|
|
||||||
| torque_nm | INTEGER | Torque en Nm |
|
|
||||||
| engine_code | TEXT | Código del motor |
|
|
||||||
|
|
||||||
#### years
|
## Arquitectura
|
||||||
| Campo | Tipo | Descripción |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| id | INTEGER | Clave primaria |
|
|
||||||
| year | INTEGER | Año |
|
|
||||||
|
|
||||||
#### model_year_engine
|
Documentacion detallada en [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
|
||||||
| Campo | Tipo | Descripción |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| id | INTEGER | Clave primaria |
|
|
||||||
| model_id | INTEGER | FK a models |
|
|
||||||
| year_id | INTEGER | FK a years |
|
|
||||||
| engine_id | INTEGER | FK a engines |
|
|
||||||
| trim_level | TEXT | Nivel de equipamiento |
|
|
||||||
| drivetrain | TEXT | Tracción |
|
|
||||||
| transmission | TEXT | Transmisión |
|
|
||||||
|
|
||||||
### Diagrama de Relaciones
|
|
||||||
|
|
||||||
```
|
```
|
||||||
brands ──┐
|
+------------------+
|
||||||
│
|
| TecDoc (Apify) |
|
||||||
├──< models ──┐
|
+--------+---------+
|
||||||
│ │
|
|
|
||||||
years ───┼─────────────┼──< model_year_engine
|
download/import
|
||||||
│ │
|
|
|
||||||
engines ─┴─────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Scripts Disponibles
|
|
||||||
|
|
||||||
### Scripts de Datos
|
|
||||||
|
|
||||||
| Script | Descripción |
|
|
||||||
|--------|-------------|
|
|
||||||
| `add_toyota_data.py` | Agrega datos de Toyota |
|
|
||||||
| `add_honda_data.py` | Agrega datos de Honda |
|
|
||||||
| `add_nissan_data.py` | Agrega datos de Nissan |
|
|
||||||
| `add_ford_data.py` | Agrega datos de Ford |
|
|
||||||
| `add_chevrolet_data.py` | Agrega datos de Chevrolet |
|
|
||||||
| `add_audi_data.py` | Agrega datos de Audi |
|
|
||||||
| `add_acura_data.py` | Agrega datos de Acura |
|
|
||||||
| ... | Y más marcas |
|
|
||||||
|
|
||||||
### Scripts de Mantenimiento
|
|
||||||
|
|
||||||
| Script | Descripción |
|
|
||||||
|--------|-------------|
|
|
||||||
| `remove_brands_and_cleanup.py` | Limpia marcas innecesarias |
|
|
||||||
| `check_and_remove_brands.py` | Verifica y elimina marcas |
|
|
||||||
|
|
||||||
## Funcionalidades del Dashboard
|
|
||||||
|
|
||||||
### Panel de Filtros
|
|
||||||
- Selección de marca
|
|
||||||
- Selección de modelo (dinámico según marca)
|
|
||||||
- Filtro por año
|
|
||||||
- Filtro por motor
|
|
||||||
|
|
||||||
### Panel de Resultados
|
|
||||||
- Visualización en tarjetas
|
|
||||||
- Información detallada del vehículo
|
|
||||||
- Especificaciones del motor
|
|
||||||
- Datos de transmisión y tracción
|
|
||||||
|
|
||||||
### Características
|
|
||||||
- Diseño responsivo
|
|
||||||
- Actualización en tiempo real
|
|
||||||
- Animaciones y transiciones suaves
|
|
||||||
- Soporte para múltiples idiomas
|
|
||||||
|
|
||||||
## Arquitectura del Sistema
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┐ ┌──────────────────┐
|
|
||||||
│ RockAuto.com │────>│ Web Scraper │
|
|
||||||
└─────────────────┘ └────────┬─────────┘
|
|
||||||
│
|
|
||||||
v
|
v
|
||||||
┌─────────────────┐ ┌──────────────────┐
|
+----------+ +--------+---------+ +----------------+
|
||||||
│ Manual Input │────>│ SQLite Database │
|
| Frontend |<--->| Flask Server |<--->| PostgreSQL |
|
||||||
└─────────────────┘ └────────┬─────────┘
|
| (HTML/JS)| | (server.py) | | nexus_autoparts|
|
||||||
│
|
+----------+ +--------+---------+ +----------------+
|
||||||
┌───────────────────────┼───────────────────────┐
|
|
|
||||||
│ │ │
|
JWT auth (PyJWT)
|
||||||
v v v
|
|
|
||||||
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
+------------+------------+
|
||||||
│ Flask API │ │ Pick Console │ │ CSV Importer │
|
| | |
|
||||||
└────────┬────────┘ │ (VT220/Rich) │ └──────────────────┘
|
TALLER BODEGA ADMIN
|
||||||
│ └──────────────────┘
|
(consulta) (inventario) (gestion)
|
||||||
v
|
|
||||||
┌─────────────────┐
|
|
||||||
│ Web Dashboard │
|
|
||||||
│ (Browser) │
|
|
||||||
└─────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contribuir
|
|
||||||
|
|
||||||
1. Fork el repositorio
|
|
||||||
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
|
|
||||||
3. Commit tus cambios (`git commit -am 'Agrega nueva funcionalidad'`)
|
|
||||||
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
|
|
||||||
5. Crea un Pull Request
|
|
||||||
|
|
||||||
## Licencia
|
|
||||||
|
|
||||||
Este proyecto es de uso interno.
|
|
||||||
|
|
||||||
## Contacto
|
|
||||||
|
|
||||||
Para más información, contactar al equipo de desarrollo.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Nexus Autoparts** - Sistema de Gestión de Base de Datos de Vehículos
|
**Nexus Autoparts** - Tu conexion directa con las partes que necesitas
|
||||||
|
|||||||
1571
docs/API.md
1571
docs/API.md
File diff suppressed because it is too large
Load Diff
449
docs/ARCHITECTURE.md
Normal file
449
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
# 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 <AT> | |
|
||||||
|
|------------------------------>| 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.
|
||||||
Reference in New Issue
Block a user