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

@@ -32,6 +32,10 @@
<span id="statusClock"></span>
</div>
<div class="status-bar__right">
<a href="/pos/dashboard" class="btn btn--ghost btn--sm" id="backToSystemBtn" style="margin-right:8px;display:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
Sistema
</a>
<div class="status-bar__user" aria-label="Usuario activo">
<div class="status-bar__user-avatar" aria-hidden="true">--</div>
<span id="employeeName">Empleado</span>
@@ -203,7 +207,7 @@
<button class="btn-secondary-action" onclick="POS.saveQuotation()" title="Cotizacion (F4)">Cotizar</button>
<button class="btn-secondary-action" onclick="POS.showLastSale()" title="Ultima venta (F5)">Ult.Venta</button>
<button class="btn-secondary-action" onclick="POS.showCutZModal()" title="Corte Z - Cerrar caja">Corte Z</button>
<button class="btn-secondary-action danger" id="btnCancelSale" title="Cancelar (Esc)">Cancelar</button>
<button class="btn-secondary-action danger" id="btnCancelSale" onclick="POS.openCancelModal()" title="Cancelar (Esc)">Cancelar</button>
</div>
</div>
</aside>
@@ -233,14 +237,14 @@
<span class="fkey-key">F6</span><span class="fkey-label">Cajon</span>
</div>
<div class="fkey-sep"></div>
<div class="fkey" title="Cantidad +/-">
<div class="fkey" onclick="POS.changeQuantity()" title="Cantidad +/-">
<span class="fkey-key">+/-</span><span class="fkey-label">Cantidad</span>
</div>
<div class="fkey" title="Descuento">
<div class="fkey" onclick="POS.applyDiscount()" title="Descuento">
<span class="fkey-key">*</span><span class="fkey-label">Descuento</span>
</div>
<div class="fkey-sep"></div>
<div class="fkey" id="fkeyEsc" title="Cancelar">
<div class="fkey" id="fkeyEsc" onclick="POS.openCancelModal()" title="Cancelar">
<span class="fkey-key">Esc</span><span class="fkey-label">Cancelar</span>
</div>
</div>
@@ -563,7 +567,7 @@
<script src="/pos/static/js/app-init.js" defer></script>
<script src="/pos/static/js/push.js" defer></script>
<script src="/pos/static/js/printer.js" defer></script>
<script src="/pos/static/js/pos.js?v=4" defer></script>
<script src="/pos/static/js/pos.js?v=5" defer></script>
<script>
// Cancel sale button wiring
@@ -577,11 +581,7 @@
closeCancelModal();
// Clear cart via POS module
if (typeof POS !== 'undefined') {
// Clear each item
const tbody = document.getElementById('cartBody');
while (tbody && tbody.firstChild) {
POS.removeFromCart(0);
}
POS.clearCart();
POS.clearCustomer();
}
});
@@ -614,6 +614,20 @@
}
updateClock();
setInterval(updateClock, 30000);
// Show "Back to System" button when NOT in kiosk mode
(function checkKiosk() {
var btn = document.getElementById('backToSystemBtn');
if (!btn) return;
function showIfNotKiosk() {
try {
var isKiosk = window.isKioskEnabled && window.isKioskEnabled();
btn.style.display = isKiosk ? 'none' : 'inline-flex';
} catch (e) { btn.style.display = 'inline-flex'; }
}
showIfNotKiosk();
setInterval(showIfNotKiosk, 2000);
})();
</script>
<script src="/pos/static/js/chat.js" defer></script>