Datos formateados como tabla en ticket en impresora térmica

JavaScript: tabular datos con límite de longitud, separador y relleno

El día de hoy veremos un algoritmo que he querido desarrollar desde hace mucho y que no fue tan complejo como creí. Se trata de una forma de crear una tabla con JavaScript y que los datos se acomoden automáticamente en cada columna para que no superen una longitud predefinida.

Datos formateados como tabla en ticket en impresora térmica
Datos formateados como tabla en ticket en impresora térmica

De este modo podremos crear tablas (en forma de texto) en donde el contenido se va a ajustar automáticamente, especificando un separador y una longitud por columna.

Este algoritmo sirve para varias cosas, entre ellas la de imprimir datos tabulados en una impresora térmica ya que los tickets de las impresoras térmicas son un poco reducidos.

Explicación del algoritmo

Con lo único que trabajamos es con cadenas. Debemos cortar o dividir estas cadenas a una máxima longitud. Si la cadena supera cierta longitud entonces debemos pasarla a una nueva línea.

Estamos trabajando con columnas, así que debemos limitar la cadena por cada columna que tengamos. Luego debemos calcular cuál de las columnas tiene la mayor cantidad de filas para ajustar las demás a la máxima “altura”.

Finalmente solo imprimimos y usamos un separador de filas y columnas opcional. Así es como podremos imprimir tablas alineadas en la consola con JS.

Separar cadena en arreglo si supera cierta longitud

Entonces la primera función que veremos es la que divide una cadena en pedazos limitados a una máxima longitud que ya expliqué detenidamente en otro post:

JavaScript: separar cadena en bloques

El código queda así:

const separarCadenaEnArregloSiSuperaLongitud = (cadena, maximaLongitud) => {
    const resultado = [];
    let indice = 0;
    while (indice < cadena.length) {
        const pedazo = cadena.substring(indice, indice + maximaLongitud);
        indice += maximaLongitud;
        resultado.push(pedazo);
    }
    return resultado;
}

Está un poco modificado porque en lugar de concatenar la cadena a un resultado lo que estoy haciendo es agregar la substring al arreglo.

Dividir cadena y contar saltos de línea

La siguiente función se encarga de invocar a la que separa la cadena en arreglos y además realiza un conteo para saber cuál de las columnas tiene la mayor cantidad de líneas.

const dividirCadenasYEncontrarMayorConteoDeBloques = (contenidosConMaximaLongitud) => {
    let mayorConteoDeCadenasSeparadas = 0;
    const cadenasSeparadas = [];
    for (const contenido of contenidosConMaximaLongitud) {
        const separadas = separarCadenaEnArregloSiSuperaLongitud(contenido.contenido, contenido.maximaLongitud);
        cadenasSeparadas.push({ separadas, maximaLongitud: contenido.maximaLongitud });
        if (separadas.length > mayorConteoDeCadenasSeparadas) {
            mayorConteoDeCadenasSeparadas = separadas.length;
        }
    }
    return [cadenasSeparadas, mayorConteoDeCadenasSeparadas];
}

Es importante notar que el parámetro que recibe la función es un arreglo de objetos (un objeto por cada columna deseada) en donde cada objeto debe tener la propiedad contenido (que es la cadena que vamos a dividir) y maximaLongitud que se refiere como bien lo dice a la máxima longitud que esa cadena podrá tener.

Tabular datos

Finalmente llegamos a la función que permite hacer tablas de texto con JavaScript en el navegador y servidor. Este método usa los dos descritos anteriormente y recibe en orden:

  • cadenas: un arreglo de objetos. Cada objeto representa una columna de la tabla y debe tener la propiedad contenido y maximaLongitud
  • relleno: el carácter con el que se rellenará la celda cuando la longitud de la cadena sea menor que la máxima longitud (recuerda que al final la tabla estará alineada por eso se necesita rellenar)
  • separadorColumnas: el separador de columnas que puede ser un pipe (|), un espacio o lo que tú prefieras.

Ahora veamos la función:

const tabularDatos = (cadenas, relleno, separadorColumnas) => {
    const [arreglosDeContenidosConMaximaLongitudSeparadas, mayorConteoDeBloques] = dividirCadenasYEncontrarMayorConteoDeBloques(cadenas)
    let indice = 0;
    const lineas = [];
    while (indice < mayorConteoDeBloques) {
        let linea = "";
        for (const contenidos of arreglosDeContenidosConMaximaLongitudSeparadas) {
            let cadena = "";
            if (indice < contenidos.separadas.length) {
                cadena = contenidos.separadas[indice];
            }
            if (cadena.length < contenidos.maximaLongitud) {
                cadena = cadena + relleno.repeat(contenidos.maximaLongitud - cadena.length);
            }
            linea += cadena + separadorColumnas;
        }
        lineas.push(linea);
        indice++;
    }
    return lineas;
}

Modo de uso

Ya te enseñé todas las funciones y tal vez por ahora no les encuentres el mayor sentido. Veamos lo interesante ahora con un ejemplo de uso.

