diff --git a/src/services/print-service.js b/src/services/print-service.js new file mode 100644 index 0000000..78ced30 --- /dev/null +++ b/src/services/print-service.js @@ -0,0 +1,28 @@ +const net = require('net'); + +function printZpl(zplData, printerIp, printerPort = 9100) { + return new Promise((resolve, reject) => { + const client = new net.Socket(); + + client.connect(printerPort, printerIp, () => { + client.write(zplData, () => { + client.end(); + }); + }); + + client.on('close', () => { + resolve({ success: true }); + }); + + client.on('error', (err) => { + reject(new Error(`Print error: ${err.message}`)); + }); + + client.setTimeout(10000, () => { + client.destroy(); + reject(new Error('Print timeout: could not connect to printer')); + }); + }); +} + +module.exports = { printZpl }; diff --git a/src/services/zpl-generator.js b/src/services/zpl-generator.js new file mode 100644 index 0000000..fa579b4 --- /dev/null +++ b/src/services/zpl-generator.js @@ -0,0 +1,19 @@ +function generateLabel(article, quantity = 1) { + const desc = (article.descripcion || '').substring(0, 40); + const intercambio = article.intercambio || 'S/N'; + const code = article.noIdentificacion || ''; + + const singleLabel = [ + '^XA', + `^FO30,20^A0N,28,28^FD${desc}^FS`, + `^FO30,60^A0N,22,22^FDInt: ${intercambio}^FS`, + `^FO30,95^BY2^BCN,50,Y,N,N^FD${code}^FS`, + '^XZ', + ].join('\n'); + + if (quantity <= 1) return singleLabel; + + return Array(quantity).fill(singleLabel).join('\n'); +} + +module.exports = { generateLabel }; diff --git a/tests/zpl-generator.test.js b/tests/zpl-generator.test.js new file mode 100644 index 0000000..63b4ed1 --- /dev/null +++ b/tests/zpl-generator.test.js @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { generateLabel } from '../src/services/zpl-generator.js'; + +describe('ZPL Generator', () => { + it('should generate valid ZPL with name, intercambio, and barcode', () => { + const zpl = generateLabel({ + descripcion: 'Balata ceramica delantera', + intercambio: 'INT-9999', + noIdentificacion: 'BK-001', + }); + + expect(zpl).toContain('^XA'); + expect(zpl).toContain('^XZ'); + expect(zpl).toContain('Balata ceramica delantera'); + expect(zpl).toContain('INT-9999'); + expect(zpl).toContain('BK-001'); + }); + + it('should handle missing intercambio', () => { + const zpl = generateLabel({ + descripcion: 'Filtro de aceite', + intercambio: null, + noIdentificacion: 'FT-200', + }); + + expect(zpl).toContain('Filtro de aceite'); + expect(zpl).toContain('S/N'); + expect(zpl).toContain('FT-200'); + }); + + it('should truncate long descriptions', () => { + const zpl = generateLabel({ + descripcion: 'Este es un nombre de articulo extremadamente largo que no cabe en la etiqueta', + intercambio: 'X-1', + noIdentificacion: 'LONG-001', + }); + + expect(zpl).toContain('^XA'); + expect(zpl).toContain('^XZ'); + }); + + it('should generate multiple labels for quantity > 1', () => { + const zpl = generateLabel({ + descripcion: 'Balata', + intercambio: 'INT-1', + noIdentificacion: 'BK-001', + }, 3); + + const labelCount = (zpl.match(/\^XA/g) || []).length; + expect(labelCount).toBe(3); + }); +});