Add ZPL label generator and print service with tests

Implement ZPL generator for creating Zebra printer labels from article
data (description, intercambio, barcode) with support for multiple
copies and description truncation. Add TCP-based print service for
sending ZPL data to network printers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-17 06:30:48 +00:00
parent ab71989440
commit 5da79cc3ec
3 changed files with 99 additions and 0 deletions

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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);
});
});