Quiero imprimir 3 columnas que contienen la descripción del producto, la cantidad y el precio. Obviamente yo no sé cuál será la longitud de cada uno, pero quiero que se imprima correctamente y que si supera la longitud, se ajuste automáticamente.

Fíjate en que el producto puede ocupar máximo 10 caracteres, la cantidad 5 y el subtotal o precio 6. Al invocar la función con los siguientes datos:

const producto = "Nintendo Switch OLED con 2 juegos incluidos (Zelda BOTW y TOTK)";
const cantidad = "1.00";
const subtotal = "$152,986.22";
const longitudProducto = 10;
const longitudCantidad = 5;
const longitudSubtotal = 6;
const separadorColumnas = "|";
const separadorColumnasEnSeparador = "+";
const columnasEncabezado = [{ contenido: "Producto", maximaLongitud: longitudProducto }, { contenido: "Cant.", maximaLongitud: longitudCantidad }, { contenido: "Precio", maximaLongitud: longitudSubtotal },];
const columnasContenido = [{ contenido: producto, maximaLongitud: longitudProducto }, { contenido: cantidad, maximaLongitud: longitudCantidad }, { contenido: subtotal, maximaLongitud: longitudSubtotal },];
const lineasEncabezado = tabularDatos(columnasEncabezado, " ", separadorColumnas);
const lineasSeparador = tabularDatos([{ contenido: "-", maximaLongitud: longitudProducto }, { contenido: "-", maximaLongitud: longitudCantidad }, { contenido: "-", maximaLongitud: longitudSubtotal }], "-", separadorColumnasEnSeparador);
const lineasCuerpo = tabularDatos(columnasContenido, " ", separadorColumnas);
for (const linea of lineasEncabezado) {
    console.log(linea);
}
for (const linea of lineasSeparador) {
    console.log(linea);
}

for (const linea of lineasCuerpo) {
    console.log(linea);
}

Se obtiene el siguiente resultado:

C:\Users\parzibyte\Desktop>node cortar.js
Producto  |Cant.|Precio|
----------+-----+------+
Nintendo S|1.00 |$152,9|
witch OLED|     |86.22 |
 con 2 jue|     |      |
gos inclui|     |      |
dos (Zelda|     |      |
 BOTW y TO|     |      |
TK)       |     |      |

Si cambiamos los parámetros para que por ejemplo el precio y producto ocupen más, tenemos lo siguiente:

C:\Users\parzibyte\Desktop>node cortar.js
Producto       |Cant.|Precio    |
---------------+-----+----------+
Nintendo Switch|1.00 |$152,986.2|
 OLED con 2 jue|     |2         |
gos incluidos (|     |          |
Zelda BOTW y TO|     |          |
TK)            |     |          |

Incluso podemos dejar todo en una longitud máxima de 2 y seguirá funcionando:

C:\Users\parzibyte\Desktop>node cortar.js
Pr|Ca|Pr|
od|nt|ec|
uc|. |io|
to|  |  |
--+--+--+
Ni|1.|$1|
nt|00|52|
en|  |,9|
do|  |86|
 S|  |.2|
wi|  |2 |
tc|  |  |
h |  |  |
OL|  |  |
ED|  |  |
 c|  |  |
on|  |  |
 2|  |  |
 j|  |  |
ue|  |  |
go|  |  |
s |  |  |
in|  |  |
cl|  |  |
ui|  |  |
do|  |  |
s |  |  |
(Z|  |  |
el|  |  |
da|  |  |
 B|  |  |
OT|  |  |
W |  |  |
y |  |  |
TO|  |  |
TK|  |  |
) |  |  |

Al final tenemos líneas que podemos enviar a cualquier lugar. Si combinamos esto con mi plugin para imprimir en impresoras térmicas desde JavaScript podemos alcanzar un resultado parecido al siguiente:

Datos formateados como tabla en ticket en impresora térmica
Datos formateados como tabla en ticket en impresora térmica

Lo mejor es que tú puedes personalizar el separador de columnas, la máxima longitud, máximo alto, etcétera. Además, puedes crear tus separadores de filas y darle un ancho máximo a cada columna con la seguridad de que los datos no se van a desordenar.

Por cierto, me inspiré en la salida de MySQL para crear mis separadores, ya que si te fijas cuando se imprimen datos desde la CLI se usan los mismos separadores. Ejemplo:

MariaDB [spos1]> select * from productos;
+------------+--------------+-------------+--------------+-------------+------------+-------+
| idProducto | codigoBarras | descripcion | precioCompra | precioVenta | existencia | stock |
+------------+--------------+-------------+--------------+-------------+------------+-------+
|          1 | 123          | 213         |        23.00 |        3.00 |     311.00 | 23.00 |
+------------+--------------+-------------+--------------+-------------+------------+-------+
1 row in set (0.000 sec)

Te repito que siempre quise hacer esa función justamente para imprimir tablas en los tickets, pero se puede usar para muchas más cosas y modificar lo que sea necesario. Recuerda que funciona en el lado del servidor con Node y también en el navegador web.

Por aquí te dejo más tutoriales de JavaScript en mi blog.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *