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

@@ -188,10 +188,10 @@
<!-- Tabs -->
<div class="tabs-row" role="tablist">
<button class="tab-btn is-active" role="tab" aria-selected="true" onclick="switchTab('cxc')">
Ctas. por Cobrar <span class="tab-btn__badge">23</span>
Ctas. por Cobrar <span class="tab-btn__badge" id="badge-cxc">0</span>
</button>
<button class="tab-btn" role="tab" aria-selected="false" onclick="switchTab('cxp')">
Ctas. por Pagar <span class="tab-btn__badge--alert tab-btn__badge">3</span>
Ctas. por Pagar <span class="tab-btn__badge--alert tab-btn__badge" id="badge-cxp">0</span>
</button>
<button class="tab-btn" role="tab" aria-selected="false" onclick="switchTab('balance')">
Balance General
@@ -498,5 +498,31 @@
<script src="/pos/static/js/pwa-install.js" defer></script>
<script src="/pos/static/js/chat.js" defer></script>
<script>
// Load accounting stats for tab badges
async function loadAccountingStats() {
const token = localStorage.getItem('pos_token') || '';
try {
const res = await fetch('/pos/api/accounting/stats', {
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
});
if (!res.ok) return;
const data = await res.json();
const map = {
'badge-cxc': data.cuentas_cobrar,
'badge-cxp': data.cuentas_pagar
};
Object.entries(map).forEach(function([id, val]) {
const el = document.getElementById(id);
if (el) el.textContent = val || 0;
});
} catch (e) {
console.error('Failed to load accounting stats:', e);
}
}
window.loadAccountingStats = loadAccountingStats;
loadAccountingStats();
</script>
</body>
</html>