Impresión · diseño esencial

El papel
confirma.

El paso "Imprime" de recepción deja de ser una promesa: la iQ4R confirma cada etiqueta con su task ID y la barra solo avanza cuando hay tinta sobre papel. Los fallos son ámbar y se reanudan — nunca se reimprime lo confirmado, nunca se pierde una tirada.

Conecta Imprime Confirma Pega
AEl otro camino — la iQ4R dice que no

Se acabó el papel, quedó la tapa abierta, se cayó el Bluetooth. Todo fallo de impresora es recuperable: el error se materializa donde vivía el chip, en ámbar, y la tirada se reanuda exactamente donde quedó.

BLa ingeniería — referencias, ZPL y secuencias

El mismo end-to-end, con cada referencia: de qué archivo sale cada dato, qué método del SDK lleva cada byte, y los tres diagramas de secuencia que gobiernan la tirada. Las marcas ámbar citan la sección exacta del manual del fabricante.

Datos — el draft que alimenta la etiqueta
existe
ReceptionDraftla interfaz completa
apps/warehouse/hooks/reception/use-reception-draft.tsx — trackingNumber, orderId, clientName, isOrphan, expectedBoxCount, boxCount, cbm, weight, needsForklift, isHeavy, photos[]
Código de etiqueta(N)-tracking-i
apps/warehouse/app/print.tsx — trackingNumber + boxCount + índice de caja; el sufijo es la caja i
Banner de manejoimpreso para Caracas
draft.needsForklift / draft.isHeavy — las banderas de "5 · Mide"
CTA "Imprimir N"lo que se cablea
hoy navega a "Pega"; pasa a llamar usePrinter().printLabels(draft)
Generador ZPL — TypeScript compartido
nuevo
buildLabelZpl(draft, i)una etiqueta = un string
apps/warehouse/lib/print/zpl-label.ts — puro, testeable sin hardware; el mismo string para Android e iOS
Geometría62 × 62 mm @ 300 dpi
300 dpi = 11.81 dots/mm → 732 × 732 dots; la preview RN mapea 1:1
Por qué ZPLdecisión
el SDK Android es ZPL-only; la iQ4R lo habla nativo cotización: "TSPL ZPL EPL DPL"; iOS tiene PTCommandZPL → un solo lenguaje en todo el stack
Módulo nativo — Android (camino principal · PDA M2 Pro)
nuevo
Wrapper KotlinExpo Modules API
apps/warehouse/modules/idprt-printer/android/ — envuelve ZPLPrinterHelper.getZPL(context) manual §1.2
connect(mac)Bluetooth clásico SPP
PortOpen("Bluetooth,"+MAC) → 0 ok / −1 fail §2.1 · Wi-Fi: PortOpen("WiFi,"+ip+",9100") §2.2 · USB §2.3
print(zpl)transporte crudo
WriteData(zpl.toByteArray()) §3.17 — el SDK no compone la etiqueta, solo la lleva
Confirmación por etiquetaanti-pérdida
setMissedHitsSwitch(true) + setMissedHitsID(i) + onResult(id, result) §3.36 — bits: 2=tapa · 8=sin papel · 32=sobrecalentada · 64=form error
Estado en vivoantes y durante
getPrinterStatus(listener) → 0 normal · 1 tapa · 2 sin papel · 3 sobrecalentada · 4 imprimiendo · 5 sin ribbon §3.35
Binarios y permisosconfig plugin
zpl-sdk-v1.14.11.jar + libcommon.aar (.so ×4 ABIs) · deps pdfium-android, utilcode §1.1 · BLUETOOTH_CONNECT + BLUETOOTH_SCAN (Android 12+)
Módulo nativo — iOS (iPhone)
nuevo
Wrapper Swiftmismo contrato JS
apps/warehouse/modules/idprt-printer/ios/ — envuelve el singleton PTDispatcher.share() PrinterSDK §2.1
scan() / connect()BLE
scanBluetooth() + whenFindAllBluetooth → connectPrinter(PTPrinter) → whenConnectSuccess / whenConnectFailureWithErrorBlock PrinterSDK §2.1 + §3
print(zpl)transporte crudo
sendData + whenSendSuccess / whenSendFailure · estado: whenUpdatePrintState → PTPrintState 0xcc00 ok · 0xcc01 sin papel · 0xcc02 tapa PrinterSDK §2.1
Gotcha documentadoel PDF lo advierte
cada sendData LIMPIA los blocks de callback PrinterSDK §1 nota 8 → el wrapper los re-registra en cada envío
Binarios y buildconfig plugin
PrinterSDK + RYCommunication + CocoaAsyncSocket (XCFrameworks, arm64 + simulador) · -ObjC + SystemConfiguration.framework PrinterSDK §1 · NSBluetoothAlwaysUsageDescription RYCommunication §2

La anatomía del ZPL. Esto es literalmente lo que viaja por Bluetooth — un string por caja, misma jerarquía que la preview:

^XA
inicio de formato
^PW732 ^LL732
62 mm × 11.81 dots/mm @ 300 dpi
^CI28
UTF-8 — clientes con acentos (Pérez, Muñoz)
^FO0,36^FB732,1,0,C^A0N,30^FD▲ MONTACARGAS^FS
banner — solo si hay banderas de manejo
^FO0,96^FB732,1,0,C^A0N,34^FDMOGOS GROUP C.A^FS
marca, centrada
^FO216,160^BQN,2,10^FDQA,{orderId}^FS
QR real — el id de la orden, magnif. 10 ≈ 300 dots
^FO0,520^FB732,1,0,C^A0N,32^FD(3)-SF-7741-2^FS
código de la caja i
^FO0,580^FB732,1,0,C^A0N,28^FDMarielena Rodríguez^FS
clientName ?? "Unassigned"
^FO0,630^FB732,1,0,C^A0N,24^FDCaracas^FS
destino del flujo actual
^PQ1
1 copia — el loop es por etiqueta, para confirmar cada task ID
^XZ
fin de formato

