Impresión ESC/POS desde el navegador: Guía para desarrolladores

El problema de la impresión en el navegador

Si alguna vez has intentado imprimir un recibo desde una aplicación web, conoces el dolor: window.print() abre un cuadro de diálogo de impresión, renderiza la página como un PDF y no tiene soporte para comandos ESC/POS como corte automático o ancho de papel personalizado.

Para las impresoras térmicas de recibos, esto es un gran inconveniente. Necesitas:

  • Impresión silenciosa — sin diálogo, sin interacción del usuario
  • Comandos ESC/POS — para corte automático, texto en negrita, alineación, códigos de barras
  • Codificación fiable — UTF-8 a la página de códigos correcta para tu impresora
  • Soporte multiplataforma — Mac, Windows, Linux

La solución es un puente WebSocket local — una pequeña aplicación en segundo plano que se ejecuta en el ordenador del usuario, acepta trabajos de impresión de tu aplicación web y envía comandos ESC/POS puros a la impresora.


Cómo funciona el puente WebSocket

Tu aplicación web → WebSocket (ws://localhost:8765) → Agente de impresión → Impresora térmica

El flujo es simple:

  1. Tu aplicación web abre una conexión WebSocket a ws://localhost:8765
  2. Envías una carga útil JSON que describe el recibo
  3. El Agente de impresión lo convierte a ESC/POS y lo envía a la impresora
  4. La impresora imprime silenciosamente — sin diálogo, sin interacción con el controlador

MenuForma Print Agent implementa este puente. Es gratuito, de código abierto y se ejecuta en Mac, Windows y Linux.


Referencia de la API

Conexión

const ws = new WebSocket('ws://localhost:8765');

ws.onopen = () => {
  console.log('Print Agent conectado');
};

ws.onerror = () => {
  console.log('Print Agent no está en ejecución');
};

Comprobar el estado del agente

Envía un ping para verificar que el agente está en ejecución:

ws.send(JSON.stringify({ type: 'ping' }));

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'pong') {
    console.log('Versión del agente:', data.version);
  }
};

Imprimir un recibo

const receipt = {
  type: 'print',
  printer: 'auto',   // o IP específica como '192.168.1.100'
  data: {
    storeName: 'Mi Restaurante',
    storeAddress: '123 Calle Principal',
    orderNumber: '#1042',
    tableNumber: 'Mesa 5',
    items: [
      { name: 'Pizza Margherita', qty: 1, price: 14.90 },
      { name: 'Agua con gas', qty: 2, price: 3.50 },
    ],
    subtotal: 21.90,
    tax: 1.97,
    total: 23.87,
    paymentMethod: 'Tarjeta',
    footer: '¡Gracias por cenar con nosotros!',
  }
};

ws.send(JSON.stringify(receipt));

Ejemplo de integración con React

import { useEffect, useRef, useCallback, useState } from 'react';

function usePrintAgent() {
  const wsRef = useRef(null);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const connect = () => {
      const ws = new WebSocket('ws://localhost:8765');
      ws.onopen = () => setConnected(true);
      ws.onclose = () => {
        setConnected(false);
        setTimeout(connect, 3000); // reintentar
      };
      wsRef.current = ws;
    };
    connect();
    return () => wsRef.current?.close();
  }, []);

const print = useCallback((receiptData) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      throw new Error('Print Agent not connected');
    }
    wsRef.current.send(JSON.stringify({ type: 'print', data: receiptData }));
  }, []);

  return { connected, print };
}

Integración con Shopify

Para las tiendas Shopify, puedes activar la impresión desde un webhook de pedido o desde una extensión del navegador:

// En tu tienda Shopify o extensión de administración
async function printShopifyOrder(order) {
  const ws = new WebSocket('ws://localhost:8765');
  
  ws.onopen = () => {
    ws.send(JSON.stringify({
      type: 'print',
      data: {
        storeName: order.shop_name,
        orderNumber: order.name,
        items: order.line_items.map(item => ({
          name: item.title,
          qty: item.quantity,
          price: parseFloat(item.price),
        })),
        total: parseFloat(order.total_price),
        paymentMethod: order.payment_gateway,
      }
    }));
  };
}

Configuración de la Impresora

El campo "printer" en la carga útil de impresión acepta:

Valor Descripción
"auto" Usar la impresora configurada en los ajustes del Agente de Impresión
"192.168.1.100" Conectar directamente a una impresora de red por IP
"USB" Usar la primera impresora USB detectada

Manejo de Errores

ws.onmessage = (event) => {
  const response = JSON.parse(event.data);
  
  if (response.type === 'print_success') {
    console.log('Printed successfully');
  } else if (response.type === 'print_error') {
    console.error('Print failed:', response.error);
    // Mostrar UI de respaldo o reintentar
  }
};

Detectando si el Agente de Impresión está Instalado

Antes de mostrar la funcionalidad de impresión en tu interfaz de usuario, verifica si el agente está ejecutándose:

async function isPrintAgentAvailable() {
  return new Promise((resolve) => {
    const ws = new WebSocket('ws://localhost:8765');
    const timeout = setTimeout(() => {
      ws.close();
      resolve(false);
    }, 1000);
    ws.onopen = () => {
      clearTimeout(timeout);
      ws.close();
      resolve(true);
    };
    ws.onerror = () => {
      clearTimeout(timeout);
      resolve(false);
    };
  });
}

// Uso
const agentAvailable = await isPrintAgentAvailable();
if (!agentAvailable) {
  // Mostrar mensaje de descarga
  window.open('/print-agent', '_blank');
}

Descargar Agente de Impresión

MenuForma Print Agent es gratuito y está disponible para Mac, Windows y Linux. Soporta impresoras térmicas USB, de red y Bluetooth, e implementa la API completa de WebSocket descrita en esta guía.

Para obtener la documentación completa de la API y el código de código abierto, visita la página del Agente de Impresión.

Related Articles

MenuForma Products