VENDEDOR EN PEDIDO Y EN ADMINISTRADOR

This commit is contained in:
2026-06-11 14:08:03 -05:00
parent ad3911d3c3
commit e4588b75db
14 changed files with 758 additions and 187 deletions
+203 -1
View File
@@ -12,7 +12,8 @@ const TABS = [
{ id: 'agotados', label: 'Agotados', icon: '🚫' },
{ id: 'descargas', label: 'Descargas', icon: '⬇️' },
{ id: 'categorias', label: 'Categorías', icon: '🗂️' },
{ id: 'usuarios', label: 'Usuarios', icon: '👥' }
{ id: 'usuarios', label: 'Usuarios', icon: '👥' },
{ id: 'pedidos', label: 'Pedidos', icon: '📋' }
];
const ROLES = {
@@ -166,6 +167,10 @@ function cambiarVista(view) {
if (view === 'descargas') {
cargarDescargas();
}
if (view === 'pedidos') {
cargarPedidos();
}
}
async function cargarEstadisticas() {
@@ -2163,6 +2168,203 @@ document.addEventListener('DOMContentLoaded', () => {
if (btn) btn.addEventListener('click', confirmarEliminarProductoDef);
});
// ════════════════════════════════
// PEDIDOS — vendedores + listado de pedidos
// ════════════════════════════════
async function cargarPedidos() {
console.log('[pedidos] cargando…');
setupVendedores();
setupPedidosFiltro();
try { await actualizarSelectVendedores(); } catch (e) { console.error('[pedidos] select error:', e); }
await Promise.all([
cargarVendedores().catch(e => console.error('[pedidos] vendedores error:', e)),
cargarListadoPedidos().catch(e => console.error('[pedidos] listado error:', e))
]);
}
// ─── VENDEDORES ───
async function cargarVendedores() {
const listEl = document.getElementById('vendedoresList');
const errEl = document.getElementById('vendedorError');
errEl.classList.remove('show');
try {
const res = await fetch('/api/vendedores', { credentials: 'include' });
if (!res.ok) throw new Error('Error al cargar vendedores');
const vendedores = await res.json();
if (vendedores.length === 0) {
listEl.innerHTML = '<p class="pedidos-empty" style="text-align:left;padding:8px 0">No hay vendedores. Agrega el primero.</p>';
} else {
listEl.innerHTML = vendedores.map(v => `
<div class="vendedor-item" data-id="${v.id}">
<span class="vendedor-nombre">${escapeHtml(v.nombre)}</span>
<button class="vendedor-remove" data-id="${v.id}" title="Eliminar vendedor">✕</button>
</div>
`).join('');
}
// Wire delete
listEl.querySelectorAll('.vendedor-remove').forEach(btn => {
btn.addEventListener('click', async () => {
const id = btn.dataset.id;
try {
const delRes = await fetch(`/api/vendedores/${id}`, {
method: 'DELETE',
credentials: 'include'
});
if (!delRes.ok) throw new Error('Error al eliminar');
await cargarVendedores();
actualizarSelectVendedores();
} catch (err) {
errEl.textContent = err.message;
errEl.classList.add('show');
}
});
});
} catch (err) {
listEl.innerHTML = `<p class="pedidos-empty" style="text-align:left;padding:8px 0">Error: ${err.message}</p>`;
}
}
async function actualizarSelectVendedores() {
// Actualiza el select del modal público y el filtro de pedidos
try {
const res = await fetch('/api/vendedores');
const vendedores = await res.json();
const selects = ['pedidosFilterVendedor'];
selects.forEach(id => {
const sel = document.getElementById(id);
if (!sel) return;
const actual = sel.value;
sel.innerHTML = '<option value="">Todos los vendedores</option>' +
vendedores.map(v => `<option value="${escapeHtml(v.nombre)}">${escapeHtml(v.nombre)}</option>`).join('');
if (actual) sel.value = actual;
});
} catch {}
}
function setupVendedores() {
const btn = document.getElementById('btnAgregarVendedor');
const input = document.getElementById('vendedorInput');
const errEl = document.getElementById('vendedorError');
if (!btn || btn.dataset.vendWired) return;
btn.dataset.vendWired = '1';
async function agregar() {
const nombre = input.value.trim();
if (!nombre) {
errEl.textContent = 'Escribe un nombre de vendedor';
errEl.classList.add('show');
return;
}
errEl.classList.remove('show');
try {
const res = await fetch('/api/vendedores', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ nombre })
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || 'Error al agregar');
}
input.value = '';
await cargarVendedores();
await actualizarSelectVendedores();
} catch (err) {
errEl.textContent = err.message;
errEl.classList.add('show');
}
}
btn.addEventListener('click', agregar);
input.addEventListener('keydown', e => { if (e.key === 'Enter') agregar(); });
}
// ─── LISTADO DE PEDIDOS ───
async function cargarListadoPedidos() {
const tbody = document.getElementById('pedidosListBody');
const filter = document.getElementById('pedidosFilterVendedor');
const vendedor = filter ? filter.value : '';
try {
let url = '/api/pedidos';
if (vendedor) url += `?vendedor=${encodeURIComponent(vendedor)}`;
const res = await fetch(url, { credentials: 'include' });
if (!res.ok) {
tbody.innerHTML = '<tr><td colspan="6" class="pedidos-empty">No autorizado</td></tr>';
return;
}
const pedidos = await res.json();
// Ordenar por fecha descendente
pedidos.sort((a, b) => new Date(b.fecha) - new Date(a.fecha));
if (pedidos.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="pedidos-empty">No hay pedidos aún.</td></tr>';
return;
}
const renderedRows = pedidos.map(p => {
const fecha = new Date(p.fecha).toLocaleDateString('es-CO', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
const itemsCount = Array.isArray(p.items) ? p.items.reduce((s, i) => s + (i.cantidad || 1), 0) : 0;
const total = p.total || 0;
const clienteNombre = p.cliente ? p.cliente.nombre : '—';
const vendedorNombre = p.vendedor || '—';
return `<tr>
<td>${escapeHtml(fecha)}</td>
<td>${escapeHtml(clienteNombre)}</td>
<td>${escapeHtml(vendedorNombre)}</td>
<td>${itemsCount}</td>
<td class="pedidos-total">$${total.toLocaleString('es-CO')}</td>
<td>
<span class="pedidos-actions-group">
<a href="/pedidos/${p.filename}" class="btn btn-ghost btn-sm" target="_blank" download>📄 Ver PDF</a>
<button type="button" class="btn btn-danger btn-sm pedido-delete" data-filename="${p.filename}" title="Eliminar pedido">Eliminar</button>
</span>
</td>
</tr>`;
});
tbody.innerHTML = renderedRows.join('');
// Wire delete buttons
tbody.querySelectorAll('.pedido-delete').forEach(btn => {
btn.addEventListener('click', async () => {
const filename = btn.dataset.filename;
if (!confirm(`¿Eliminar el pedido "${filename}"? Esta acción no se puede deshacer.`)) return;
try {
const res = await fetch(`/api/pedidos/${encodeURIComponent(filename)}`, {
method: 'DELETE',
credentials: 'include'
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || 'Error al eliminar');
}
await cargarListadoPedidos();
} catch (err) {
alert('Error: ' + err.message);
}
});
});
} catch (err) {
tbody.innerHTML = `<tr><td colspan="6" class="pedidos-empty">Error: ${err.message}</td></tr>`;
}
}
function setupPedidosFiltro() {
const filter = document.getElementById('pedidosFilterVendedor');
if (!filter || filter.dataset.pedWired) return;
filter.dataset.pedWired = '1';
filter.addEventListener('change', cargarListadoPedidos);
}
// ════════════════════════════════
// DESCARGAS — gestor genérico de árbol de selección
// Usado por los subtabs PDF y Fotos.