CCamilo Griffa
←Trabajo

Proyecto propio · 2026

Boleta Ticket

Cada emisor entrega su boleta distinto. El PDF es lo único que todos tienen en común, así que la app entra por ahí y no toca ni un servidor.

Ver en producción

Ficha

Rol
Proyecto propio
Año
2026

Stack

  • JavaScript
  • PDF.js
  • JsBarcode
  • Vercel

8

emisores verificados contra boletas reales

0

llamadas a un servidor: todo corre en el navegador

72 mm

de papel útil sobre los 80 del rollo

Por qué la entrada es el PDF y no la página

En el mostrador hay que reimprimir boletas de impuestos como ticket de 80 mm. El problema no es imprimir: es que cada emisor publica la suya distinto. La Municipalidad de Córdoba la muestra en el visor de PDF del navegador, otras la descargan directo, otras la embeben en la propia página.

Una web app no tiene forma de leer la pestaña que el usuario tiene abierta ni de saber qué estructura usa cada sitio, así que integrarse página por página estaba descartado de entrada: un raspador distinto y frágil por emisor, roto por cualquier rediseño. Lo único que las ocho tienen en común es que el emisor te deja bajar un PDF.

Pedirlo como archivo convierte la heterogeneidad de ocho sistemas de facturación en un solo formato de entrada. Arrastrás el PDF a la página y sumar un emisor nuevo es escribir un perfil, no integrarse con un sitio más.

El PDF no sale del navegador

Una boleta de impuestos trae domicilio, DNI o CUIT y montos. La app no tiene backend: leer el PDF, decodificar los códigos de barras y armar el ticket pasa entero en el navegador de quien la usa, sin mandar nada a ningún lado.

Esto no es una promesa en el README: la política de seguridad del sitio deja "connect-src: 'none'", así que el propio navegador bloquea cualquier llamada de red que el código intentara hacer, a propósito o por error.

Los códigos se leen de la imagen, no del texto

La forma obvia de conseguir el número del código de barras es leerlo del texto del PDF. No alcanza, por dos razones distintas:

  • Rentas no lo imprime en ninguna parte. Sus dos códigos son ITF de 42 dígitos y el número no figura en el documento.
  • Varios traen dígitos verificadores que no se pueden derivar. El código de pago de Rentas termina en dos dígitos de control cuyo algoritmo no se deduce mirando una boleta.

Adivinar ese dígito sería el peor error posible: el código escanea, el cajero cobra, y el ente rechaza el pago. Así que el lector decodifica la imagen del propio código —ITF y CODE128— y el número que sale es, por construcción, el mismo que imprime la empresa. Cuando además está escrito en el texto, la regla sigue existiendo pero como control cruzado: si no coinciden, la boleta queda marcada para revisar.

El lector trabaja en dos pasadas, una en baja resolución para localizar las franjas y otra en alta sólo sobre la tira de cada código. Renderizar una A4 entera a la resolución que hace falta para medir barras da doce megapíxeles y cuelga el navegador.

Lo que decide si escanea es el ancho de barra

No es el largo del código: es el ancho de la barra más angosta. Por debajo de unos 0,25 mm la pistola empieza a fallar. Sobre papel de 80 mm quedan 72 útiles, y ese presupuesto en realidad se mide en puntos de cabeza de impresión: 576 a 203 dpi.

Un módulo tiene que ocupar un número entero de puntos. Si cae en 1,79 la impresora redondea cada barra por su cuenta —unas de dos puntos, otras de uno—, los anchos relativos se deforman y la pistola ya no puede recuperar el patrón.

De ahí sale la decisión que más se nota: los códigos ITF se reimprimen en CODE128. El ITF de 42 dígitos necesita 101,8 mm y no entra en el papel; los mismos 42 dígitos en CODE128 subset C entran en 72,0 mm con 0,252 mm de barra. Los dígitos que devuelve la pistola son idénticos, sólo cambia la simbología.

Los dos puntos por barra están medidos, no estimados: probados con la pistola del mostrador sobre tickets impresos. EPEC, de 46 dígitos, y los cedulones de la Provincia, de 42, leen. Aguas Cordobesas, que cae en 1,79 puntos, no lee. Cuando un código no entra a un ancho legible, la boleta se marca para revisar en lugar de imprimir algo que no va a escanear.

Un perfil por emisor, y un banco de pruebas que los sostiene

Cada perfil declara cómo reconocer su boleta, qué campos extraer, en qué orden mostrarlos y de dónde salen los códigos. Un campo puede tener varias reglas: se prueban en orden y gana la primera que matchea, así un mismo perfil aguanta variantes de la misma boleta sin duplicarse.

Hay además un perfil genérico de reserva que agarra etiquetas comunes y tiras largas de dígitos. Una boleta desconocida igual muestra algo, y sirve para arrancar un perfil nuevo viendo qué agarró.

El banco de pruebas es Node pelado, sin una sola dependencia. Congela lo que cada perfil extrae hoy, así que si se cambia un regex y se mueve otra cosa, aparece en el momento y no en el mostrador.