@danielrebolledo/cafe-ipdh-lib 1.0.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,31 @@
1
1
  # cafe-ipdh-lib
2
2
 
3
- Librería NPM para gestionar impresoras fiscales y de recibos. Simplifica la construcción de comandos para diferentes modelos de impresoras.
3
+ Librería para gestionar impresoras fiscales y de recibos. Construye comandos y los envía mediante una API unificada (`sendPrinterCommands`) que soporta AEG y DTP.
4
4
 
5
5
  ## Modelos soportados
6
6
 
7
- - **AEG-R1**: Impresora fiscal vía HTTP (comandos JSON)
7
+ | Marca | Modelo | Transporte |
8
+ |-------|--------|------------|
9
+ | **AEG** | R1 | HTTP (POST a `/cmdoJson`) |
10
+ | **DTP** | 80i | TCP |
8
11
 
9
12
  ## Instalación
10
13
 
11
14
  ```bash
12
- npm install cafe-ipdh-lib
15
+ npm install @danielrebolledo/cafe-ipdh-lib
13
16
  ```
14
17
 
15
- ## Uso
18
+ ## Uso unificado: `sendPrinterCommands`
16
19
 
17
- ### Imprimir factura fiscal (AEG-R1)
20
+ Tanto AEG como DTP usan la misma función:
18
21
 
19
22
  ```ts
20
- import { aegPrinter } from "cafe-ipdh-lib";
23
+ import {
24
+ aegPrinter,
25
+ dtpPrinter,
26
+ sendPrinterCommands,
27
+ DtpClient,
28
+ } from "@danielrebolledo/cafe-ipdh-lib";
21
29
 
22
30
  const order = {
23
31
  client: { id: "V12345678", name: "Cliente", email: "c@mail.com" },
@@ -26,53 +34,188 @@ const order = {
26
34
  total: 10,
27
35
  };
28
36
 
29
- const commands = aegPrinter.buildInvoiceCommands(order, {
30
- paymentMethodId: "cash_nat",
31
- storeName: "Mi Tienda",
37
+ // AEG-R1 (HTTP)
38
+ const resultAeg = await sendPrinterCommands({
39
+ brand: "AEG",
40
+ model: "R1",
41
+ ip: "http://192.168.1.100",
42
+ commands: aegPrinter.buildInvoiceCommands(order, {
43
+ paymentMethodId: "cash_nat",
44
+ storeName: "Mi Tienda",
45
+ }),
32
46
  });
33
47
 
34
- // Enviar commands a tu API/impresora (HTTP, etc.)
48
+ // DTP-80i (TCP; requiere client ya conectado)
49
+ const resultDtp = await sendPrinterCommands({
50
+ brand: "DTP",
51
+ model: "80i",
52
+ client: dtpClient, // DtpClient conectado
53
+ commands: dtpPrinter.buildInvoiceCommands(order, {
54
+ paymentMethodId: "cash_nat",
55
+ storeName: "Mi Tienda",
56
+ }),
57
+ });
35
58
  ```
36
59
 
37
- ### Imprimir recibo de pago
60
+ ## Ejemplos por entorno
61
+
62
+ ### Web (AEG-R1)
63
+
64
+ En un proyecto web (Next.js, Vite, etc.), normalmente se usa AEG por HTTP:
38
65
 
