fix(catalog): unifica modelos duplicados por variante de carroceria/generacion

- catalog_service.get_models ahora agrupa variantes (p. ej. AVEO Saloon,
  AVEO Hatchback) bajo un unico display_name y devuelve variant_ids.
- Se elige el id_model mas bajo como canonico para presentacion.
- /catalog/years y /catalog/engines aceptan model_id como lista separada
  por comas para consultar todos los MYEs de las variantes agrupadas.
- catalog.js usa variant_ids al cargar años/motores y en el selector
  desplegable (incluyendo carga desde VIN).
This commit is contained in:
2026-06-15 18:24:58 +00:00
parent 85ecf52561
commit f5711ae22f
3 changed files with 87 additions and 37 deletions

View File

@@ -427,6 +427,12 @@
});
}
function modelIdsParam(model) {
if (!model) return '';
if (model.variant_ids && model.variant_ids.length) return model.variant_ids.join(',');
return String(model.id);
}
function loadModels() {
nav.level = 'models';
pushNavState();
@@ -440,14 +446,15 @@
if (!data || !data.data || !data.data.length) { showEmpty('Sin modelos', 'No hay modelos con partes para ' + nav.brand.name); return; }
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (m) {
return '<div class="nav-card" role="listitem" data-model-id="' + m.id_model + '" data-name="' + esc(m.display_name || m.name_model) + '">' +
return '<div class="nav-card" role="listitem" data-model-id="' + m.id_model + '" data-variant-ids="' + esc((m.variant_ids || [m.id_model]).join(',')) + '" data-name="' + esc(m.display_name || m.name_model) + '">' +
'<div class="nav-card__name">' + esc(m.display_name || m.name_model) + '</div>' +
'</div>';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.model = { id: parseInt(this.dataset.modelId), name: this.dataset.name };
var variantIds = (this.dataset.variantIds || this.dataset.modelId).split(',').map(function(x){ return parseInt(x); });
nav.model = { id: parseInt(this.dataset.modelId), name: this.dataset.name, variant_ids: variantIds };
loadYears();
});
});
@@ -462,7 +469,7 @@
setupLevelFilter(false);
showLoading();
apiFetch(API + '/years?model_id=' + nav.model.id).then(function (data) {
apiFetch(API + '/years?model_id=' + modelIdsParam(nav.model)).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin anios', 'No hay anios con partes para este modelo.'); return; }
navGrid.className = 'nav-grid nav-grid--years';
@@ -489,7 +496,7 @@
setupLevelFilter(false);
showLoading();
apiFetch(API + '/engines?model_id=' + nav.model.id + '&year_id=' + nav.year.id).then(function (data) {
apiFetch(API + '/engines?model_id=' + modelIdsParam(nav.model) + '&year_id=' + nav.year.id).then(function (data) {
hideLoading();
if (!data || !data.data || !data.data.length) { showEmpty('Sin motores', 'No hay configuraciones de motor para esta combinacion.'); return; }
@@ -1829,7 +1836,8 @@
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>' +
models.map(function (m) {
return '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
var variants = (m.variant_ids || [m.id_model]).join(',');
return '<option value="' + m.id_model + '" data-variant-ids="' + esc(variants) + '">' + esc(m.display_name || m.name_model) + '</option>';
}).join('');
});
}
@@ -1837,13 +1845,17 @@
function vsModelChanged() {
var modelId = vsModel.value;
var yearVal = vsYear.value;
var selectedOption = vsModel.options[vsModel.selectedIndex];
var variantIds = selectedOption && selectedOption.dataset.variantIds
? selectedOption.dataset.variantIds.split(',').map(function(x){ return parseInt(x); })
: (modelId ? [parseInt(modelId)] : []);
vsEngine.innerHTML = '<option value="">Motor...</option>';
vsEngine.disabled = true;
if (!modelId || !yearVal) return;
if (!modelId || !yearVal || !variantIds.length) return;
vsEngine.disabled = false;
apiFetch(API + '/engines?model_id=' + modelId + '&year_id=' + yearVal).then(function (data) {
apiFetch(API + '/engines?model_id=' + variantIds.join(',') + '&year_id=' + yearVal).then(function (data) {
var engines = data.data || data;
if (!engines) return;
vsEngine.innerHTML = '<option value="">Motor...</option>' +
@@ -1869,8 +1881,12 @@
var modelText = vsModel.options[vsModel.selectedIndex].text;
var engineText = vsEngine.options[vsEngine.selectedIndex].text;
var selectedModelOption = vsModel.options[vsModel.selectedIndex];
var modelVariantIds = selectedModelOption && selectedModelOption.dataset.variantIds
? selectedModelOption.dataset.variantIds.split(',').map(function(x){ return parseInt(x); })
: [parseInt(vsModel.value)];
nav.brand = { id: parseInt(vsBrand.value), name: brandText };
nav.model = { id: parseInt(vsModel.value), name: modelText };
nav.model = { id: parseInt(vsModel.value), name: modelText, variant_ids: modelVariantIds };
nav.year = { id: parseInt(vsYear.value), year: yearText };
nav.engine = { id_mye: parseInt(myeId), name: engineText };
nav.level = 'categories';
@@ -2058,14 +2074,29 @@
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>' +
models.map(function (m) {
return '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
var variants = (m.variant_ids || [m.id_model]).join(',');
return '<option value="' + m.id_model + '" data-variant-ids="' + esc(variants) + '">' + esc(m.display_name || m.name_model) + '</option>';
}).join('');
vsModel.disabled = false;
if (match.model_id) {
vsModel.value = String(match.model_id);
// The VIN match may point to a variant that is now grouped under
// a canonical model; select the option whose variants include it.
var matchedOption = Array.from(vsModel.options).find(function (opt) {
if (!opt.value) return false;
var vids = (opt.dataset.variantIds || opt.value).split(',').map(function(x){ return parseInt(x); });
return vids.indexOf(match.model_id) !== -1;
});
if (matchedOption) {
vsModel.value = matchedOption.value;
} else {
vsModel.value = String(match.model_id);
}
var selectedVariantIds = vsModel.selectedIndex >= 0 && vsModel.options[vsModel.selectedIndex].dataset.variantIds
? vsModel.options[vsModel.selectedIndex].dataset.variantIds.split(',').map(function(x){ return parseInt(x); })
: [match.model_id];
// Load engines
apiFetch(API + '/engines?model_id=' + match.model_id + '&year_id=' + match.year_id).then(function (engData) {
apiFetch(API + '/engines?model_id=' + selectedVariantIds.join(',') + '&year_id=' + match.year_id).then(function (engData) {
var engines = engData && (engData.data || engData);
if (!engines) return;
vsEngine.innerHTML = '<option value="">Motor...</option>' +