PDF PEDIDOS
This commit is contained in:
+256
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user