39
66
  ```ts
40
- const receiptCommands = aegPrinter.buildReceiptCommands({
41
- orderId: "ORD-123",
42
- amountPaid: 150.5,
43
- paymentMethod: "Tarjeta débito",
44
- organizationName: "Mi Negocio",
45
- });
67
+ import {
68
+ aegPrinter,
69
+ sendPrinterCommands,
70
+ type Order,
71
+ } from "@danielrebolledo/cafe-ipdh-lib";
72
+
73
+ async function imprimirFactura(order: Order, printerIp: string) {
74
+ const commands = aegPrinter.buildInvoiceCommands(order, {
75
+ paymentMethodId: "cash_nat",
76
+ storeName: "Mi Tienda",
77
+ });
78
+
79
+ const result = await sendPrinterCommands({
80
+ brand: "AEG",
81
+ model: "R1",
82
+ ip: printerIp,
83
+ commands,
84
+ });
85
+
86
+ const invoiceRef = Array.isArray(result)
87
+ ? result.find((r) => r.cmd === "endFac")?.dataD ?? 0
88
+ : result.documentNumber ?? 0;
89
+
90
+ return { success: true, invoiceRef };
91
+ }
92
+ ```
93
+
94
+ ### React Native (DTP-80i)
95
+
96
+ En React Native (Expo, etc.) se suele usar DTP vía TCP con `react-native-tcp-socket`:
97
+
98
+ ```bash
99
+ npm install react-native-tcp-socket
100
+ ```
101
+
102
+ ```ts
103
+ import TcpSocket from "react-native-tcp-socket";
104
+ import {
105
+ DtpClient,
106
+ dtpPrinter,
107
+ sendPrinterCommands,
108
+ type Order,
109
+ } from "@danielrebolledo/cafe-ipdh-lib";
110
+
111
+ const createConnection = (opts: { host: string; port: number }) =>
112
+ new Promise((resolve, reject) => {
113
+ const socket = TcpSocket.createConnection(opts, () => resolve(socket));
114
+ socket.once("error", reject);
115
+ });
116
+
117
+ async function imprimirFactura(order: Order, host: string, port: number) {
118
+ const client = new DtpClient({
119
+ host,
120
+ port,
121
+ connectTimeoutMs: 15000,
122
+ createConnection: createConnection as any,
123
+ });
124
+
125
+ try {
126
+ await client.connect();
127
+
128
+ const commands = dtpPrinter.buildInvoiceCommands(order, {
129
+ paymentMethodId: "cash_nat",
130
+ storeName: "Mi Tienda",
131
+ });
132
+
133
+ const result = await sendPrinterCommands({
134
+ brand: "DTP",
135
+ model: "80i",
136
+ client,
137
+ commands,
138
+ });
139
+
140
+ return {
141
+ success: true,
142
+ invoiceRef: result.documentNumber ?? 0,
143
+ };
144
+ } finally {
145
+ client.close();
146
+ }
147
+ }
46
148
  ```
47
149
 
48
150
  ## API
49
151
 
50
- ### `aegPrinter`
152
+ ### Drivers
51
153
 
52
- Driver para impresora AEG-R1.
154
+ | Driver | Modelo | Métodos |
155
+ |--------|--------|---------|
156
+ | `aegPrinter` | AEG-R1 | `buildInvoiceCommands`, `buildReceiptCommands` |
157
+ | `dtpPrinter` | DTP-80i | `buildInvoiceCommands`, `buildReceiptCommands`, `buildCreditNoteCommands` |
53
158
 
54
- - `buildInvoiceCommands(order, options)` → comandos para factura fiscal
55
- - `buildReceiptCommands(options)` → comandos para recibo DNF
159
+ ### `sendPrinterCommands`
160
+
161
+ Función unificada para enviar comandos a cualquier impresora.
162
+
163
+ - **AEG**: `{ brand: "AEG", model: "R1", ip, commands }`
164
+ - **DTP**: `{ brand: "DTP", model: "80i", client, commands }`
165
+
166
+ Retorna:
167
+ - AEG: `PrinterCommandResponse[]`
168
+ - DTP: `{ documentNumber?: number; totalAmount?: number }`
169
+
170
+ ### Node.js y DTP
171
+
172
+ Para usar DTP en Node.js (backend), importa `createNodeDtpConnection` del entry `node`:
173
+
174
+ ```ts
175
+ import {
176
+ DtpClient,
177
+ dtpPrinter,
178
+ sendPrinterCommands,
179
+ } from "@danielrebolledo/cafe-ipdh-lib";
180
+ import {
181
+ createNodeDtpConnection,
182
+ } from "@danielrebolledo/cafe-ipdh-lib/node";
183
+
184
+ const client = new DtpClient({
185
+ host: "192.168.1.10",
186
+ port: 3010,
187
+ createConnection: createNodeDtpConnection,
188
+ });
189
+ await client.connect();
190
+
191
+ const result = await sendPrinterCommands({
192
+ brand: "DTP",
193
+ model: "80i",
194
+ client,
195
+ commands: dtpPrinter.buildInvoiceCommands(order, opts),
196
+ });
197
+ ```
56
198
 
57
199
  ### Tipos exportados
58
200
 
59
201
  - `Order`, `OrderItem`, `FiscalClient`, `OrderPayment`
