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:
@@ -19,6 +19,11 @@ const Config = (() => {
|
||||
return true;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function headers() {
|
||||
return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' };
|
||||
}
|
||||
@@ -623,6 +628,67 @@ const Config = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Allowed Part Brands
|
||||
// -------------------------------------------------------------------------
|
||||
async function loadAllowedBrands() {
|
||||
var container = document.getElementById('allowed-brands-container');
|
||||
if (!container) return;
|
||||
try {
|
||||
var res = await fetch(API + '/available-brands', { headers: headers() });
|
||||
if (!res.ok) throw new Error('Failed to load brands');
|
||||
var d = await res.json();
|
||||
var allBrands = d.brands || [];
|
||||
|
||||
var res2 = await fetch(API + '/allowed-brands', { headers: headers() });
|
||||
if (!res2.ok) throw new Error('Failed to load allowed brands');
|
||||
var d2 = await res2.json();
|
||||
var allowed = d2.brands || [];
|
||||
|
||||
if (!allBrands.length) {
|
||||
container.innerHTML = '<p style="color:var(--color-text-muted);font-size:var(--text-body-sm);">No hay marcas disponibles.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
allBrands.forEach(function(b) {
|
||||
var checked = allowed.indexOf(b) !== -1 ? 'checked' : '';
|
||||
html += '<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer;font-size:var(--text-body-sm);color:var(--color-text-primary);padding:var(--space-1);">' +
|
||||
'<input type="checkbox" value="' + escapeHtml(b) + '" data-brand-checkbox ' + checked + ' style="width:16px;height:16px;cursor:pointer;">' +
|
||||
escapeHtml(b) + '</label>';
|
||||
});
|
||||
container.innerHTML = html;
|
||||
} catch (e) {
|
||||
console.error('Config.loadAllowedBrands:', e);
|
||||
if (container) container.innerHTML = '<p style="color:var(--color-error);font-size:var(--text-body-sm);">Error al cargar marcas.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAllowedBrands() {
|
||||
var btn = document.getElementById('btn-save-allowed-brands');
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Guardando...'; }
|
||||
try {
|
||||
var checked = [];
|
||||
document.querySelectorAll('[data-brand-checkbox]').forEach(function(cb) {
|
||||
if (cb.checked) checked.push(cb.value);
|
||||
});
|
||||
var res = await fetch(API + '/allowed-brands', {
|
||||
method: 'PUT',
|
||||
headers: headers(),
|
||||
body: JSON.stringify({ brands: checked })
|
||||
});
|
||||
if (!res.ok) {
|
||||
var err = await res.json().catch(function() { return { error: res.statusText }; });
|
||||
throw new Error(err.error || 'Save failed');
|
||||
}
|
||||
toast('Marcas permitidas actualizadas');
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Guardar Marcas'; }
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Init
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -650,6 +716,12 @@ const Config = (() => {
|
||||
btnCompat.addEventListener('click', saveVehicleCompatSource);
|
||||
}
|
||||
|
||||
// Allowed brands save button
|
||||
var btnBrands = document.getElementById('btn-save-allowed-brands');
|
||||
if (btnBrands) {
|
||||
btnBrands.addEventListener('click', saveAllowedBrands);
|
||||
}
|
||||
|
||||
// Kiosk mode toggle
|
||||
var kioskToggle = document.getElementById('cfg-kiosk-mode');
|
||||
if (kioskToggle && window.NexusKiosk) {
|
||||
@@ -671,12 +743,13 @@ const Config = (() => {
|
||||
loadBusiness();
|
||||
loadCurrency();
|
||||
loadVehicleCompatSource();
|
||||
loadAllowedBrands();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
return {
|
||||
init, setTheme, selectThemeOption,
|
||||
init, setTheme, selectThemeOption, loadAllowedBrands, saveAllowedBrands,
|
||||
loadBranches, loadEmployees, saveBranch, saveEmployee, editEmployee,
|
||||
loadBusiness, saveBusiness, saveTaxParams,
|
||||
loadCurrency, saveCurrency,
|
||||
|
||||
Reference in New Issue
Block a user