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

@@ -123,6 +123,8 @@ const POS = (() => {
currentRegister = null;
document.getElementById('registerInfo').innerHTML =
'<span style="color:var(--color-error);cursor:pointer;" onclick="POS.showOpenRegisterModal()" title="Clic para abrir caja">&#x26A0; Sin caja abierta — Clic para abrir</span>';
// Force open register modal on first load
showOpenRegisterModal();
}
} catch (e) {
console.warn('Register check failed:', e);
@@ -253,6 +255,9 @@ const POS = (() => {
discount_pct: parseFloat(item.discount_pct || 0),
tax_rate: parseFloat(item.tax_rate || 0.16),
stock: item.stock || 0,
price_1: parseFloat(item.price_1 || 0),
price_2: parseFloat(item.price_2 || 0),
price_3: parseFloat(item.price_3 || 0),
});
renderCart();
@@ -265,6 +270,67 @@ const POS = (() => {
renderCart();
}
function clearCart() {
cart.length = 0;
selectedRow = -1;
renderCart();
}
function openCancelModal() {
const overlay = document.getElementById('overlay-cancelar-venta');
const dialog = document.getElementById('modal-cancelar-venta');
if (overlay) overlay.classList.add('active');
if (dialog) dialog.classList.add('active');
}
function closeCancelModal() {
const overlay = document.getElementById('overlay-cancelar-venta');
const dialog = document.getElementById('modal-cancelar-venta');
if (overlay) overlay.classList.remove('active');
if (dialog) dialog.classList.remove('active');
}
function changeQuantity() {
if (selectedRow < 0 || selectedRow >= cart.length) {
showToast('Selecciona un articulo primero', 'warn');
return;
}
const q = prompt('Nueva cantidad:', cart[selectedRow].quantity);
if (q !== null) {
const n = parseInt(q);
if (n > 0) {
cart[selectedRow].quantity = n;
renderCart();
}
}
}
function applyDiscount() {
if (selectedRow < 0 || selectedRow >= cart.length) {
showToast('Selecciona un articulo primero', 'warn');
return;
}
const d = prompt('Descuento %:', cart[selectedRow].discount_pct);
if (d !== null) {
const n = parseFloat(d);
if (n >= 0 && n <= 100) {
cart[selectedRow].discount_pct = n;
renderCart();
}
}
}
// Wire confirm-cancel button
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('btnConfirmCancel');
if (btn) {
btn.addEventListener('click', function() {
clearCart();
closeCancelModal();
});
}
});
function renderCart() {
const tbody = document.getElementById('cartBody');
const table = document.getElementById('cartTable');
@@ -472,6 +538,9 @@ const POS = (() => {
cost: item.cost,
tax_rate: item.tax_rate,
stock: item.stock,
price_1: item.price_1,
price_2: item.price_2,
price_3: item.price_3,
});
hideSearchResults();
document.getElementById('itemSearch').value = '';
@@ -530,11 +599,22 @@ const POS = (() => {
}
}
function recalcCartPrices() {
const tier = currentCustomer ? (currentCustomer.price_tier || 1) : 1;
cart.forEach(item => {
if (item.price_1 > 0) {
item.unit_price = tier === 3 ? item.price_3 : tier === 2 ? item.price_2 : item.price_1;
}
});
}
async function selectCustomer(customer) {
currentCustomer = customer;
document.getElementById('customerAutocomplete').style.display = 'none';
document.getElementById('customerSearchWrap').querySelector('input').style.display = 'none';
recalcCartPrices();
const tiers = { 1: 'P1 Mostrador', 2: 'P2 Taller', 3: 'P3 Mayoreo' };
document.getElementById('customerName').textContent = customer.name;
document.getElementById('customerTier').textContent = tiers[customer.price_tier] || 'P1';
@@ -1255,7 +1335,7 @@ const POS = (() => {
init();
return {
addToCart, removeFromCart, selectRow,
addToCart, removeFromCart, clearCart, selectRow,
updateQty, updateDiscount,
addFromSearch, hideSearchResults,
selectCustomer, clearCustomer,
@@ -1268,5 +1348,6 @@ const POS = (() => {
connectThermal, thermalPrint,
showOpenRegisterModal, closeOpenRegisterModal, openRegister,
showCutZModal, closeCutZModal, loadCutX, confirmCutZ,
openCancelModal, closeCancelModal, changeQuantity, applyDiscount,
};
})();