feat: MercadoLibre integration + inventory bulk publish + WhatsApp bridge fixes
- Add MercadoLibre OAuth, listings, orders, webhooks and category search - New marketplace_external_bp.py, meli_service.py, marketplace_external_service.py - New marketplace_external.html/js with ML management UI - Inventory: bulk publish to ML with category autocomplete, listing type and shipping selectors - Inventory: new .btn--meli styles, select/label CSS fixes - WhatsApp bridge: rate limiting, 440/515/408 error handling, stale watchdog - DB migration v3.4_meli_integration.sql for marketplace_listings, orders, sync_queue - Add Celery tasks for ML sync and webhook processing - Sidebar: MercadoLibre navigation link
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
_offset: 0,
|
||||
_limit: 50,
|
||||
_total: 0,
|
||||
_allowedBrands: [],
|
||||
|
||||
// Navigation state
|
||||
nav: {
|
||||
@@ -71,7 +72,9 @@
|
||||
},
|
||||
|
||||
loading: function(on) {
|
||||
this.el('brandCatalogLoading').style.display = on ? 'block' : 'none';
|
||||
var el = this.el('brandCatalogLoading');
|
||||
if (on) el.classList.add('is-visible');
|
||||
else el.classList.remove('is-visible');
|
||||
},
|
||||
|
||||
setContent: function(html) {
|
||||
@@ -88,23 +91,23 @@
|
||||
|
||||
buildBreadcrumb: function() {
|
||||
var parts = [];
|
||||
parts.push('<a href="javascript:void(0)" onclick="BrandCatalog.loadBrands()" style="color:var(--color-primary);text-decoration:none;">Marcas</a>');
|
||||
parts.push('<a href="javascript:void(0)" class="breadcrumb__link" onclick="BrandCatalog.loadBrands()">Marcas</a>');
|
||||
if (this.nav.brand) {
|
||||
parts.push('<a href="javascript:void(0)" onclick="BrandCatalog.selectBrand(' + JSON.stringify(this.nav.brand) + ',' + this.nav.brandId + ')" style="color:var(--color-primary);text-decoration:none;">' + escapeHtml(this.nav.brand) + '</a>');
|
||||
parts.push('<a href="javascript:void(0)" class="breadcrumb__link" onclick=\'BrandCatalog.selectBrand(' + JSON.stringify(this.nav.brand) + ',' + this.nav.brandId + ')\'>' + escapeHtml(this.nav.brand) + '</a>');
|
||||
}
|
||||
if (this.nav.model) {
|
||||
parts.push('<a href="javascript:void(0)" onclick="BrandCatalog.selectModel(' + this.nav.modelId + ',' + JSON.stringify(this.nav.model) + ')" style="color:var(--color-primary);text-decoration:none;">' + escapeHtml(this.nav.model) + '</a>');
|
||||
parts.push('<a href="javascript:void(0)" class="breadcrumb__link" onclick=\'BrandCatalog.selectModel(' + this.nav.modelId + ',' + JSON.stringify(this.nav.model) + ')\'>' + escapeHtml(this.nav.model) + '</a>');
|
||||
}
|
||||
if (this.nav.year) {
|
||||
parts.push('<a href="javascript:void(0)" onclick="BrandCatalog.selectYear(' + this.nav.yearId + ',' + this.nav.year + ')" style="color:var(--color-primary);text-decoration:none;">' + this.nav.year + '</a>');
|
||||
parts.push('<a href="javascript:void(0)" class="breadcrumb__link" onclick=\'BrandCatalog.selectYear(' + this.nav.yearId + ',' + this.nav.year + ')\'>' + this.nav.year + '</a>');
|
||||
}
|
||||
if (this.nav.engine) {
|
||||
parts.push('<a href="javascript:void(0)" onclick="BrandCatalog.selectEngine(' + this.nav.myeId + ',' + JSON.stringify(this.nav.engine) + ')" style="color:var(--color-primary);text-decoration:none;">' + escapeHtml(this.nav.engine) + '</a>');
|
||||
parts.push('<a href="javascript:void(0)" class="breadcrumb__link" onclick=\'BrandCatalog.selectEngine(' + this.nav.myeId + ',' + JSON.stringify(this.nav.engine) + ')\'>' + escapeHtml(this.nav.engine) + '</a>');
|
||||
}
|
||||
if (this.nav.category) {
|
||||
parts.push('<strong>' + escapeHtml(this.nav.category) + '</strong>');
|
||||
parts.push('<span class="breadcrumb__current">' + escapeHtml(this.nav.category) + '</span>');
|
||||
}
|
||||
this.setBreadcrumb(parts.join(' › '));
|
||||
this.setBreadcrumb('<nav class="breadcrumb">' + parts.join('<span class="breadcrumb__sep">›</span>') + '</nav>');
|
||||
},
|
||||
|
||||
// ---------- BRANDS ----------
|
||||
@@ -112,12 +115,10 @@
|
||||
this.loading(true);
|
||||
this.state = 'brands';
|
||||
this.reset();
|
||||
this.setBreadcrumb('<strong>Marcas de vehiculo</strong>');
|
||||
this.setBreadcrumb('<nav class="breadcrumb"><span class="breadcrumb__current">Marcas de vehiculo</span></nav>');
|
||||
this.setSearch(
|
||||
'<input type="text" id="brandSearchInput" placeholder="Buscar marca..." ' +
|
||||
'style="width:100%;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
||||
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);' +
|
||||
'outline:none;" oninput="BrandCatalog.filterBrands(this.value)">'
|
||||
'class="level-filter" oninput="BrandCatalog.filterBrands(this.value)">'
|
||||
);
|
||||
var self = this;
|
||||
fetch('/pos/api/catalog/vehicle-brands', { headers: this._headers() })
|
||||
@@ -130,24 +131,25 @@
|
||||
self.loading(false);
|
||||
self._allBrands = data.brands || [];
|
||||
if (!self._allBrands.length) {
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron marcas.</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">No se encontraron marcas.</div></div>');
|
||||
return;
|
||||
}
|
||||
self.renderBrandList(self._allBrands);
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar marcas: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar marcas</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderBrandList: function(brands) {
|
||||
var html = '';
|
||||
var html = '<div class="nav-grid">';
|
||||
brands.forEach(function(b) {
|
||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectBrand(' + JSON.stringify(b.name) + ',' + b.id + ')">' +
|
||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(b.name) + '</div>' +
|
||||
html += '<div class="nav-card" onclick=\'BrandCatalog.selectBrand(' + JSON.stringify(b.name) + ',' + b.id + ')\'>' +
|
||||
'<div class="nav-card__name">' + escapeHtml(b.name) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
@@ -186,23 +188,28 @@
|
||||
self.loading(false);
|
||||
var models = data.data || [];
|
||||
if (!models.length) {
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron modelos.</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">No se encontraron modelos.</div></div>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
models.forEach(function(m) {
|
||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectModel(' + m.id_model + ',' + JSON.stringify(m.display_name || m.name_model) + ')">' +
|
||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(m.display_name || m.name_model) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
self.setContent(html);
|
||||
self.renderModelList(models);
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar modelos: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar modelos</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderModelList: function(models) {
|
||||
var html = '<div class="nav-grid">';
|
||||
models.forEach(function(m) {
|
||||
html += '<div class="nav-card" onclick=\'BrandCatalog.selectModel(' + m.id_model + ',' + JSON.stringify(m.display_name || m.name_model) + ')\'>' +
|
||||
'<div class="nav-card__name">' + escapeHtml(m.display_name || m.name_model) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
selectModel: function(modelId, modelName) {
|
||||
this.nav.model = modelName;
|
||||
this.nav.modelId = modelId;
|
||||
@@ -226,23 +233,28 @@
|
||||
self.loading(false);
|
||||
var years = data.data || [];
|
||||
if (!years.length) {
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron años.</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">No se encontraron años.</div></div>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
years.forEach(function(y) {
|
||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectYear(' + y.id_year + ',' + y.year_car + ')">' +
|
||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + y.year_car + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
self.setContent(html);
|
||||
self.renderYearList(years);
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar años: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar años</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderYearList: function(years) {
|
||||
var html = '<div class="nav-grid nav-grid--years">';
|
||||
years.forEach(function(y) {
|
||||
html += '<div class="nav-card nav-card--year" onclick=\'BrandCatalog.selectYear(' + y.id_year + ',' + y.year_car + ')\'>' +
|
||||
'<div class="nav-card__name">' + y.year_car + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
selectYear: function(yearId, yearCar) {
|
||||
this.nav.year = yearCar;
|
||||
this.nav.yearId = yearId;
|
||||
@@ -266,24 +278,29 @@
|
||||
self.loading(false);
|
||||
var engines = data.data || [];
|
||||
if (!engines.length) {
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron motores.</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">No se encontraron motores.</div></div>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
engines.forEach(function(e) {
|
||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectEngine(' + e.id_mye + ',' + JSON.stringify(e.name_engine) + ')">' +
|
||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(e.name_engine) + '</div>' +
|
||||
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + escapeHtml(e.trim_level || '') + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
self.setContent(html);
|
||||
self.renderEngineList(engines);
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar motores: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar motores</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderEngineList: function(engines) {
|
||||
var html = '<div class="nav-grid">';
|
||||
engines.forEach(function(e) {
|
||||
html += '<div class="nav-card" onclick=\'BrandCatalog.selectEngine(' + e.id_mye + ',' + JSON.stringify(e.name_engine) + ')\'>' +
|
||||
'<div class="nav-card__name">' + escapeHtml(e.name_engine) + '</div>' +
|
||||
'<div class="nav-card__sub">' + escapeHtml(e.trim_level || '') + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
selectEngine: function(myeId, engineName) {
|
||||
this.nav.engine = engineName;
|
||||
this.nav.myeId = myeId;
|
||||
@@ -305,26 +322,36 @@
|
||||
.then(function(data) {
|
||||
if (!data) return;
|
||||
self.loading(false);
|
||||
self._allowedBrands = data.allowed_brands || [];
|
||||
var categories = data.data || [];
|
||||
if (!categories.length) {
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-text-muted);">No se encontraron categorias.</p>');
|
||||
var msg = 'No se encontraron categorias.';
|
||||
if (self._allowedBrands.length) {
|
||||
msg = 'Este vehiculo no tiene cobertura de ' + self._allowedBrands.join(', ') + '.';
|
||||
}
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">' + msg + '</div><div class="empty-state__subtitle">Prueba con otro vehiculo o contacta a soporte para ampliar el catalogo.</div></div>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
categories.forEach(function(c) {
|
||||
html += '<div class="catalog-category-card" onclick="BrandCatalog.selectCategory(' + c.id_part_category + ',' + JSON.stringify(c.name) + ')">' +
|
||||
'<div style="font-size:var(--text-h4);font-family:var(--font-heading);margin-bottom:4px;">' + escapeHtml(c.name) + '</div>' +
|
||||
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);">' + (c.part_count || 0) + ' refacciones</div>' +
|
||||
'</div>';
|
||||
});
|
||||
self.setContent(html);
|
||||
self.renderCategoryList(categories);
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar categorias: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar categorias</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderCategoryList: function(categories) {
|
||||
var html = '<div class="nav-grid">';
|
||||
categories.forEach(function(c) {
|
||||
html += '<div class="nav-card" onclick=\'BrandCatalog.selectCategory(' + c.id_part_category + ',' + JSON.stringify(c.name) + ')\'>' +
|
||||
'<div class="nav-card__name">' + escapeHtml(c.name) + '</div>' +
|
||||
'<div class="nav-card__sub">' + (c.part_count || 0) + ' refacciones</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
selectCategory: function(catId, catName) {
|
||||
this.nav.category = catName;
|
||||
this.nav.categoryId = catId;
|
||||
@@ -340,11 +367,10 @@
|
||||
this.setSearch(
|
||||
'<div style="display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center;">' +
|
||||
'<input type="text" id="partsSearchInput" placeholder="Buscar refaccion..." value="' + escapeHtml(searchTerm || '') + '" ' +
|
||||
'style="flex:1;min-width:200px;padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radius-md);' +
|
||||
'font-size:var(--text-body);background:var(--color-surface);color:var(--color-text-primary);outline:none;" ' +
|
||||
'class="level-filter" ' +
|
||||
'onkeydown="if(event.key===\'Enter\')BrandCatalog.searchParts(this.value)">' +
|
||||
'<button class="btn btn--primary btn--sm" onclick="BrandCatalog.searchParts(document.getElementById(\'partsSearchInput\').value)">Buscar</button>' +
|
||||
'<button class="btn btn--secondary btn--sm" onclick="BrandCatalog.clearPartsSearch()">Limpiar</button>' +
|
||||
'<button class="btn btn-primary" onclick="BrandCatalog.searchParts(document.getElementById(\'partsSearchInput\').value)">Buscar</button>' +
|
||||
'<button class="btn btn-ghost" onclick="BrandCatalog.clearPartsSearch()">Limpiar</button>' +
|
||||
'</div>'
|
||||
);
|
||||
var url = '/pos/api/catalog/mye-parts?mye_id=' + encodeURIComponent(myeId) + '&category_id=' + encodeURIComponent(categoryId) +
|
||||
@@ -361,6 +387,7 @@
|
||||
.then(function(data) {
|
||||
if (!data) return;
|
||||
self.loading(false);
|
||||
self._allowedBrands = data.allowed_brands || [];
|
||||
self._lastItems = data.items || [];
|
||||
self._total = data.total || 0;
|
||||
self._offset = data.offset || 0;
|
||||
@@ -368,16 +395,20 @@
|
||||
})
|
||||
.catch(function(err) {
|
||||
self.loading(false);
|
||||
self.setContent('<p style="grid-column:1/-1;text-align:center;color:var(--color-error);">Error al cargar refacciones: ' + escapeHtml(err.message) + '</p>');
|
||||
self.setContent('<div class="empty-state is-visible"><div class="empty-state__title">Error al cargar refacciones</div><div class="empty-state__subtitle">' + escapeHtml(err.message) + '</div></div>');
|
||||
});
|
||||
},
|
||||
|
||||
renderPartsList: function(items, searchTerm) {
|
||||
var html = '';
|
||||
if (!items.length) {
|
||||
html += '<div style="grid-column:1/-1;text-align:center;padding:var(--space-8);">' +
|
||||
'<p style="color:var(--color-text-muted);font-size:var(--text-body-lg);">No se encontraron refacciones.</p>' +
|
||||
'<button class="btn btn--primary" style="margin-top:var(--space-3);" onclick="BrandCatalog.loadCategories(' + this.nav.myeId + ')">Volver a categorias</button>' +
|
||||
var msg = 'No se encontraron refacciones.';
|
||||
if (this._allowedBrands.length) {
|
||||
msg = 'No hay refacciones de ' + this._allowedBrands.join(', ') + ' en esta categoria.';
|
||||
}
|
||||
html += '<div class="empty-state is-visible">' +
|
||||
'<div class="empty-state__title">' + msg + '</div>' +
|
||||
'<div class="empty-state__subtitle"><button class="btn btn-primary" onclick="BrandCatalog.loadCategories(' + this.nav.myeId + ')">Volver a categorias</button></div>' +
|
||||
'</div>';
|
||||
this.setContent(html);
|
||||
return;
|
||||
@@ -389,40 +420,55 @@
|
||||
'Mostrando ' + startIdx + '-' + endIdx + ' de ' + this._total + ' refacciones' +
|
||||
'</div>';
|
||||
|
||||
html += '<div style="grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-3);">';
|
||||
html += '<div class="nav-grid nav-grid--parts">';
|
||||
items.forEach(function(p) {
|
||||
var price = p.local_price ? '$' + Number(p.local_price).toFixed(2) : 'Consultar precio';
|
||||
var img = '/pos/static/images/placeholder-part.png';
|
||||
var hasAm = !!p.manufacturer;
|
||||
var price = p.local_price
|
||||
? '$' + Number(p.local_price).toFixed(2)
|
||||
: (p.price_usd ? '$' + Number(p.price_usd).toFixed(2) : 'Consultar precio');
|
||||
var stockBadge = p.local_stock > 0
|
||||
? '<span style="display:inline-block;background:var(--color-success);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">' + p.local_stock + ' en stock</span>'
|
||||
: '<span style="display:inline-block;background:var(--color-text-muted);color:#fff;font-size:11px;padding:2px 8px;border-radius:var(--radius-sm);margin-left:6px;">Sin stock local</span>';
|
||||
html += '<div class="catalog-category-card" style="padding:0;overflow:hidden;display:flex;flex-direction:column;">' +
|
||||
'<div style="height:160px;background:#f5f5f5;display:flex;align-items:center;justify-content:center;">' +
|
||||
'<img src="' + escapeHtml(img) + '" alt="" style="max-width:100%;max-height:100%;object-fit:contain;">' +
|
||||
? '<span class="stock-badge stock-badge--local">En stock</span>'
|
||||
: '<span class="stock-badge stock-badge--none">Sin stock local</span>';
|
||||
var imgHtml = p.image_url
|
||||
? '<img src="' + escapeHtml(p.image_url) + '" alt="">'
|
||||
: '<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
|
||||
var brandLine = hasAm
|
||||
? '<div style="font-size:var(--text-caption);color:var(--color-accent);font-weight:600;">' + escapeHtml(p.manufacturer) + '</div>'
|
||||
: '';
|
||||
html += '<div class="part-card">' +
|
||||
'<div class="part-card__image">' + imgHtml + '</div>' +
|
||||
'<div class="part-card__body">' +
|
||||
brandLine +
|
||||
'<div class="part-card__oem">' + escapeHtml(p.oem_part_number || 'N/A') + '</div>' +
|
||||
'<div class="part-card__name">' + escapeHtml(p.name || '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div style="padding:var(--space-3);flex:1;display:flex;flex-direction:column;">' +
|
||||
'<div style="font-weight:600;font-size:var(--text-body);margin-bottom:4px;">' + escapeHtml(p.oem_part_number || 'N/A') + stockBadge + '</div>' +
|
||||
'<div style="font-size:var(--text-body-sm);color:var(--color-text-muted);margin-bottom:8px;flex:1;">' + escapeHtml(p.name || '') + '</div>' +
|
||||
'<div style="font-size:var(--text-h5);font-weight:700;color:var(--color-primary);margin-bottom:8px;">' + price + '</div>' +
|
||||
'<button class="btn btn--primary btn--sm" style="width:100%;" onclick="BrandCatalog.addToCart(' + p.id + ', event)">Agregar</button>' +
|
||||
'<div class="part-card__footer">' +
|
||||
stockBadge +
|
||||
'<span class="part-card__price">' + price + '</span>' +
|
||||
'</div>' +
|
||||
'<button class="btn btn-primary" onclick="BrandCatalog.addToCart(' + p.id + ', event)">Agregar</button>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
html += this.renderPagination();
|
||||
|
||||
this.setContent(html);
|
||||
},
|
||||
|
||||
renderPagination: function() {
|
||||
var hasPrev = this._offset > 0;
|
||||
var hasNext = (this._offset + this._limit) < this._total;
|
||||
var pageNum = Math.floor(this._offset / this._limit) + 1;
|
||||
var totalPages = Math.ceil(this._total / this._limit) || 1;
|
||||
html += '<div style="grid-column:1/-1;display:flex;justify-content:center;align-items:center;gap:var(--space-3);padding:var(--space-4) 0;">' +
|
||||
'<button class="btn btn--secondary" ' + (hasPrev ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
||||
var html = '<div class="pagination">' +
|
||||
'<button class="page-item" ' + (hasPrev ? '' : 'disabled') +
|
||||
' onclick="BrandCatalog.goToPage(' + (this._offset - this._limit) + ')">← Anterior</button>' +
|
||||
'<span style="font-size:var(--text-body-sm);color:var(--color-text-muted);">Pagina ' + pageNum + ' de ' + totalPages + '</span>' +
|
||||
'<button class="btn btn--secondary" ' + (hasNext ? '' : 'disabled style="opacity:0.5;cursor:not-allowed;"') +
|
||||
'<button class="page-item" ' + (hasNext ? '' : 'disabled') +
|
||||
' onclick="BrandCatalog.goToPage(' + (this._offset + this._limit) + ')">Siguiente →</button>' +
|
||||
'</div>';
|
||||
|
||||
this.setContent(html);
|
||||
return html;
|
||||
},
|
||||
|
||||
searchParts: function(term) {
|
||||
@@ -451,16 +497,17 @@
|
||||
return;
|
||||
}
|
||||
if (window.CatalogApp && CatalogApp.addToCart) {
|
||||
var isAftermarket = !!part.manufacturer;
|
||||
CatalogApp.addToCart({
|
||||
id: part.id,
|
||||
id: part.oem_id || part.id,
|
||||
part_number: part.oem_part_number || 'N/A',
|
||||
name: part.name || 'Refaccion',
|
||||
brand: '',
|
||||
price: part.local_price || 0,
|
||||
brand: part.manufacturer || '',
|
||||
price: part.local_price || part.price_usd || 0,
|
||||
tax_rate: 0.16,
|
||||
unit: 'PZA',
|
||||
stock: part.local_stock || 0,
|
||||
source: 'oem-brand',
|
||||
source: isAftermarket ? 'aftermarket' : 'oem-brand',
|
||||
inventory_id: null
|
||||
}, 1);
|
||||
var btn = event.target;
|
||||
|
||||
Reference in New Issue
Block a user