Secuencia A — Conexión. Una vez por turno; la dirección queda persistida y los próximos turnos auto-reconectan.

Operador Sheet impresora usePrinter() Módulo nativo SDK vendor iQ4R M2 Pro · iPhoneapp/printer · nuevohooks/print · nuevomodules/idprt-printerZPLPrinterHelper · PTDispatcherBT · 300 dpi toca "sin conexión" scan() discovery BT clásico / scanBluetooth() BLE [{name:"iQ4R-XXXX", mac, rssi}] → lista en pantalla toca "iQ4R-XXXX" connect(mac) → PortOpen("Bluetooth,"+mac) §2.1 0 = conectada → persiste la dirección chip verde en el paso Imprime · próximos turnos: auto-reconexión — el operador no vuelve aquí

Secuencia B — Impresión feliz. Un loop por etiqueta; cada una confirmada en papel con su task ID antes de avanzar la barra.

Operador Pantalla Imprime usePrinter() Módulo nativo SDK vendor iQ4R M2 Pro · iPhoneCTA "Imprimir 3"+ lib/print/zpl-label.tsmodules/idprt-printerZPLPrinterHelper · PTDispatcherBT · 300 dpi loop · por cada caja i = 1…3 "Imprimir 3" printLabels(draft) print(zpl[i], taskId: i) setMissedHitsID(i) §3.36 + WriteData(zpl) §3.17 bytes por SPP / BLE onResult(i, 0) — etiqueta i en papel onLabelPrinted(i) → UI "i de 3…" 3ª confirmada → Pega buildLabelZpl(draft, i) → 3 strings ZPL la barra avanza con confirmaciones reales en papel — no con "datos enviados"

Secuencia C — Error a mitad de tirada. Se acabó el papel en la 2 de 3: reanudar desde la 2, nunca reimprimir la 1.

Operador Pantalla Imprime usePrinter() Módulo nativo SDK vendor iQ4R M2 Pro · iPhoneUI de progresocola viva en memoriamodules/idprt-printerZPLPrinterHelper · PTDispatcherBT · 300 dpi onResult(2, bit 8 = sin papel) §3.36 onLabelFailed(2, "paper") — la 1 ya está confirmada "Sin papel — impresa 1 de 3" repone papel → "Reanudar" resume(desde i = 2) — cola viva en usePrinter WriteData(zpl[2]) · WriteData(zpl[3]) onResult(2, 0) · onResult(3, 0) → Pega misma mecánica: tapa abierta (bit 2) · sobrecalentada (bit 32) · en iOS: PTPrintState 0xcc01 / 0xcc02
llamadarespuesta / confirmaciónerror recuperable
Verificación de capacidades — documentado vs. validar día 1
honesto
Detectar sin papel / tapa / temperatura
✅ documentado en ambos — Android §3.35 · iOS PTPrintState
Confirmación por etiqueta (task IDs)
✅ documentado §3.36 — "Universal interface" · ⚠️ validar que la iQ4R lo honra con el APK demo del fabricante, el día 1, sin escribir código
Reanudar tras cerrar la tapa
✅ mecanismo nuestro: poll de estado §3.35 + reenviar los task IDs sin confirmar — no depende del firmware
Estado de impresión en iOS con ZPL
⚠️ el único hueco real: whenUpdatePrintState requiere activar el "status callback switch" — documentado para ESC/CPCL, no para ZPL. Plan B: poll de estado, igual que Android
La tirada nunca se pierde
✅ 100% software nuestro — cola en usePrinter() + draft persistente
Fuentes
Cotización iDPRT Q20260120J01
Xiamen Hanin Co., Ltd. — iQ4R (P/N 100700642) · M2 Pro PDA Android (P/N 102900028) · RF2 RFID (P/N 103000006)
Android ZPL SDK v1.14.11
manual §§1–4 + binarios (jar + aar ×4 ABIs) + demo + APK de validación
PrinterSDK Integration Guide (iOS)
28 pp. — PTDispatcher §2.1 · PTCommandZPL §2.5 · conexión §3 · ejemplos §4 · XCFrameworks con slice de simulador
RYCommunication Integration Guide (iOS)
9 pp. — protocolo RYAccessory · BLE · Info.plist
Spec de ingeniería
repo mogos-group → docs/superpowers/specs/2026-06-12-warehouse-printing-design.md — la versión completa con rutas, líneas y plan de validación

Debajo del cristal

La etiqueta no es una imagen: es ZPL puro generado en la app — texto, QR real con el id de la orden y el banner de manejo, calculados a 300 dpi (732 × 732 puntos para 62 × 62 mm). Un módulo nativo lo lleva por Bluetooth a la iQ4R desde la PDA Android del operador o desde un iPhone, con el mismo contrato. La impresora responde etiqueta por etiqueta; esa respuesta es la única fuente del progreso que se ve arriba.

draft de recepción → buildLabelZpl(draft, caja i) → módulo nativo (Android · iOS) → iQ4R → confirmación por task ID → UI