/* ============================================================
BULK EDIT MANAGEMENT
============================================================ */
let _bulkEditChanges = {};
let _bulkCategoryFilter = 'all';
function initBulkEdit() {
renderBulkCategoryFilters();
renderBulkPriceTools();
renderBulkEditTable();
renderBulkListHeaders();
updateBulkChangesCount();
}
function renderBulkCategoryFilters() {
const container = document.getElementById('bulk-category-filters');
if (!container) return;
const categories = CONFIG.CATEGORIES.map(c => c.id);
const buttons = categories.map(cat => {
const label = CONFIG.CATEGORIES.find(c => c.id === cat)?.name || cat;
return ``;
});
container.innerHTML = buttons.join('');
}
function renderBulkPriceTools() {
const container = document.getElementById('bulk-price-tools');
if (!container) return;
const activeLists = STATE.priceLists.filter(pl => pl.is_active);
if (!activeLists.length) {
container.innerHTML = '';
return;
}
container.innerHTML = `
`;
}
function applyBulkPriceAdjustment() {
const listId = parseInt(document.getElementById('bulk-tool-list')?.value);
const percent = parseFloat(document.getElementById('bulk-tool-percent')?.value) || 0;
if (!listId) return;
const filteredProducts = _bulkCategoryFilter === 'all'
? STATE.products
: STATE.products.filter(p => p.cat === _bulkCategoryFilter);
filteredProducts.forEach(product => {
if (!_bulkEditChanges[product.id]) _bulkEditChanges[product.id] = {};
if (!_bulkEditChanges[product.id].priceOverrides) _bulkEditChanges[product.id].priceOverrides = {};
const basePrice = getPriceForList(product, listId);
const newPrice = roundPrice(basePrice * (1 + percent / 100));
_bulkEditChanges[product.id].priceOverrides[listId] = newPrice;
});
updateBulkChangesCount();
renderBulkEditTable();
showToast(`Ajuste de ${percent >= 0 ? '+' : ''}${percent}% aplicado a ${filteredProducts.length} productos`, 'success');
}
function applyBulkPriceAbsolute() {
const listId = parseInt(document.getElementById('bulk-tool-list')?.value);
if (!listId) return;
const list = STATE.priceLists.find(pl => pl.id === listId);
if (!list) return;
const filteredProducts = _bulkCategoryFilter === 'all'
? STATE.products
: STATE.products.filter(p => p.cat === _bulkCategoryFilter);
filteredProducts.forEach(product => {
if (!_bulkEditChanges[product.id]) _bulkEditChanges[product.id] = {};
if (!_bulkEditChanges[product.id].priceOverrides) _bulkEditChanges[product.id].priceOverrides = {};
const newPrice = roundPrice(product.sale * list.factor);
_bulkEditChanges[product.id].priceOverrides[listId] = newPrice;
});
updateBulkChangesCount();
renderBulkEditTable();
showToast(`Precio base × factor aplicado a ${filteredProducts.length} productos`, 'success');
}
function clearBulkListOverrides() {
const listId = parseInt(document.getElementById('bulk-tool-list')?.value);
if (!listId) return;
const filteredProducts = _bulkCategoryFilter === 'all'
? STATE.products
: STATE.products.filter(p => p.cat === _bulkCategoryFilter);
filteredProducts.forEach(product => {
if (_bulkEditChanges[product.id]?.priceOverrides) {
delete _bulkEditChanges[product.id].priceOverrides[listId];
if (!Object.keys(_bulkEditChanges[product.id].priceOverrides).length) {
delete _bulkEditChanges[product.id].priceOverrides;
}
if (!Object.keys(_bulkEditChanges[product.id]).length) {
delete _bulkEditChanges[product.id];
}
}
const list = STATE.priceLists.find(pl => pl.id === listId);
if (list && list.overrides && list.overrides[product.id] !== undefined) {
if (!_bulkEditChanges[product.id]) _bulkEditChanges[product.id] = {};
if (!_bulkEditChanges[product.id].priceOverrides) _bulkEditChanges[product.id].priceOverrides = {};
_bulkEditChanges[product.id].priceOverrides[listId] = null;
}
});
updateBulkChangesCount();
renderBulkEditTable();
showToast('Overrides limpiados. Guardá para confirmar.', 'info');
}
function renderBulkListHeaders() {
const headerCell = document.getElementById('bulk-price-list-headers');
if (!headerCell) return;
const activeLists = STATE.priceLists.filter(pl => pl.is_active);
if (!activeLists.length) {
headerCell.textContent = '';
return;
}
headerCell.style.textAlign = 'center';
headerCell.style.padding = '12px';
headerCell.style.fontWeight = '600';
headerCell.innerHTML = activeLists.map(l =>
`${escapeHtml(l.name)}`
).join('');
}
function renderBulkEditTable() {
const tbody = document.getElementById('bulk-edit-tbody');
if (!tbody) return;
const filteredProducts = _bulkCategoryFilter === 'all'
? STATE.products
: STATE.products.filter(p => p.cat === _bulkCategoryFilter);
const activeLists = STATE.priceLists.filter(pl => pl.is_active);
tbody.innerHTML = filteredProducts.map(product => {
const changes = _bulkEditChanges[product.id] || {};
const currentStock = changes.stock !== undefined ? changes.stock : product.stock;
const currentCost = changes.cost !== undefined ? changes.cost : product.cost;
const currentSale = changes.sale !== undefined ? changes.sale : product.sale;
const hasChanges = Object.keys(changes).length > 0;
return `
|
${escapeHtml(product.name)}
${escapeHtml(product.sku || '—')}
|
${escapeHtml(product.cat || '—')} |
|
|
|
${activeLists.map(list => {
const pendingOverride = changes.priceOverrides?.[list.id];
const currentVal = pendingOverride !== undefined && pendingOverride !== null
? pendingOverride
: getPriceForList(product, list.id);
const isOverridden = (list.overrides && list.overrides[product.id] !== undefined) || pendingOverride !== undefined;
return ` `;
}).join('')}
|
`;
}).join('');
}
function handleBulkCellChange(productId, field, value) {
if (!_bulkEditChanges[productId]) {
_bulkEditChanges[productId] = {};
}
const product = STATE.products.find(p => p.id === productId);
if (!product) return;
const parsedValue = field === 'stock' ? parseInt(value) : parseFloat(value);
_bulkEditChanges[productId][field] = parsedValue;
const row = document.querySelector(`tr[data-product-id="${productId}"]`);
if (row) {
row.style.backgroundColor = '#fef3c7';
}
updateBulkChangesCount();
}
function handleBulkPriceOverride(productId, listId, price) {
if (!_bulkEditChanges[productId]) {
_bulkEditChanges[productId] = {};
}
const parsedPrice = parseFloat(price);
if (!_bulkEditChanges[productId].priceOverrides) {
_bulkEditChanges[productId].priceOverrides = {};
}
_bulkEditChanges[productId].priceOverrides[listId] = parsedPrice;
const row = document.querySelector(`tr[data-product-id="${productId}"]`);
if (row) {
row.style.backgroundColor = '#fef3c7';
}
updateBulkChangesCount();
}
function updateBulkChangesCount() {
const count = Object.keys(_bulkEditChanges).length;
const countEl = document.getElementById('bulk-changes-count');
if (countEl) {
countEl.textContent = count;
}
}
function filterBulkByCategory(category) {
_bulkCategoryFilter = category;
document.querySelectorAll('.category-filter-btn').forEach(btn => {
btn.classList.remove('active', 'bg-indigo-600', 'text-white');
btn.classList.add('bg-gray-100', 'text-gray-700');
});
const activeBtn = document.querySelector(`.category-filter-btn[data-category="${category}"]`);
if (activeBtn) {
activeBtn.classList.remove('bg-gray-100', 'text-gray-700');
activeBtn.classList.add('active', 'bg-indigo-600', 'text-white');
}
renderBulkEditTable();
}
function saveBulkChanges() {
const changedCount = Object.keys(_bulkEditChanges).length;
if (changedCount === 0) {
showToast('No hay cambios para guardar', 'info');
return;
}
for (const productId in _bulkEditChanges) {
const changes = _bulkEditChanges[productId];
const product = STATE.products.find(p => p.id === productId);
if (!product) continue;
const oldSale = product.sale;
const oldStock = product.stock;
if (changes.stock !== undefined) product.stock = changes.stock;
if (changes.cost !== undefined) product.cost = changes.cost;
if (changes.sale !== undefined) {
product.sale = changes.sale;
product.price = changes.sale;
}
if (changes.priceOverrides) {
for (const listId in changes.priceOverrides) {
const priceVal = changes.priceOverrides[listId];
if (priceVal === null) {
const list = STATE.priceLists.find(pl => pl.id === parseInt(listId));
if (list && list.overrides) delete list.overrides[productId];
} else {
setPriceOverride(parseInt(listId), productId, priceVal);
}
}
}
if (typeof logChange === 'function') {
const fields = [];
if (changes.stock !== undefined && changes.stock !== oldStock) fields.push(`stock: ${oldStock}→${changes.stock}`);
if (changes.sale !== undefined && changes.sale !== oldSale) fields.push(`precio: ${fmt(oldSale)}→${fmt(changes.sale)}`);
if (changes.priceOverrides) {
for (const listId in changes.priceOverrides) {
const list = STATE.priceLists.find(pl => pl.id === parseInt(listId));
if (list) fields.push(`${list.name}: ${fmt(changes.priceOverrides[listId])}`);
}
}
if (fields.length) {
logChange({
type: 'bulk',
productId: product.id,
productName: product.name,
field: fields.join(', '),
oldValue: null,
newValue: null,
note: `Edición masiva: ${fields.join(', ')}`
});
}
}
}
persistData();
_bulkEditChanges = {};
updateBulkChangesCount();
renderBulkEditTable();
showToast(`${changedCount} producto(s) actualizado(s)`, 'success');
}
function goBackFromBulkEdit() {
if (Object.keys(_bulkEditChanges).length > 0) {
showConfirmDialog('Tienes cambios sin guardar. ¿Descartar cambios y volver?', () => {
_bulkEditChanges = {};
window.location.href = '/admin/productos';
});
} else {
window.location.href = '/admin/productos';
}
}