From 4dfeeeea20f0c16f3c740efa5838103cf1d83a8a Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Tue, 3 Feb 2026 21:04:08 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20funcionalidad=20para=20imprim?= =?UTF-8?q?ir=20im=C3=A1genes=20en=20base64=20y=20mejorar=20la=20impresi?= =?UTF-8?q?=C3=B3n=20de=20tickets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/printService.js | 40 ++++++++++++++ src/services/ticketService.js | 99 +++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 15 deletions(-) diff --git a/src/services/printService.js b/src/services/printService.js index 500d994..46c7b22 100644 --- a/src/services/printService.js +++ b/src/services/printService.js @@ -341,6 +341,46 @@ class PrintService { } }); } + + /** + * Imprimir imagen base64 + * @param {string} base64Image - Imagen en base64 (png, jpg) + * @param {string} printerName - Nombre de la impresora (opcional) + * @returns {Promise} + */ + async printImage(base64Image, printerName = null) { + try { + await this.connect(); + + const printer = printerName || this.getSavedPrinter() || await this.getDefaultPrinter(); + + const config = this.qz.configs.create(printer, { + scaleContent: true + }); + + // Asegurar data URI + let imgData = base64Image; + if (!imgData.startsWith('data:image')) { + imgData = `data:image/png;base64,${base64Image}`; + } + + const data = [ + { + type: 'pixel', + format: 'image', + flavor: 'base64', + data: imgData + } + ]; + + await this.qz.print(config, data); + console.log('Imagen impresa exitosamente'); + return true; + } catch (error) { + console.error('Error imprimiendo imagen:', error); + throw error; + } + } } // Exportar instancia singleton diff --git a/src/services/ticketService.js b/src/services/ticketService.js index ace083c..e18a399 100644 --- a/src/services/ticketService.js +++ b/src/services/ticketService.js @@ -3,6 +3,30 @@ import QRCode from 'qrcode'; import { formatMoney, PAYMENT_METHODS } from '@/utils/formatters'; import printService from '@Services/printService'; +/** + * Cargar PDF.js dinámicamente desde CDN + */ +async function loadPdfJs() { + if (window.pdfjsLib) return window.pdfjsLib; + + console.log('Cargando PDF.js desde CDN...'); + + // Cargar script principal + await new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; + script.integrity = 'sha512-q+GRHzvH4z5fXN5WjM8oKGTf4PxcX1QzGjZfrFqVBqAdgEecT0kFvvn7uZ2+GL3LMDM9M79cfsREi+T17J9wMA=='; + script.crossOrigin = 'anonymous'; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + + // Configurar worker + window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; + return window.pdfjsLib; +} + /** * Servicio para generar tickets de venta en formato PDF */ @@ -367,25 +391,70 @@ const ticketService = { // Imprimir automáticamente si se solicita if (autoPrint) { try { - // Convertir PDF a base64 data URI - const pdfBase64 = doc.output('datauristring'); - - // Intentar imprimir con QZ Tray + // Intentar convertir PDF a Imagen y usar QZ Tray (Mejor compatibilidad con térmicas) try { - await printService.printPDF(pdfBase64, printerName); - console.log('Ticket enviado a la impresora con QZ Tray'); - } catch (qzError) { - console.warn('QZ Tray falló, usando diálogo de impresión:', qzError.message); + // Obtener Blob del PDF + const pdfBlob = doc.output('blob'); + const pdfUrl = URL.createObjectURL(pdfBlob); + + // Cargar librería PDF.js + const pdfjs = await loadPdfJs(); + + // Cargar documento + const pdf = await pdfjs.getDocument(pdfUrl).promise; + const page = await pdf.getPage(1); // Página 1 + + // Configurar escala (2.0 da buena nitidez para tickets) + const scale = 2.0; + const viewport = page.getViewport({ scale }); + + // Crear canvas temporal + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + + // Renderizar PDF en Canvas + await page.render({ + canvasContext: context, + viewport: viewport + }).promise; + + // Convertir Canvas a Imagen Base64 + const imgData = canvas.toDataURL('image/png'); + + // Enviar imagen a QZ Tray + await printService.printImage(imgData, printerName); + + console.log('Ticket impreso como imagen con QZ Tray'); + + // Limpieza + URL.revokeObjectURL(pdfUrl); + canvas.remove(); - // Fallback: usar window.print() - await printService.printPDFWithDialog(pdfBase64); - window.Notify.info('Se abrió el diálogo de impresión'); + } catch (imgError) { + console.warn('Falló impresión como imagen, intentando método nativo PDF:', imgError); + + // Fallback: Intentar mandar el PDF directo (puede fallar en térmicas) o usar diálogo + // Convertir PDF a base64 data URI + const pdfBase64 = doc.output('datauristring'); + + // Intentar QZ Tray Direct PDF + try { + await printService.printPDF(pdfBase64, printerName); + } catch (qzError) { + console.warn('QZ Tray PDF falló, abriendo diálogo:', qzError); + await printService.printPDFWithDialog(pdfBase64); + } } } catch (error) { - console.error('Error imprimiendo ticket:', error); - - // Mostrar error al usuario - window.Notify.warning('No se pudo imprimir automáticamente. Usa el PDF descargado.'); + console.error('Error general imprimiendo ticket:', error); + + // Fallback final + const pdfBase64 = doc.output('datauristring'); + await printService.printPDFWithDialog(pdfBase64); + + window.Notify.warning('Hubo un problema con la impresión automática. Se abrió el diálogo del sistema.'); } }