618 lines
22 KiB
JavaScript
618 lines
22 KiB
JavaScript
// Node version: 16.20.2
|
|
// Package json dependencies
|
|
|
|
/*
|
|
"dependencies": {
|
|
"@thiagoelg/node-printer": "^0.6.2",
|
|
"cors": "^2.8.5",
|
|
"express": "^5.1.0",
|
|
"iconv": "^3.0.1",
|
|
"nodemon": "^3.1.10"
|
|
}
|
|
*/
|
|
|
|
// || nvm -> nvm install 16.20.2
|
|
|
|
|
|
import express from 'express'
|
|
import cors from 'cors'
|
|
import printer from '@thiagoelg/node-printer'
|
|
import iconv from "iconv-lite";
|
|
import QRCode from 'qrcode'
|
|
import { createCanvas, loadImage } from 'canvas'
|
|
// Genera un QR y lo imprime en la impresora térmica
|
|
|
|
const ESC = "\x1B"
|
|
const GS = "\x1D"
|
|
const defaultPrinter = printer.getDefaultPrinterName()
|
|
|
|
function init() { return ESC + "@"; }
|
|
function cut() { return GS + "V" + "\x41" + "\x00"; }
|
|
function line(n = 1) { return "\n".repeat(n); }
|
|
function boldOn() { return ESC + "E" + "\x01"; }
|
|
function boldOff() { return ESC + "E" + "\x00"; }
|
|
function center() { return ESC + "a" + "\x01"; }
|
|
function centerOff() { return ESC + "a" + "\x00"; }
|
|
function left() { return ESC + "a" + "\x00"; }
|
|
function right() { return ESC + "a" + "\x02"; }
|
|
function textNormal() {
|
|
return "\x1D\x21\x00"; // GS ! n → normal
|
|
}
|
|
function textDoubleHeight() {
|
|
return "\x1D\x21\x10"; // GS ! 16 → doble alto
|
|
}
|
|
function textDoubleWidth() {
|
|
return "\x1D\x21\x01"; // GS ! 1 → doble ancho
|
|
}
|
|
function textDoubleSize() {
|
|
return "\x1D\x21\x11"; // GS ! 17 → doble ancho y alto
|
|
}
|
|
function textTripleSize() {
|
|
return "\x1D\x21\x22"; // GS ! 34 → triple (si la impresora lo soporta)
|
|
}
|
|
|
|
function textQuadSize() {
|
|
return "\x1D\x21\x33"; // GS ! 34 → triple (si la impresora lo soporta)
|
|
}
|
|
|
|
function textFifthSize() {
|
|
return "\x1D\x21\x44"; // GS ! 34 → triple (si la impresora lo soporta)
|
|
}
|
|
|
|
|
|
const app = express()
|
|
app.use(cors())
|
|
app.use(express.json())
|
|
|
|
const max = 30;
|
|
|
|
app.get('/printers', (req, res) => {
|
|
res.send(200)
|
|
})
|
|
|
|
app.post('/digitalpowerstock/ticket/venta', (req, res) => {
|
|
const { nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, vendedor, productos, total, adelanto } = req.body;
|
|
|
|
ticketDPSTock(nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, vendedor, productos, total, adelanto)
|
|
res.send(200)
|
|
})
|
|
|
|
app.post('/digitalpowerstock/ticket/gondola', (req, res) => {
|
|
const { nombre, top_price, off, bottom_price } = req.body;
|
|
|
|
ticketDPSTockGondola(nombre, top_price, off, bottom_price)
|
|
res.send(200)
|
|
})
|
|
|
|
app.post('/facturador/ticket/a', (req, res) => {
|
|
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal } = req.body;
|
|
ticketFacturadorA(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal)
|
|
res.send(200)
|
|
})
|
|
|
|
app.post('/facturador/ticket/b', (req, res) => {
|
|
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
|
|
ticketFacturadorB(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
|
)
|
|
res.send(200)
|
|
})
|
|
|
|
app.post('/facturador/ticket/c', (req, res) => {
|
|
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
|
|
ticketFacturadorC(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
|
)
|
|
res.send(200)
|
|
})
|
|
|
|
async function printQRAsImage(link, moduleSize = 6) {
|
|
try {
|
|
const qrSize = moduleSize * 33;
|
|
|
|
const qrBuffer = await QRCode.toBuffer(link, {
|
|
width: qrSize,
|
|
margin: 2,
|
|
errorCorrectionLevel: 'M',
|
|
type: 'png'
|
|
});
|
|
|
|
const img = await loadImage(qrBuffer);
|
|
const canvas = createCanvas(img.width, img.height);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
const widthBytes = Math.ceil(width / 8);
|
|
|
|
const imageBytes = [];
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < widthBytes; x++) {
|
|
let byte = 0;
|
|
for (let bit = 0; bit < 8; bit++) {
|
|
const pixelX = x * 8 + bit;
|
|
if (pixelX < width) {
|
|
const idx = (y * width + pixelX) * 4;
|
|
const brightness = imageData.data[idx];
|
|
if (brightness < 128) {
|
|
byte |= (1 << (7 - bit));
|
|
}
|
|
}
|
|
}
|
|
imageBytes.push(byte);
|
|
}
|
|
}
|
|
|
|
const commands = [];
|
|
|
|
// Centrar
|
|
commands.push(Buffer.from([0x1B, 0x61, 0x01]));
|
|
|
|
// GS v 0
|
|
const xL = widthBytes & 0xFF;
|
|
const xH = (widthBytes >> 8) & 0xFF;
|
|
const yL = height & 0xFF;
|
|
const yH = (height >> 8) & 0xFF;
|
|
|
|
commands.push(Buffer.from([0x1D, 0x76, 0x30, 0x00, xL, xH, yL, yH]));
|
|
commands.push(Buffer.from(imageBytes));
|
|
|
|
commands.push(Buffer.from([0x0A, 0x0A]));
|
|
commands.push(Buffer.from([0x1B, 0x61, 0x00]));
|
|
|
|
return Buffer.concat(commands);
|
|
|
|
} catch (error) {
|
|
console.error('Error generando QR:', error);
|
|
return Buffer.from('');
|
|
}
|
|
}
|
|
|
|
export function removeAccents(str) {
|
|
return String(str)
|
|
.normalize('NFD') // descompone letras + diacríticos
|
|
.replace(/[\u0300-\u036f]/g, ''); // elimina marcas diacríticas
|
|
}
|
|
|
|
function printDoubleLine() {
|
|
return left() + "============================================" + line();
|
|
}
|
|
|
|
function printDottedLine() {
|
|
return left() + "--------------------------------------------" + line();
|
|
}
|
|
|
|
function printProducts(products) {
|
|
let ticket = "";
|
|
const lineWidth = 46;
|
|
const priceWidth = 10;
|
|
|
|
for (const producto of products) {
|
|
const nombre = removeAccents(producto?.nombre);
|
|
const cantidad = producto?.cantidad;
|
|
const precio = `$${producto?.precio}`;
|
|
|
|
// Construir la parte izquierda: "2 x Producto"
|
|
const prefix = `${cantidad} x `;
|
|
const leftPart = `${prefix}${nombre}`;
|
|
|
|
// Calcular espacio disponible para el texto completo (sin considerar el precio aún)
|
|
const availableSpace = lineWidth;
|
|
|
|
if (leftPart.length <= lineWidth - precio.length - 1) {
|
|
// Si cabe todo en una línea (con el precio)
|
|
const spaces = lineWidth - leftPart.length - precio.length;
|
|
const spacePadding = " ".repeat(Math.max(0, spaces));
|
|
ticket += left() + leftPart + spacePadding + precio + line();
|
|
} else {
|
|
// Dividir el texto en líneas
|
|
const lines = [];
|
|
let remainingText = leftPart;
|
|
const indentation = " ".repeat(prefix.length);
|
|
|
|
// Primera línea sin indentación
|
|
lines.push(remainingText.substring(0, lineWidth));
|
|
remainingText = remainingText.substring(lineWidth);
|
|
|
|
// Líneas intermedias con indentación
|
|
while (remainingText.length > lineWidth - indentation.length - precio.length - 1) {
|
|
const lineText = indentation + remainingText.substring(0, lineWidth - indentation.length);
|
|
lines.push(lineText);
|
|
remainingText = remainingText.substring(lineWidth - indentation.length);
|
|
}
|
|
|
|
// Última línea con el precio
|
|
const lastLineText = indentation + remainingText;
|
|
const spaces = lineWidth - lastLineText.length - precio.length;
|
|
const spacePadding = " ".repeat(Math.max(0, spaces));
|
|
lines.push(lastLineText + spacePadding + precio);
|
|
|
|
// Agregar todas las líneas al ticket
|
|
for (const _line of lines) {
|
|
ticket += left() + _line + line();
|
|
}
|
|
}
|
|
|
|
if (Number(producto.descuento) > 0) {
|
|
ticket += left() + `${producto.descuento}% OFF` + line();
|
|
}
|
|
|
|
if (producto?.observaciones?.length > 0) {
|
|
const obs = removeAccents(producto.observaciones);
|
|
ticket += "Observaciones: " + obs + line();
|
|
}
|
|
ticket += printDottedLine();
|
|
}
|
|
|
|
return ticket;
|
|
}
|
|
|
|
function printBetween(leftText, rightText, lineWidth = 46) {
|
|
// Asegurar que los valores sean strings
|
|
const left = String(leftText);
|
|
const right = String(rightText);
|
|
|
|
// Calcular el espacio disponible para el texto izquierdo
|
|
const availableSpace = lineWidth - right.length;
|
|
|
|
// Si el texto izquierdo es muy largo, cortarlo
|
|
let finalLeft = left;
|
|
if (left.length > availableSpace) {
|
|
finalLeft = left.substring(0, availableSpace - 3) + "...";
|
|
}
|
|
|
|
// Calcular espacios de relleno
|
|
const spaces = lineWidth - finalLeft.length - right.length;
|
|
const spacePadding = " ".repeat(Math.max(0, spaces));
|
|
|
|
// Retornar la línea formateada
|
|
return finalLeft + spacePadding + right;
|
|
}
|
|
|
|
async function ticketDPSTock(
|
|
nombre, direccion, cp, localidad, provincia, pais,
|
|
telefono, mensaje, comprobante, cliente, fecha,
|
|
vendedor, productos, total, adelanto
|
|
) {
|
|
|
|
try {
|
|
const products = JSON.parse(productos);
|
|
//const products = productos;
|
|
let ticket = "";
|
|
|
|
// Encabezado
|
|
ticket += init();
|
|
ticket += center();
|
|
ticket += boldOn() + nombre + boldOff() + line();
|
|
ticket += direccion + line();
|
|
ticket += `(${cp}) ${localidad}` + line();
|
|
ticket += `${provincia} ${pais}` + line();
|
|
ticket += telefono + line();
|
|
ticket += mensaje + line(3);
|
|
|
|
// Datos comprobante
|
|
ticket += left();
|
|
ticket += `Nro comprobante: #${comprobante}` + line();
|
|
ticket += `Cliente: ${cliente}` + line();
|
|
ticket += `Fecha: ${fecha}` + line();
|
|
ticket += `Vendedor: ${vendedor}` + line(2);
|
|
|
|
ticket += printProducts(JSON.parse(productos))
|
|
ticket += line();
|
|
|
|
// Total
|
|
ticket += line(2);
|
|
//ticket += left() + `Total: $${total}` + line(3);
|
|
ticket += boldOn();
|
|
ticket += printBetween("Total: ", `$${total}`) + line(3);
|
|
if (adelanto) {
|
|
ticket += printBetween("Abonado: ", `$${adelanto}`) + line(3);
|
|
}
|
|
ticket += boldOff();
|
|
|
|
// Corte
|
|
ticket += cut();
|
|
|
|
// Convertir a CP850 (acentos correctos)
|
|
const data = iconv.encode(ticket, "CP850");
|
|
|
|
// Enviar a la impresora
|
|
printer.printDirect({
|
|
data,
|
|
printer: printer.getDefaultPrinterName(),
|
|
type: "RAW",
|
|
success: jobID => console.log("Trabajo enviado:", jobID),
|
|
error: err => console.error("Error:", err)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Error al imprimir:", error);
|
|
}
|
|
}
|
|
|
|
async function ticketDPSTockGondola(
|
|
nombre, top_price, off, bottom_price
|
|
) {
|
|
|
|
try {
|
|
//const products = productos;
|
|
let ticket = "";
|
|
|
|
// Encabezado
|
|
ticket += init();
|
|
ticket += left();
|
|
ticket += textDoubleSize() + nombre + textNormal() + line(1);
|
|
ticket += textQuadSize() + boldOn() + "$" + top_price + textNormal() + line();
|
|
ticket += textTripleSize() + "x unidad" + textNormal() + boldOff() + line();
|
|
|
|
if (off) {
|
|
ticket += textDoubleSize() + off + "% OFF" + line();
|
|
}
|
|
|
|
if (bottom_price && !off) {
|
|
ticket += textDoubleSize() + "$" + bottom_price + line();
|
|
}
|
|
|
|
|
|
ticket += line(2);
|
|
// Corte
|
|
ticket += cut();
|
|
|
|
// Convertir a CP850 (acentos correctos)
|
|
const data = iconv.encode(ticket, "CP850");
|
|
|
|
// Enviar a la impresora
|
|
printer.printDirect({
|
|
data,
|
|
printer: printer.getDefaultPrinterName(),
|
|
type: "RAW",
|
|
success: jobID => console.log("Trabajo enviado:", jobID),
|
|
error: err => console.error("Error:", err)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Error al imprimir:", error);
|
|
}
|
|
}
|
|
|
|
async function ticketFacturadorA(
|
|
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal
|
|
) {
|
|
|
|
try {
|
|
let ticket = "";
|
|
|
|
// Encabezado
|
|
ticket += init();
|
|
ticket += center();
|
|
ticket += boldOn() + comercio + boldOff() + line(2);
|
|
ticket += centerOff();
|
|
|
|
ticket += nombre + line();
|
|
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
|
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
|
ticket += "Direccion: " + direccion + line();
|
|
ticket += `${localidad} (${provincia})` + line();
|
|
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
|
ticket += condicion_iva_comercio + line();
|
|
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TICKET / FACTURA A" + line();
|
|
ticket += "Nro: " + comprobante + line();
|
|
ticket += "Fecha: " + fecha + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
|
ticket += "CLIENTE: " + cliente + line();
|
|
ticket += "CUIT: " + cliente_cuit + line();
|
|
ticket += "DIRECCION: " + cliente_direccion + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += printProducts(JSON.parse(productos)) + line()
|
|
|
|
// Total
|
|
ticket += printBetween("SUBTOTAL. IMP. NETO GRAVADO: ", `$${subtotal}`) + line();
|
|
ticket += printBetween("IVA: ", `$${iva_contenido}`) + line();
|
|
ticket += boldOn();
|
|
ticket += printBetween("Total: ", `$${total}`) + line(2);
|
|
ticket += boldOff();
|
|
|
|
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
|
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
|
ticket += "CAE: " + cae + line()
|
|
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
|
|
|
ticket += line(2) + center()
|
|
|
|
// Convertir texto a CP850 ANTES del QR
|
|
const textData = iconv.encode(ticket, "CP850");
|
|
|
|
// Generar QR como Buffer binario
|
|
const qr = (await printQRAsImage(qr_link, 12));
|
|
|
|
// Agregar saltos y corte después del QR
|
|
const footer = iconv.encode(line(1) + cut(), "CP850");
|
|
|
|
// Combinar todo
|
|
const data = Buffer.concat([textData, qr, footer]);
|
|
|
|
// Enviar a la impresora
|
|
printer.printDirect({
|
|
data,
|
|
printer: printer.getDefaultPrinterName(),
|
|
type: "RAW",
|
|
success: jobID => console.log("Trabajo enviado:", jobID),
|
|
error: err => console.error("Error:", err)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Error al imprimir:", error);
|
|
}
|
|
}
|
|
|
|
async function ticketFacturadorB(
|
|
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
|
) {
|
|
|
|
try {
|
|
let ticket = "";
|
|
|
|
// Encabezado
|
|
ticket += init();
|
|
ticket += center();
|
|
ticket += boldOn() + comercio + boldOff() + line(2);
|
|
ticket += centerOff();
|
|
|
|
ticket += nombre + line();
|
|
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
|
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
|
ticket += "Direccion: " + direccion + line();
|
|
ticket += `${localidad} (${provincia})` + line();
|
|
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
|
ticket += condicion_iva_comercio + line();
|
|
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TICKET / FACTURA B" + line();
|
|
ticket += "Nro: " + comprobante + line();
|
|
ticket += "Fecha: " + fecha + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
|
ticket += "CLIENTE: " + cliente + line();
|
|
ticket += "CUIT: " + cliente_cuit + line();
|
|
ticket += "DIRECCION: " + cliente_direccion + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += printProducts(JSON.parse(productos)) + line()
|
|
|
|
// Total
|
|
ticket += boldOn();
|
|
ticket += printBetween("Total: ", `$${total}`) + line(2);
|
|
ticket += boldOff();
|
|
|
|
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
|
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
|
ticket += "IVA CONTENIDO: $" + iva_contenido + line()
|
|
ticket += "CAE: " + cae + line()
|
|
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
|
|
|
ticket += line(2) + center()
|
|
|
|
// Convertir texto a CP850 ANTES del QR
|
|
const textData = iconv.encode(ticket, "CP850");
|
|
|
|
// Generar QR como Buffer binario
|
|
const qr = (await printQRAsImage(qr_link, 12));
|
|
|
|
// Agregar saltos y corte después del QR
|
|
const footer = iconv.encode(line(1) + cut(), "CP850");
|
|
|
|
// Combinar todo
|
|
const data = Buffer.concat([textData, qr, footer]);
|
|
|
|
// Enviar a la impresora
|
|
printer.printDirect({
|
|
data,
|
|
printer: printer.getDefaultPrinterName(),
|
|
type: "RAW",
|
|
success: jobID => console.log("Trabajo enviado:", jobID),
|
|
error: err => console.error("Error:", err)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Error al imprimir:", error);
|
|
}
|
|
}
|
|
|
|
async function ticketFacturadorC(
|
|
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
|
) {
|
|
|
|
try {
|
|
let ticket = "";
|
|
|
|
// Encabezado
|
|
ticket += init();
|
|
ticket += center();
|
|
ticket += boldOn() + comercio + boldOff() + line(2);
|
|
ticket += centerOff();
|
|
|
|
ticket += nombre + line();
|
|
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
|
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
|
ticket += "Direccion: " + direccion + line();
|
|
ticket += `${localidad} (${provincia})` + line();
|
|
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
|
ticket += condicion_iva_comercio + line();
|
|
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TICKET / FACTURA C" + line();
|
|
ticket += "Nro: " + comprobante + line();
|
|
ticket += "Fecha: " + fecha + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
|
ticket += "CLIENTE: " + cliente + line();
|
|
ticket += "CUIT: " + cliente_cuit + line();
|
|
ticket += "DIRECCION: " + cliente_direccion + line();
|
|
|
|
ticket += printDoubleLine()
|
|
|
|
ticket += printProducts(JSON.parse(productos)) + line()
|
|
|
|
// Total
|
|
|
|
ticket += boldOn();
|
|
ticket += printBetween("Total: ", `$${total}`);
|
|
ticket += boldOff();
|
|
ticket += line(2);
|
|
|
|
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
|
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
|
ticket += "CAE: " + cae + line()
|
|
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
|
|
|
ticket += line(2) + center()
|
|
|
|
// Convertir texto a CP850 ANTES del QR
|
|
const textData = iconv.encode(ticket, "CP850");
|
|
|
|
// Generar QR como Buffer binario
|
|
const qr = (await printQRAsImage(qr_link, 12));
|
|
|
|
// Agregar saltos y corte después del QR
|
|
const footer = iconv.encode(line(1) + cut(), "CP850");
|
|
|
|
// Combinar todo
|
|
const data = Buffer.concat([textData, qr, footer]);
|
|
|
|
// Enviar a la impresora
|
|
printer.printDirect({
|
|
data,
|
|
printer: printer.getDefaultPrinterName(),
|
|
type: "RAW",
|
|
success: jobID => console.log("Trabajo enviado:", jobID),
|
|
error: err => console.error("Error:", err)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Error al imprimir:", error);
|
|
}
|
|
}
|
|
|
|
app.listen(3030, () => {
|
|
console.log("Servicio de impresion de tickets inicializado en el puerto 3030");
|
|
// testQROnly();
|
|
}) |