PDF PEDIDOS

This commit is contained in:
2026-06-09 10:05:00 -05:00
parent b19deda02c
commit d0581d36d1
9 changed files with 896 additions and 6 deletions
+256
View File
@@ -275,10 +275,21 @@ function renderProductos() {
<h3 class="producto-nombre">${escapeHtml(nombre)}</h3>
${desc ? `<p class="producto-desc">${escapeHtml(desc)}</p>` : ''}
<div class="producto-precio">${precio}</div>
<div class="producto-card-add">
<input type="number" class="producto-card-add-qty" value="1" min="1" max="99"
data-ref="${p.ref}" aria-label="Cantidad">
<button class="producto-card-add-btn" data-ref="${p.ref}"
data-nombre="${escapeHtml(nombre)}"
data-precio="${p.precio ?? ''}">
+ Agregar
</button>
</div>
</div>
</article>
`;
}).join('');
wireAddToCart();
}
// ════════════════════════════════
@@ -328,11 +339,256 @@ function setupNav() {
});
}
// ════════════════════════════════
// CARRITO DE COMPRAS
// ════════════════════════════════
let carrito = []; // { ref, nombre, precio, cantidad }
function actualizarBadge() {
const total = carrito.reduce((s, i) => s + i.cantidad, 0);
const badge = document.getElementById('cartBadge');
badge.textContent = total;
badge.style.display = total > 0 ? 'flex' : 'none';
}
function actualizarTotal() {
const total = carrito.reduce((s, i) => s + i.precio * i.cantidad, 0);
document.querySelector('#cartTotal strong').textContent =
total > 0 ? `$${total.toLocaleString('es-CO')}` : '$0';
document.getElementById('cartPdfBtn').disabled = carrito.length === 0;
}
function renderCarritoModal() {
const body = document.getElementById('cartBody');
if (carrito.length === 0) {
body.innerHTML = '<p class="cart-empty">El carrito está vacío.</p>';
actualizarTotal();
return;
}
const totalGeneral = carrito.reduce((s, i) => s + i.precio * i.cantidad, 0);
body.innerHTML = `<div class="cart-items">
${carrito.map((item, idx) => {
const subtotal = item.precio * item.cantidad;
return `<div class="cart-item">
<div class="cart-item-info">
<div class="cart-item-name">${escapeHtml(item.nombre)}</div>
<div class="cart-item-ref">${escapeHtml(item.ref)}$${(item.precio || 0).toLocaleString('es-CO')} c/u</div>
</div>
<div class="cart-item-controls">
<button class="cart-item-qty-btn" data-cart-dec="${idx}"></button>
<span class="cart-item-qty">${item.cantidad}</span>
<button class="cart-item-qty-btn" data-cart-inc="${idx}">+</button>
</div>
<div class="cart-item-subtotal">$${subtotal.toLocaleString('es-CO')}</div>
<button class="cart-item-remove" data-cart-remove="${idx}" title="Eliminar">✕</button>
</div>`;
}).join('')}
</div>`;
body.querySelectorAll('[data-cart-inc]').forEach(btn => {
btn.addEventListener('click', () => {
const idx = parseInt(btn.dataset.cartInc, 10);
carrito[idx].cantidad++;
renderCarritoModal();
actualizarBadge();
});
});
body.querySelectorAll('[data-cart-dec]').forEach(btn => {
btn.addEventListener('click', () => {
const idx = parseInt(btn.dataset.cartDec, 10);
if (carrito[idx].cantidad > 1) {
carrito[idx].cantidad--;
} else {
carrito.splice(idx, 1);
}
renderCarritoModal();
actualizarBadge();
});
});
body.querySelectorAll('[data-cart-remove]').forEach(btn => {
btn.addEventListener('click', () => {
const idx = parseInt(btn.dataset.cartRemove, 10);
carrito.splice(idx, 1);
renderCarritoModal();
actualizarBadge();
});
});
actualizarTotal();
}
// Modales
function abrirModal(id) {
document.getElementById(id).classList.add('open');
document.body.style.overflow = 'hidden';
}
function cerrarModal(id) {
document.getElementById(id).classList.remove('open');
document.body.style.overflow = '';
}
function setupCartModals() {
// FAB → abrir carrito
document.getElementById('cartFab').addEventListener('click', () => {
renderCarritoModal();
abrirModal('cartModal');
});
// Cerrar carrito
document.querySelectorAll('[data-cart-close]').forEach(el => {
el.addEventListener('click', () => cerrarModal('cartModal'));
});
// Cerrar pedido
document.querySelectorAll('[data-order-close]').forEach(el => {
el.addEventListener('click', () => cerrarModal('orderModal'));
});
// Vaciar carrito
document.getElementById('cartClearBtn').addEventListener('click', () => {
carrito = [];
renderCarritoModal();
actualizarBadge();
});
// Generar PDF → abrir formulario
document.getElementById('cartPdfBtn').addEventListener('click', () => {
cerrarModal('cartModal');
document.getElementById('orderForm').reset();
document.getElementById('orderError').classList.remove('show');
abrirModal('orderModal');
});
// Enviar pedido
document.getElementById('orderSubmitBtn').addEventListener('click', enviarPedido);
// Enter en el formulario también envía
document.getElementById('orderForm').addEventListener('submit', e => {
e.preventDefault();
enviarPedido();
});
}
async function enviarPedido() {
const nombre = document.getElementById('orderNombre').value.trim();
const ciudad = document.getElementById('orderCiudad').value.trim();
const telefono = document.getElementById('orderTelefono').value.trim();
const direccion = document.getElementById('orderDireccion').value.trim();
const errEl = document.getElementById('orderError');
if (!nombre || !ciudad || !telefono || !direccion) {
errEl.textContent = 'Todos los campos son obligatorios';
errEl.classList.add('show');
return;
}
errEl.classList.remove('show');
const btn = document.getElementById('orderSubmitBtn');
const textEl = btn.querySelector('.btn-text');
const spin = btn.querySelector('.btn-spinner');
btn.disabled = true;
textEl.textContent = 'Generando…';
spin.style.display = 'inline-block';
try {
const res = await fetch('/api/pedidos/generar-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: carrito.map(i => ({ ref: i.ref, nombre: i.nombre, precio: i.precio, cantidad: i.cantidad })),
cliente: { nombre, ciudad, telefono, direccion }
})
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || 'Error al generar el pedido');
}
// Descargar PDF
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const dispo = res.headers.get('Content-Disposition') || '';
const match = dispo.match(/filename="(.+)"/);
a.download = match ? match[1] : `pedido-${Date.now()}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
// Limpiar carrito y cerrar modal
carrito = [];
actualizarBadge();
cerrarModal('orderModal');
} catch (err) {
errEl.textContent = err.message;
errEl.classList.add('show');
} finally {
btn.disabled = false;
textEl.textContent = '✅ Generar pedido';
spin.style.display = 'none';
}
}
// Wire add-to-cart buttons después de renderizar productos
function wireAddToCart() {
document.querySelectorAll('.producto-card-add-btn').forEach(btn => {
if (btn.dataset.cartWired) return;
btn.dataset.cartWired = '1';
btn.addEventListener('click', () => {
const ref = btn.dataset.ref;
const nombre = btn.dataset.nombre;
const precio = parseFloat(btn.dataset.precio);
const qtyInput = document.querySelector(`.producto-card-add-qty[data-ref="${ref}"]`);
const cantidad = qtyInput ? parseInt(qtyInput.value, 10) : 1;
if (!cantidad || cantidad < 1) return;
// Mostrar feedback si el producto no tiene precio
if (!precio || isNaN(precio)) {
const orig = btn.textContent;
btn.textContent = '⚠ Sin precio';
btn.style.opacity = '0.7';
setTimeout(() => { btn.textContent = orig; btn.style.opacity = '1'; }, 1500);
return;
}
const existente = carrito.find(i => i.ref === ref);
if (existente) {
existente.cantidad += cantidad;
} else {
carrito.push({ ref, nombre, precio, cantidad });
}
if (qtyInput) qtyInput.value = 1;
actualizarBadge();
const original = btn.textContent;
btn.textContent = '✓ Agregado';
setTimeout(() => { btn.textContent = original; }, 1200);
});
});
}
// ════════════════════════════════
// INIT
// ════════════════════════════════
document.addEventListener('DOMContentLoaded', () => {
setupSearch();
setupNav();
setupCartModals();
cargarDatos();
});