PDF PEDIDOS
This commit is contained in:
@@ -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
|
||||
// ════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user