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:
2026-05-26 04:24:07 +00:00
parent 50c0dbe7d4
commit a236187f3a
66 changed files with 7335 additions and 498 deletions

View File

@@ -16,6 +16,7 @@
var activePhone = null;
var pollTimer = null;
var statusPollTimer = null;
var qrPollTimer = null;
var connectionState = 'unknown'; // 'open', 'close', 'connecting', 'unknown'
// -- Helpers ---------------------------------------------------------------
@@ -88,6 +89,10 @@
api('GET', '/status').then(function (data) {
var state = (data.instance || data).state || data.state || 'close';
updateConnectionUI(state);
// If bridge already has a QR ready, show it immediately
if (state === 'qr' || state === 'connecting') {
fetchQR();
}
}).catch(function () {
updateConnectionUI('close');
});
@@ -106,7 +111,8 @@
// Load conversations + start polling on page load / reconnect
loadConversations();
startPolling();
} else if (state === 'connecting') {
stopQRPolling();
} else if (state === 'connecting' || state === 'qr') {
statusDot.className = 'status-dot status-dot--warn';
statusText.textContent = 'Escaneando QR...';
connectSection.style.display = 'flex';
@@ -125,6 +131,7 @@
refreshQrBtn.style.display = 'none';
qrImg.style.display = 'none';
qrPlaceholder.style.display = '';
stopQRPolling();
}
}
@@ -141,8 +148,15 @@
return;
}
// Instance created, now fetch QR
fetchQR();
// Switch UI to connecting state immediately
updateConnectionUI('connecting');
qrPlaceholder.textContent = 'Iniciando conexion con WhatsApp, generando QR...';
qrPlaceholder.style.display = '';
qrImg.style.display = 'none';
// Start polling for QR; the first fetchQR may not have QR ready yet
startStatusPolling();
startQRPolling();
}).catch(function () {
connectBtn.disabled = false;
connectBtn.textContent = 'Conectar WhatsApp';
@@ -151,7 +165,10 @@
}
function fetchQR() {
qrPlaceholder.textContent = 'Generando QR...';
// Only update placeholder text if we don't already have a QR image showing
if (qrImg.style.display !== 'block') {
qrPlaceholder.textContent = 'Generando codigo QR, espera unos segundos...';
}
api('GET', '/qr').then(function (data) {
var base64 = data.qr || data.base64 || data.qrcode || '';
@@ -164,14 +181,18 @@
// Start polling for connection state while QR is shown
startStatusPolling();
startQRPolling();
} else if ((data.instance && data.instance.state === 'open') || data.state === 'open') {
// Already connected
updateConnectionUI('open');
loadConversations();
} else {
qrPlaceholder.textContent = 'No se pudo generar el QR. Intenta de nuevo.';
qrPlaceholder.style.display = '';
qrImg.style.display = 'none';
// QR not ready yet — this is normal right after pressing Connect
if (qrImg.style.display !== 'block') {
qrPlaceholder.textContent = 'Generando codigo QR, por favor espera... (el codigo cambia cada pocos segundos, escanealo en cuanto aparezca)';
qrPlaceholder.style.display = '';
qrImg.style.display = 'none';
}
}
}).catch(function () {
qrPlaceholder.textContent = 'Error al obtener QR';
@@ -208,6 +229,24 @@
}
}
function startQRPolling() {
stopQRPolling();
qrPollTimer = setInterval(function () {
if (connectionState === 'connecting' || connectionState === 'qr') {
fetchQR();
} else {
stopQRPolling();
}
}, 5000);
}
function stopQRPolling() {
if (qrPollTimer) {
clearInterval(qrPollTimer);
qrPollTimer = null;
}
}
connectBtn.addEventListener('click', doConnect);
disconnectBtn.addEventListener('click', doDisconnect);
refreshQrBtn.addEventListener('click', fetchQR);