Files
Autoparts-DB/dashboard/dashboard.js
consultoria-as f395d67136 Initial commit: Sistema Autoparts DB
- Base de datos SQLite con información de vehículos
- Dashboard web con Flask y Bootstrap
- Scripts de web scraping para RockAuto
- Interfaz CLI para consultas
- Documentación completa del proyecto

Incluye:
- 12 marcas de vehículos
- 10,923 modelos
- 10,919 especificaciones de motores
- 12,075 combinaciones modelo-año-motor
2026-01-19 08:45:03 +00:00

441 lines
16 KiB
JavaScript

// Vehicle Dashboard JavaScript - Navegación por tarjetas
class VehicleDashboard {
constructor() {
this.currentView = 'brands'; // brands, models, vehicles
this.selectedBrand = null;
this.selectedModel = null;
this.allVehicles = [];
this.filteredVehicles = [];
this.stats = { brands: 0, models: 0, vehicles: 0 };
this.init();
}
async init() {
await this.loadStats();
await this.showBrands();
this.bindFilterEvents();
}
async loadStats() {
try {
const [brandsRes, vehiclesRes] = await Promise.all([
fetch('/api/brands'),
fetch('/api/vehicles')
]);
if (brandsRes.ok && vehiclesRes.ok) {
const brands = await brandsRes.json();
const vehicles = await vehiclesRes.json();
// Contar modelos únicos
const uniqueModels = new Set(vehicles.map(v => `${v.brand}-${v.model}`));
this.stats.brands = brands.length;
this.stats.models = uniqueModels.size;
this.stats.vehicles = vehicles.length;
document.getElementById('totalBrands').textContent = this.stats.brands;
document.getElementById('totalModels').textContent = this.stats.models;
document.getElementById('totalVehicles').textContent = this.stats.vehicles;
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
updateBreadcrumb() {
const breadcrumb = document.getElementById('breadcrumb');
let html = '';
if (this.currentView === 'brands') {
html = `<li class="breadcrumb-item active"><i class="fas fa-home"></i> Marcas</li>`;
} else if (this.currentView === 'models') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item active">${this.selectedBrand}</li>
`;
} else if (this.currentView === 'vehicles') {
html = `
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToBrands(); return false;">
<i class="fas fa-home"></i> Marcas
</a>
</li>
<li class="breadcrumb-item">
<a href="#" onclick="dashboard.goToModels('${this.selectedBrand}'); return false;">
${this.selectedBrand}
</a>
</li>
<li class="breadcrumb-item active">${this.selectedModel}</li>
`;
}
breadcrumb.innerHTML = html;
}
async showBrands() {
this.currentView = 'brands';
this.selectedBrand = null;
this.selectedModel = null;
this.updateBreadcrumb();
this.hideFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando marcas...</h4>
</div>
`;
try {
const [brandsRes, vehiclesRes] = await Promise.all([
fetch('/api/brands'),
fetch('/api/vehicles')
]);
if (!brandsRes.ok || !vehiclesRes.ok) {
throw new Error('Error al cargar datos');
}
const brands = await brandsRes.json();
const vehicles = await vehiclesRes.json();
// Contar modelos y vehículos por marca
const brandStats = {};
brands.forEach(brand => {
brandStats[brand] = { models: new Set(), vehicles: 0 };
});
vehicles.forEach(v => {
if (brandStats[v.brand]) {
brandStats[v.brand].models.add(v.model);
brandStats[v.brand].vehicles++;
}
});
if (brands.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car"></i>
<h4>No hay marcas disponibles</h4>
<p>Agrega algunas marcas a la base de datos</p>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid brands-grid">
${brands.map(brand => `
<div class="brand-card" onclick="dashboard.goToModels('${brand}')">
<div class="brand-icon">
<i class="fas fa-car"></i>
</div>
<div class="brand-name">${brand}</div>
<div class="brand-count">
${brandStats[brand].models.size} modelos
</div>
<div class="brand-count">
${brandStats[brand].vehicles} vehículos
</div>
</div>
`).join('')}
</div>`;
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar marcas</h4>
<p>${error.message}</p>
</div>
`;
}
}
async goToModels(brand) {
this.currentView = 'models';
this.selectedBrand = brand;
this.selectedModel = null;
this.updateBreadcrumb();
this.hideFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando modelos de ${brand}...</h4>
</div>
`;
try {
const [modelsRes, vehiclesRes] = await Promise.all([
fetch(`/api/models?brand=${encodeURIComponent(brand)}`),
fetch(`/api/vehicles?brand=${encodeURIComponent(brand)}`)
]);
if (!modelsRes.ok || !vehiclesRes.ok) {
throw new Error('Error al cargar datos');
}
const models = await modelsRes.json();
const vehicles = await vehiclesRes.json();
// Contar vehículos y años por modelo
const modelStats = {};
models.forEach(model => {
modelStats[model] = { years: new Set(), vehicles: 0, engines: new Set() };
});
vehicles.forEach(v => {
if (modelStats[v.model]) {
modelStats[v.model].years.add(v.year);
modelStats[v.model].vehicles++;
modelStats[v.model].engines.add(v.engine);
}
});
if (models.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car-side"></i>
<h4>No hay modelos para ${brand}</h4>
<p>Esta marca no tiene modelos registrados</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToBrands()">
<i class="fas fa-arrow-left"></i> Volver a marcas
</button>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid models-grid">
${models.map(model => {
const stats = modelStats[model];
const yearsArray = Array.from(stats.years).sort((a, b) => b - a);
const yearRange = yearsArray.length > 0
? (yearsArray.length > 1
? `${yearsArray[yearsArray.length - 1]} - ${yearsArray[0]}`
: `${yearsArray[0]}`)
: 'N/A';
return `
<div class="model-card" onclick="dashboard.goToVehicles('${brand}', '${model}')">
<div class="model-name">${model}</div>
<div class="model-info">
<i class="fas fa-calendar-alt"></i> ${yearRange}
</div>
<div class="model-info">
<i class="fas fa-cogs"></i> ${stats.engines.size} motores
</div>
<div class="model-info">
<i class="fas fa-list"></i> ${stats.vehicles} variantes
</div>
</div>
`;
}).join('')}
</div>`;
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar modelos</h4>
<p>${error.message}</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToBrands()">
<i class="fas fa-arrow-left"></i> Volver a marcas
</button>
</div>
`;
}
}
async goToVehicles(brand, model) {
this.currentView = 'vehicles';
this.selectedBrand = brand;
this.selectedModel = model;
this.updateBreadcrumb();
this.showFilters();
const container = document.getElementById('mainContent');
container.innerHTML = `
<div class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<h4>Cargando vehículos...</h4>
</div>
`;
try {
const response = await fetch(
`/api/vehicles?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`
);
if (!response.ok) {
throw new Error('Error al cargar vehículos');
}
this.allVehicles = await response.json();
this.filteredVehicles = [...this.allVehicles];
// Poblar filtros
await this.populateFilters(brand, model);
this.displayVehicles();
} catch (error) {
console.error('Error:', error);
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-triangle"></i>
<h4>Error al cargar vehículos</h4>
<p>${error.message}</p>
<button class="btn btn-back mt-3" onclick="dashboard.goToModels('${brand}')">
<i class="fas fa-arrow-left"></i> Volver a modelos
</button>
</div>
`;
}
}
async populateFilters(brand, model) {
try {
const [yearsRes, enginesRes] = await Promise.all([
fetch(`/api/years?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`),
fetch(`/api/engines?brand=${encodeURIComponent(brand)}&model=${encodeURIComponent(model)}`)
]);
if (yearsRes.ok) {
const years = await yearsRes.json();
const yearFilter = document.getElementById('yearFilter');
yearFilter.innerHTML = '<option value="">Todos los años</option>';
years.forEach(year => {
yearFilter.innerHTML += `<option value="${year}">${year}</option>`;
});
}
if (enginesRes.ok) {
const engines = await enginesRes.json();
const engineFilter = document.getElementById('engineFilter');
engineFilter.innerHTML = '<option value="">Todos los motores</option>';
engines.forEach(engine => {
engineFilter.innerHTML += `<option value="${engine}">${engine}</option>`;
});
}
} catch (error) {
console.error('Error populating filters:', error);
}
}
bindFilterEvents() {
document.getElementById('yearFilter').addEventListener('change', () => {
this.applyFilters();
});
document.getElementById('engineFilter').addEventListener('change', () => {
this.applyFilters();
});
}
applyFilters() {
const year = document.getElementById('yearFilter').value;
const engine = document.getElementById('engineFilter').value;
this.filteredVehicles = this.allVehicles.filter(v => {
return (!year || v.year.toString() === year) &&
(!engine || v.engine === engine);
});
this.displayVehicles();
}
displayVehicles() {
const container = document.getElementById('mainContent');
const resultCount = document.getElementById('resultCount');
resultCount.textContent = `${this.filteredVehicles.length} resultado${this.filteredVehicles.length !== 1 ? 's' : ''}`;
if (this.filteredVehicles.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-car"></i>
<h4>No se encontraron vehículos</h4>
<p>Intenta ajustar los filtros</p>
</div>
`;
return;
}
container.innerHTML = `<div class="content-grid vehicles-grid">
${this.filteredVehicles.map(v => `
<div class="vehicle-card">
<div class="vehicle-header">
<div class="vehicle-title">${v.year} ${v.brand} ${v.model}</div>
<div class="vehicle-engine">${v.engine}</div>
</div>
<div class="vehicle-body">
<div class="vehicle-specs">
<div class="spec-item">
<i class="fas fa-gas-pump"></i>
<div class="spec-value">${v.fuel_type || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-tachometer-alt"></i>
<div class="spec-value">${v.power_hp || 0} HP</div>
</div>
<div class="spec-item">
<i class="fas fa-cogs"></i>
<div class="spec-value">${v.transmission || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-road"></i>
<div class="spec-value">${v.drivetrain || 'N/A'}</div>
</div>
<div class="spec-item">
<i class="fas fa-cube"></i>
<div class="spec-value">${v.cylinders || 0} cil.</div>
</div>
<div class="spec-item">
<i class="fas fa-oil-can"></i>
<div class="spec-value">${v.displacement_cc || 0} cc</div>
</div>
</div>
${v.trim_level && v.trim_level !== 'unknown' ? `
<div class="mt-2 text-center">
<span class="badge bg-primary">${v.trim_level}</span>
</div>
` : ''}
</div>
</div>
`).join('')}
</div>`;
}
goToBrands() {
this.showBrands();
}
showFilters() {
document.getElementById('filtersBar').style.display = 'block';
// Reset filters
document.getElementById('yearFilter').value = '';
document.getElementById('engineFilter').value = '';
}
hideFilters() {
document.getElementById('filtersBar').style.display = 'none';
}
}
// Initialize dashboard globally
let dashboard;
document.addEventListener('DOMContentLoaded', () => {
dashboard = new VehicleDashboard();
});