diff --git a/backend/server.js b/backend/server.js
index d827b1a..395e866 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -22,6 +22,7 @@ const SECRET = process.env.SESSION_SECRET || 'infinity-cambia-esta-clave-en-prod
const DATA_DIR = path.join(__dirname, 'data');
const FRONTEND_DIR = path.join(__dirname, 'frontend');
const IMG_DIR = path.join(__dirname, 'Imagenes');
+const PEDIDOS_DIR = path.join(__dirname, 'pedidos');
// ════════════════════════════════
// MIDDLEWARES
@@ -30,6 +31,7 @@ app.use(express.json());
app.use(cookieParser(SECRET));
app.use(express.static(FRONTEND_DIR));
app.use('/Imagenes', express.static(IMG_DIR, { maxAge: '7d' }));
+app.use('/pedidos', express.static(PEDIDOS_DIR));
// Evita el 404 de favicon.ico
app.get('/favicon.ico', (req, res) => res.status(204).end());
@@ -1886,6 +1888,161 @@ app.post('/api/descargas/fotos', requireDescargasAccess, async (req, res) => {
}
});
+// ════════════════════════════════
+// PEDIDOS — generar PDF con pedido
+// ════════════════════════════════
+app.post('/api/pedidos/generar-pdf', async (req, res) => {
+ const { items, cliente } = req.body || {};
+
+ if (!Array.isArray(items) || items.length === 0) {
+ return res.status(400).json({ error: 'El pedido debe tener al menos un producto' });
+ }
+ if (!cliente || !cliente.nombre || !cliente.ciudad || !cliente.telefono || !cliente.direccion) {
+ return res.status(400).json({ error: 'Todos los datos del cliente son obligatorios' });
+ }
+
+ try {
+ const filename = `pedido-${Date.now()}.pdf`;
+ const filepath = path.join(PEDIDOS_DIR, filename);
+
+ // Asegurar que la carpeta existe
+ await fs.mkdir(PEDIDOS_DIR, { recursive: true });
+
+ const doc = new PDFDocument({
+ size: 'A4',
+ margins: { top: 50, bottom: 50, left: 50, right: 50 },
+ info: {
+ Title: `Pedido - ${cliente.nombre}`,
+ Author: 'Infinity',
+ Subject: 'Pedido de productos'
+ }
+ });
+
+ const writeStream = fsSync.createWriteStream(filepath);
+ doc.pipe(writeStream);
+
+ const pageW = doc.page.width;
+ const MARGIN_X = 50;
+ const pageH = doc.page.height;
+
+ // === ENCABEZADO ===
+ doc.fontSize(36).fillColor('#8b5cf6').font('Helvetica-Bold')
+ .text('∞ Infinity', MARGIN_X, 50, { width: pageW - 2 * MARGIN_X });
+
+ doc.moveTo(MARGIN_X, 100).lineTo(pageW - MARGIN_X, 100)
+ .strokeColor('#8b5cf6').lineWidth(1.5).stroke();
+
+ doc.fontSize(22).fillColor('#1a1a2e').font('Helvetica-Bold')
+ .text('Pedido', MARGIN_X, 120, { width: pageW - 2 * MARGIN_X });
+
+ const fechaStr = new Date().toLocaleDateString('es-CO', {
+ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
+ hour: '2-digit', minute: '2-digit'
+ });
+ doc.fontSize(10).fillColor('#8b8ba7').font('Helvetica')
+ .text(fechaStr, MARGIN_X, 150, { width: pageW - 2 * MARGIN_X });
+
+ // === DATOS DEL CLIENTE ===
+ doc.fontSize(14).fillColor('#1a1a2e').font('Helvetica-Bold')
+ .text('Datos del cliente', MARGIN_X, 185, { width: pageW - 2 * MARGIN_X });
+
+ const clientFields = [
+ ['Nombre', cliente.nombre],
+ ['Ciudad', cliente.ciudad],
+ ['Teléfono', cliente.telefono],
+ ['Dirección', cliente.direccion]
+ ];
+
+ let clientY = 210;
+ doc.fontSize(11).fillColor('#4a4a68').font('Helvetica');
+ for (const [label, value] of clientFields) {
+ doc.text(`${label}: `, MARGIN_X, clientY, { continued: true });
+ doc.fillColor('#1a1a2e').text(value, { width: pageW - 2 * MARGIN_X });
+ clientY += 18;
+ }
+
+ // === TABLA DE PRODUCTOS ===
+ let tableY = clientY + 20;
+ doc.moveTo(MARGIN_X, tableY).lineTo(pageW - MARGIN_X, tableY)
+ .strokeColor('#e8e6f0').lineWidth(0.5).stroke();
+
+ tableY += 12;
+ const colRef = MARGIN_X;
+ const colNombre = MARGIN_X + 80;
+ const colCant = pageW - MARGIN_X - 180;
+ const colPrecio = pageW - MARGIN_X - 120;
+ const colSubtotal = pageW - MARGIN_X - 60;
+
+ doc.fontSize(9).fillColor('#8b8ba7').font('Helvetica-Bold');
+ doc.text('REF', colRef, tableY);
+ doc.text('Producto', colNombre, tableY);
+ doc.text('Cant', colCant, tableY, { align: 'center', width: 40 });
+ doc.text('Precio', colPrecio, tableY, { align: 'right', width: 60 });
+ doc.text('Total', colSubtotal, tableY, { align: 'right', width: 60 });
+
+ const headerY = tableY;
+
+ let totalGeneral = 0;
+ for (const item of items) {
+ const subtotal = (item.precio || 0) * (item.cantidad || 1);
+ totalGeneral += subtotal;
+
+ tableY += 22;
+ if (tableY > pageH - 90) {
+ doc.addPage();
+ tableY = 60;
+ }
+
+ doc.fontSize(9).fillColor('#8b8ba7').font('Helvetica')
+ .text(String(item.ref || '—'), colRef, tableY, { width: 76 });
+ doc.fillColor('#1a1a2e').font('Helvetica')
+ .text(String(item.nombre || item.ref || '—'), colNombre, tableY, { width: colCant - colNombre - 10 });
+ doc.fillColor('#1a1a2e').font('Helvetica-Bold')
+ .text(String(item.cantidad || 1), colCant, tableY, { align: 'center', width: 40 });
+ doc.fillColor('#4a4a68').font('Helvetica')
+ .text(`$${(item.precio || 0).toLocaleString('es-CO')}`, colPrecio, tableY, { align: 'right', width: 60 });
+ doc.fillColor('#8b5cf6').font('Helvetica-Bold')
+ .text(`$${subtotal.toLocaleString('es-CO')}`, colSubtotal, tableY, { align: 'right', width: 60 });
+ }
+
+ // Línea final
+ tableY += 20;
+ doc.moveTo(MARGIN_X, tableY).lineTo(pageW - MARGIN_X, tableY)
+ .strokeColor('#e8e6f0').lineWidth(0.5).stroke();
+
+ tableY += 14;
+ doc.fontSize(11).fillColor('#4a4a68').font('Helvetica');
+ doc.text('Total pedido:', MARGIN_X, tableY, { continued: true });
+ doc.fontSize(16).fillColor('#8b5cf6').font('Helvetica-Bold')
+ .text(` $${totalGeneral.toLocaleString('es-CO')}`, { width: pageW - 2 * MARGIN_X });
+
+ // === FOOTER con número de página ===
+ const range = doc.bufferedPageRange();
+ for (let i = range.start; i < range.start + range.count; i++) {
+ doc.switchToPage(i);
+ doc.fontSize(8).fillColor('#8b8ba7').font('Helvetica')
+ .text(`${i + 1} / ${range.count}`,
+ MARGIN_X, pageH - 40, { width: pageW - 2 * MARGIN_X, align: 'center' });
+ }
+
+ doc.end();
+
+ // Esperar a que termine de escribir
+ await new Promise((resolve, reject) => {
+ writeStream.on('finish', resolve);
+ writeStream.on('error', reject);
+ });
+
+ // Enviar el archivo
+ res.setHeader('Content-Type', 'application/pdf');
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
+ res.sendFile(filepath);
+ } catch (err) {
+ console.error('Pedido PDF error:', err);
+ res.status(500).json({ error: 'Error al generar el pedido: ' + err.message });
+ }
+});
+
// ════════════════════════════════
// START
// ════════════════════════════════
diff --git a/data/productos.json b/data/productos.json
index 64ac9f7..d5c8e58 100644
--- a/data/productos.json
+++ b/data/productos.json
@@ -57,7 +57,9 @@
"dama",
"aaa"
],
- "img": "bolsos/dama/aaa/AF100-197.webp"
+ "img": "bolsos/dama/aaa/AF100-197.webp",
+ "nombre": "BOLSO",
+ "precio": 10000
},
{
"id": "AF100-198",
@@ -67,7 +69,9 @@
"dama",
"aaa"
],
- "img": "bolsos/dama/aaa/AF100-198.webp"
+ "img": "bolsos/dama/aaa/AF100-198.webp",
+ "nombre": "BOLSO",
+ "precio": 10000
},
{
"id": "AF100-199",
@@ -77,7 +81,9 @@
"dama",
"aaa"
],
- "img": "bolsos/dama/aaa/AF100-199.webp"
+ "img": "bolsos/dama/aaa/AF100-199.webp",
+ "nombre": "BOLSO",
+ "precio": 10000
},
{
"id": "AF100-201",
@@ -87,7 +93,9 @@
"dama",
"aaa"
],
- "img": "bolsos/dama/aaa/AF100-201.webp"
+ "img": "bolsos/dama/aaa/AF100-201.webp",
+ "nombre": "BOLSO",
+ "precio": 10000
},
{
"id": "AF100-202",
diff --git a/docker-compose.yml b/docker-compose.yml
index 70904ab..98a78aa 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -10,6 +10,8 @@ services:
# Persistencia: los cambios en JSON se guardan en el host
- ./data:/app/data
- ./Imagenes:/app/Imagenes
+ - ./pedidos:/app/pedidos
+ - ./frontend:/app/frontend
environment:
- NODE_ENV=production
- PORT=3000
diff --git a/frontend/app.js b/frontend/app.js
index cfd9baf..69451db 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -275,10 +275,21 @@ function renderProductos() {
${escapeHtml(nombre)}
${desc ? `${escapeHtml(desc)}
` : ''}
${precio}
+
+
+
+
`;
}).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 = 'El carrito está vacío.
';
+ actualizarTotal();
+ return;
+ }
+
+ const totalGeneral = carrito.reduce((s, i) => s + i.precio * i.cantidad, 0);
+
+ body.innerHTML = `
+ ${carrito.map((item, idx) => {
+ const subtotal = item.precio * item.cantidad;
+ return `
+
+
${escapeHtml(item.nombre)}
+
${escapeHtml(item.ref)} — $${(item.precio || 0).toLocaleString('es-CO')} c/u
+
+
+
+ ${item.cantidad}
+
+
+
$${subtotal.toLocaleString('es-CO')}
+
+
`;
+ }).join('')}
+
`;
+
+ 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();
});
+
+
diff --git a/frontend/estilos.css b/frontend/estilos.css
index 83174b2..ef35eca 100644
--- a/frontend/estilos.css
+++ b/frontend/estilos.css
@@ -887,3 +887,394 @@ section {
font-size: 2rem;
}
}
+
+/* ════════════════════════════════
+ CARRITO DE COMPRAS
+ ════════════════════════════════ */
+
+/* Botón flotante del carrito */
+.cart-fab {
+ position: fixed;
+ bottom: 28px;
+ right: 28px;
+ z-index: 900;
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ border: none;
+ background: linear-gradient(135deg, #8b5cf6, #a78bfa);
+ color: #fff;
+ font-size: 1.5rem;
+ cursor: pointer;
+ box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.cart-fab:hover {
+ transform: scale(1.08);
+ box-shadow: 0 6px 28px rgba(139, 92, 246, 0.55);
+}
+
+.cart-fab:active {
+ transform: scale(0.95);
+}
+
+.cart-fab-badge {
+ position: absolute;
+ top: -4px;
+ right: -4px;
+ min-width: 22px;
+ height: 22px;
+ padding: 0 6px;
+ border-radius: 11px;
+ background: #ef4444;
+ color: #fff;
+ font-size: 0.7rem;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: 'Plus Jakarta Sans', sans-serif;
+ box-shadow: 0 2px 6px rgba(239, 68, 68, 0.4);
+}
+
+/* Modal genérico (compartido con carrito y pedido) */
+.modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal.open {
+ display: flex;
+}
+
+.modal-backdrop {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.45);
+ backdrop-filter: blur(4px);
+}
+
+.modal-content {
+ position: relative;
+ background: #fff;
+ border-radius: 16px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
+ max-width: 520px;
+ width: 90vw;
+ max-height: 85vh;
+ display: flex;
+ flex-direction: column;
+ animation: modalIn 0.25s ease-out;
+}
+
+@keyframes modalIn {
+ from { opacity: 0; transform: translateY(20px) scale(0.97); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 24px 0;
+}
+
+.modal-header h2 {
+ font-family: 'DM Serif Display', serif;
+ font-size: 1.4rem;
+ color: #1a1a2e;
+ margin: 0;
+}
+
+.modal-close {
+ background: none;
+ border: none;
+ font-size: 1.3rem;
+ color: #8b8ba7;
+ cursor: pointer;
+ padding: 4px 8px;
+ border-radius: 6px;
+ transition: background 0.15s;
+}
+
+.modal-close:hover {
+ background: #f3f0ff;
+ color: #1a1a2e;
+}
+
+.modal-body {
+ padding: 20px 24px;
+ overflow-y: auto;
+ flex: 1;
+}
+
+.modal-footer {
+ padding: 16px 24px 20px;
+ border-top: 1px solid #e8e6f0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+/* Carrito — lista de items */
+.cart-items {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.cart-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ background: #f8f7fc;
+ border-radius: 10px;
+}
+
+.cart-item-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.cart-item-name {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: #1a1a2e;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cart-item-ref {
+ font-size: 0.72rem;
+ color: #8b8ba7;
+}
+
+.cart-item-controls {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.cart-item-qty-btn {
+ width: 28px;
+ height: 28px;
+ border: 1px solid #d4d0e0;
+ border-radius: 6px;
+ background: #fff;
+ color: #1a1a2e;
+ font-size: 1rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.15s;
+}
+
+.cart-item-qty-btn:hover {
+ background: #f3f0ff;
+}
+
+.cart-item-qty {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #1a1a2e;
+ min-width: 24px;
+ text-align: center;
+}
+
+.cart-item-subtotal {
+ font-size: 0.85rem;
+ font-weight: 700;
+ color: #8b5cf6;
+ min-width: 70px;
+ text-align: right;
+}
+
+.cart-item-remove {
+ background: none;
+ border: none;
+ color: #ef4444;
+ cursor: pointer;
+ font-size: 1rem;
+ padding: 4px;
+ border-radius: 4px;
+ transition: background 0.15s;
+}
+
+.cart-item-remove:hover {
+ background: #fef2f2;
+}
+
+.cart-empty {
+ text-align: center;
+ color: #8b8ba7;
+ padding: 32px 0;
+ font-size: 0.95rem;
+}
+
+.cart-total {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 1.05rem;
+ color: #1a1a2e;
+}
+
+.cart-total strong {
+ font-size: 1.3rem;
+ color: #8b5cf6;
+}
+
+.cart-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+/* Formulario de pedido */
+.order-modal-content {
+ max-width: 440px;
+}
+
+.form-group {
+ margin-bottom: 14px;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #4a4a68;
+ margin-bottom: 4px;
+}
+
+.form-group input {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1.5px solid #d4d0e0;
+ border-radius: 10px;
+ font-size: 0.9rem;
+ font-family: 'Plus Jakarta Sans', sans-serif;
+ color: #1a1a2e;
+ background: #fafaff;
+ transition: border-color 0.2s, box-shadow 0.2s;
+ box-sizing: border-box;
+}
+
+.form-group input:focus {
+ outline: none;
+ border-color: #8b5cf6;
+ box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
+}
+
+.form-error {
+ color: #ef4444;
+ font-size: 0.82rem;
+ display: none;
+ margin-bottom: 8px;
+}
+
+.form-error.show {
+ display: block;
+}
+
+/* Botón "Agregar" en tarjetas de producto */
+.producto-card-add {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #e8e6f0;
+}
+
+.producto-card-add-qty {
+ width: 48px;
+ padding: 6px 8px;
+ border: 1.5px solid #d4d0e0;
+ border-radius: 8px;
+ font-size: 0.82rem;
+ font-weight: 600;
+ text-align: center;
+ font-family: 'Plus Jakarta Sans', sans-serif;
+ color: #1a1a2e;
+ background: #fff;
+ transition: border-color 0.2s;
+ box-sizing: border-box;
+}
+
+.producto-card-add-qty:focus {
+ outline: none;
+ border-color: #8b5cf6;
+}
+
+.producto-card-add-btn {
+ flex: 1;
+ padding: 7px 12px;
+ border: none;
+ border-radius: 8px;
+ background: linear-gradient(135deg, #8b5cf6, #a78bfa);
+ color: #fff;
+ font-size: 0.78rem;
+ font-weight: 600;
+ font-family: 'Plus Jakarta Sans', sans-serif;
+ cursor: pointer;
+ transition: opacity 0.2s, transform 0.15s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+}
+
+.producto-card-add-btn:hover {
+ opacity: 0.9;
+ transform: translateY(-1px);
+}
+
+.producto-card-add-btn:active {
+ transform: scale(0.97);
+}
+
+/* Spinner en botones */
+.btn-spinner {
+ margin-left: 6px;
+}
+
+/* Responsive carrito */
+@media (max-width: 480px) {
+ .cart-fab {
+ width: 52px;
+ height: 52px;
+ bottom: 20px;
+ right: 20px;
+ font-size: 1.3rem;
+ }
+
+ .modal-content {
+ width: 95vw;
+ border-radius: 12px;
+ }
+
+ .cart-item {
+ flex-wrap: wrap;
+ }
+
+ .cart-actions {
+ flex-direction: column;
+ }
+
+ .cart-actions .btn {
+ width: 100%;
+ justify-content: center;
+ }
+}
diff --git a/frontend/index.html b/frontend/index.html
index bb3a3b1..8efb504 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -9,7 +9,7 @@
-
+
@@ -102,6 +102,82 @@
-
+
+
+
+
+
+
+
+
+
+
El carrito está vacío.
+
+
+
+
+
+
+
+
+