60
- - `AegPrinterCommand`
61
- - `BuildInvoiceOptions`, `BuildReceiptOptions`
202
+ - `AegPrinterCommand`, `DtpPrinterCommand`
203
+ - `BuildInvoiceOptions`, `BuildReceiptOptions`, `BuildCreditNoteOptions`
204
+ - `SendPrinterCommandsArgs`, `SendPrinterCommandsResult`
62
205
  - `PaymentMethodId`, `PrinterTaxValues`, `TaxValues`
63
206
 
64
207
  ## Scripts
65
208
 
66
209
  ```bash
67
- npm run build # Compilar con tsup
68
- npm run dev # Watch mode
69
- npm run lint # Biome check
70
- npm run test # Vitest
210
+ npm run build # Compilar
211
+ npm run dev # Watch mode
212
+ npm run lint # Biome
213
+ npm run test # Vitest
71
214
  ```
72
215
 
73
216
  ## Stack
74
217
 
75
218
  - TypeScript
76
- - tsup (bundle)
77
- - Biome (lint/format)
78
- - Vitest (tests)
219
+ - tsup
220
+ - Biome
221
+ - Vitest
@@ -70,6 +70,19 @@ interface BuildInvoiceOptions {
70
70
  paymentMethodId: "pos_credit" | "pos_debit" | "pos_debit_credit_int" | "cash_int" | "cash_nat";
71
71
  storeName?: string;
72
72
  }
73
+ /**
74
+ * Opciones para construir comandos de nota de crédito.
75
+ */
76
+ interface BuildCreditNoteOptions {
77
+ /** Número de la factura original que se está anulando. */
78
+ referenceInvoiceNumber: number;
79
+ /** Fecha de la factura original. */
80
+ referenceInvoiceDate: Date;
81
+ /** Serial fiscal de la factura original (opcional). */
82
+ referenceInvoiceSerial?: string;
83
+ paymentMethodId: "pos_credit" | "pos_debit" | "pos_debit_credit_int" | "cash_int" | "cash_nat";
84
+ storeName?: string;
85
+ }
73
86
  /**
74
87
  * Opciones para construir comandos de recibo de pago.
75
88
  */
@@ -95,6 +108,10 @@ interface PrinterDriver<TCmd = unknown> {
95
108
  * Construye comandos para imprimir un recibo de pago (DNF).
96
109
  */
97
110
  buildReceiptCommands(options: BuildReceiptOptions): TCmd[];
111
+ /**
112
+ * Construye comandos para imprimir una nota de crédito fiscal (opcional).
113
+ */
114
+ buildCreditNoteCommands?(order: Order, options: BuildCreditNoteOptions): TCmd[];
98
115
  }
99
116
 
100
117
  /**
@@ -255,4 +272,4 @@ interface ExecuteDtpCommandsResult {
255
272
  */
256
273
  declare function executeDtpCommands(client: DtpClient, commands: DtpPrinterCommand[]): Promise<ExecuteDtpCommandsResult>;
257
274
 
258
- export { type BuildInvoiceOptions as B, type CreateDtpConnection as C, type DocumentType as D, type ExecuteDtpCommandsResult as E, type FiscalClient as F, type ItemTax as I, type Order as O, type PrinterDriver as P, type BuildReceiptOptions as a, DtpClient as b, type DtpOptions as c, type DtpPrinterCommand as d, type DtpSocketLike as e, type OrderItem as f, type OrderPayment as g, type PrinterCommandResponse as h, dtpPrinter as i, executeDtpCommands as j };
275
+ export { type BuildCreditNoteOptions as B, type CreateDtpConnection as C, DtpClient as D, type ExecuteDtpCommandsResult as E, type FiscalClient as F, type ItemTax as I, type Order as O, type PrinterDriver as P, type DtpPrinterCommand as a, type BuildInvoiceOptions as b, type BuildReceiptOptions as c, type DocumentType as d, type DtpOptions as e, type DtpSocketLike as f, type OrderItem as g, type OrderPayment as h, type PrinterCommandResponse as i, dtpPrinter as j, executeDtpCommands as k };
@@ -70,6 +70,19 @@ interface BuildInvoiceOptions {
70
70
  paymentMethodId: "pos_credit" | "pos_debit" | "pos_debit_credit_int" | "cash_int" | "cash_nat";
71
71
  storeName?: string;
72
72
  }
73
+ /**
74
+ * Opciones para construir comandos de nota de crédito.
75
+ */
76
+ interface BuildCreditNoteOptions {
77
+ /** Número de la factura original que se está anulando. */
78
+ referenceInvoiceNumber: number;
79
+ /** Fecha de la factura original. */
80
+ referenceInvoiceDate: Date;
81
+ /** Serial fiscal de la factura original (opcional). */
82
+ referenceInvoiceSerial?: string;
83
+ paymentMethodId: "pos_credit" | "pos_debit" | "pos_debit_credit_int" | "cash_int" | "cash_nat";
84
+ storeName?: string;
85
+ }
73
86
  /**
74
87
  * Opciones para construir comandos de recibo de pago.
75
88
  */
@@ -95,6 +108,10 @@ interface PrinterDriver<TCmd = unknown> {
95
108
  * Construye comandos para imprimir un recibo de pago (DNF).
96
109
  */
97
110
  buildReceiptCommands(options: BuildReceiptOptions): TCmd[];
111
+ /**
112
+ * Construye comandos para imprimir una nota de crédito fiscal (opcional).
113
+ */
114
+ buildCreditNoteCommands?(order: Order, options: BuildCreditNoteOptions): TCmd[];
98
115
  }
99
116
 
100
117
  /**
@@ -255,4 +272,4 @@ interface ExecuteDtpCommandsResult {
255
272
  */
256
273
  declare function executeDtpCommands(client: DtpClient, commands: DtpPrinterCommand[]): Promise<ExecuteDtpCommandsResult>;
257
274
 
258
- export { type BuildInvoiceOptions as B, type CreateDtpConnection as C, type DocumentType as D, type ExecuteDtpCommandsResult as E, type FiscalClient as F, type ItemTax as I, type Order as O, type PrinterDriver as P, type BuildReceiptOptions as a, DtpClient as b, type DtpOptions as c, type DtpPrinterCommand as d, type DtpSocketLike as e, type OrderItem as f, type OrderPayment as g, type PrinterCommandResponse as h, dtpPrinter as i, executeDtpCommands as j };
275
+ export { type BuildCreditNoteOptions as B, type CreateDtpConnection as C, DtpClient as D, type ExecuteDtpCommandsResult as E, type FiscalClient as F, type ItemTax as I, type Order as O, type PrinterDriver as P, type DtpPrinterCommand as a, type BuildInvoiceOptions as b, type BuildReceiptOptions as c, type DocumentType as d, type DtpOptions as e, type DtpSocketLike as f, type OrderItem as g, type OrderPayment as h, type PrinterCommandResponse as i, dtpPrinter as j, executeDtpCommands as k };
package/dist/index.cjs CHANGED
@@ -666,6 +666,121 @@ var dtpPrinter = {
666
666
  commands.push({ cmd: "F5", data: {} });
667
667
  return commands;
668
668
  },
669
+ buildCreditNoteCommands(order, options) {
670
+ const commands = [];
671
+ if (!order) {
672
+ throw new Error("Order es requerido");
673
+ }
674
+ if (!order.client) {
675
+ throw new Error("Order debe tener un cliente asociado");
676
+ }
677
+ if (!order.items || order.items.length === 0) {
678
+ throw new Error("Order debe tener al menos un item");
679
+ }
680
+ const {
681
+ referenceInvoiceNumber,
682
+ referenceInvoiceDate,
683
+ referenceInvoiceSerial = "",
684
+ paymentMethodId,
685
+ storeName = "N/A"
686
+ } = options;
687
+ const clientName = order.client.name || "";
688
+ const clientRif = order.client.id || "";
689
+ const storeLine = `Tienda: ${storeName}`;
690
+ commands.push({
691
+ cmd: "F0",
692
+ data: {
693
+ iTipo: 1,
694
+ sNombreCliente: truncateString2(clientName, 64),
695
+ sRifCliente: truncateString2(clientRif, 20),
696
+ iFacturaReferencia: referenceInvoiceNumber,
697
+ fechaReferencia: referenceInvoiceDate,
698
+ sSerialReferencia: truncateString2(referenceInvoiceSerial, 20),
699
+ bLogo: false,
700
+ sLineaAdicional: truncateString2(storeLine, 64)
701
+ }
702
+ });
703
+ let subtotalWithoutTaxes = 0;
704
+ for (const item of order.items) {
705
+ if (!item.name) continue;
706
+ const taxId = item.taxes && item.taxes.length > 0 ? item.taxes[0].id : null;
707
+ const iImpuesto = mapTaxIdToDtpCode(taxId);
708
+ const price = item.price ?? 0;
709
+ const lPrecio = Math.round(Math.max(price, 0) * 100);
710
+ const quantity = item.selectedQuantity ?? item.quantity ?? 1;
711
+ const lCantidad = -Math.round(Math.max(quantity, 1) * 1e3);
712
+ subtotalWithoutTaxes += price * Math.max(quantity, 1);
713
+ commands.push({
714
+ cmd: "F1",
715
+ data: {
716
+ iTipo: 1,
717
+ sDescripcion: truncateString2(item.name, 64),
718
+ sCodigo: truncateString2(item.sku ?? "N/A", 20),
719
+ lCantidad,
720
+ sUnidad: "UND",
721
+ lPrecio,
722
+ iImpuesto,
723
+ iDecPrecio: 2,
724
+ iDecCantidad: 3
725
+ }
726
+ });
727
+ }
728
+ const isForeignCurrency = paymentMethodId === "pos_debit_credit_int" || paymentMethodId === "cash_int";
729
+ if (isForeignCurrency && subtotalWithoutTaxes > 0) {
730
+ const roundedSubtotal = Math.round((subtotalWithoutTaxes + Number.EPSILON) * 100) / 100;
731
+ const igtfPercentage = 3 /* BI_IGTF */ / 100;
732
+ const igtfAmount = Math.round((roundedSubtotal * igtfPercentage + Number.EPSILON) * 100) / 100;
733
+ commands.push({
734
+ cmd: "F1",
735
+ data: {
736
+ iTipo: 1,
737
+ sDescripcion: "IGTF 3% pago en divisas",
738
+ sCodigo: "IGTF",
739
+ lCantidad: -1e3,
740
+ sUnidad: "UND",
741
+ lPrecio: Math.round(igtfAmount * 100),
742
+ iImpuesto: 4,
743
+ iDecPrecio: 2,
744
+ iDecCantidad: 3
745
+ }
746
+ });
747
+ }
748
+ commands.push({
749
+ cmd: "F2",
750
+ data: { mode: 1, foreignCurrencyAmount: 0 }
751
+ });
752
+ if (!order.payments?.length) {
753
+ throw new Error("Order debe tener al menos un pago exitoso");
754
+ }
755
+ for (const payment of order.payments) {
756
+ const tipo = mapPaymentMethod2(payment.paymentMethod);
757
+ const lMonto = Math.round(payment.amount * 100);
758
+ const isForeign = payment.paymentMethod === "pos_debit_credit_int" || payment.paymentMethod === "cash_int";
759
+ if (isForeign) {
760
+ commands.push({
761
+ cmd: "F11",
762
+ data: {
763
+ iFormaPago: tipo,
764
+ sDescripcion: paymentMethodLabel(tipo),
765
+ lMonto,
766
+ lTasaCambio: 1,
767
+ sSimbolo: "USD"
768
+ }
769
+ });
770
+ } else {
771
+ commands.push({
772
+ cmd: "F4",
773
+ data: {
774
+ iFormaPago: tipo,
775
+ sDescripcion: paymentMethodLabel(tipo),
776
+ lMonto
777
+ }
778
+ });
779
+ }
780
+ }
781
+ commands.push({ cmd: "F5", data: {} });
782
+ return commands;
783
+ },
669
784
  buildReceiptCommands(options) {
670
785
  const {
671
786
  orderId,
@@ -803,9 +918,7 @@ async function sendPrinterCommands(args, options) {
803
918
  `Modelo inv\xE1lido para DTP: "${model}". Solo se soporta "80i".`
804
919
  );
805
920
  }
806
- throw new Error(
807
- "DTP-80i requiere el m\xF3dulo nativo ExpoDtpFiscalPrinter. Use la conexi\xF3n TCP directamente desde su aplicaci\xF3n."
808
- );
921
+ return sendDtpCommands(args);
809
922
  default: {
810
923
  const _exhaustive = brand;
811
924
  throw new Error(
@@ -814,6 +927,10 @@ async function sendPrinterCommands(args, options) {
814
927
  }
815
928
  }
816
929
  }
930
+ async function sendDtpCommands(args) {
931
+ const { client, commands } = args;
932
+ return executeDtpCommands(client, commands);
933
+ }
817
934
  async function sendAegCommands(args, options) {
818
935
  const { ip, commands } = args;
819
936
  const baseUrl = normalizeBaseUrl(ip);