arca-sdk 0.4.0 → 0.5.0

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
@@ -5,8 +5,9 @@
5
5
  SDK en TypeScript para integración con servicios de ARCA:
6
6
  - ✅ **Type-safe**: TypeScript strict mode
7
7
  - ✅ **Simple**: No más XML manual
8
- - ✅ **Automático**: Cache de tokens, retry logic
9
- - ✅ **Fiscal**: Generador de QR oficial AFIP integrado
8
+ - ✅ **Automático**: Cache de tokens, persistencia opcional (God Mode)
9
+ - ✅ **Fiscal**: Generador de QR oficial ARCA/AFIP ultra-robusto
10
+ - ✅ **Padrón**: Consulta de CUIT (A13) para autocompletado de datos
10
11
  - ✅ **Resiliente**: Maneja errores SSL ("dh key too small") y timeouts
11
12
  - ✅ **Moderno**: ESM + CJS nativo, Node.js 18+
12
13
 
@@ -23,283 +24,128 @@ bun add arca-sdk
23
24
 
24
25
  ## ⚡ Quick Start
25
26
  ```typescript
26
- import { WsaaService } from 'arca-sdk';
27
- import * as fs from 'node:fs';
28
-
29
- // 1. Crear servicio con tus certificados
30
- const wsaa = new WsaaService({
31
- environment: 'homologacion', // 'homologacion' o 'produccion'
32
- cuit: '20123456789',
33
- cert: fs.readFileSync('./cert.pem', 'utf-8'),
34
- key: fs.readFileSync('./key.pem', 'utf-8'),
35
- service: 'wsfe',
36
- });
37
-
38
- // 2. Obtener ticket (automático, con cache)
39
- const ticket = await wsaa.login();
40
-
41
- // 3. Usar token en otros servicios ARCA
42
- console.log('Token:', ticket.token);
43
- ```
44
-
45
- **Eso es todo.** No XML. No SOAP. No pain.
46
-
47
- ---
48
-
49
- ## 📖 Servicios Disponibles
50
-
51
- ### WSAA - Autenticación
52
- ```typescript
53
- import { WsaaService } from 'arca-sdk';
27
+ import { WsaaService, WsfeService } from 'arca-sdk';
54
28
 
55
- const wsaa = new WsaaService({
29
+ // 1. Configuración base
30
+ const config = {
56
31
  environment: 'homologacion',
57
32
  cuit: '20123456789',
58
33
  cert: '...certificado PEM...',
59
34
  key: '...clave privada PEM...',
60
- service: 'wsfe', // o 'wsmtxca', etc
61
- });
62
-
63
- const ticket = await wsaa.login();
64
- // Ticket válido por ~12 horas
65
- // Se renueva automáticamente
66
- ```
35
+ };
67
36
 
68
- ### WSFE - Facturación
69
- ```typescript
37
+ // 2. Emitir un Ticket C en dos líneas
70
38
  const wsfe = new WsfeService(config);
71
- const cae = await wsfe.emitirFacturaC({ items });
72
- console.log('CAE:', cae.cae);
73
- ```
74
-
75
- ---
39
+ const result = await wsfe.emitirTicketCSimple({ total: 1500 });
76
40
 
77
- ## 🔑 Certificados
78
-
79
- Necesitás certificados de ARCA en formato PEM:
80
- - `cert.pem`: Certificado X.509
81
- - `key.pem`: Clave privada
82
-
83
- **Homologación (testing):**
84
- 1. Ir a [ARCA Homologación](https://www.afip.gob.ar/ws/documentacion/certificados.asp)
85
- 2. Generar certificado de prueba
86
- 3. Descargar cert + key
87
-
88
- **Producción:**
89
- 1. Generar CSR con tu CUIT
90
- 2. Subir a ARCA
91
- 3. Descargar certificado firmado
92
-
93
- ---
94
-
95
- ## 🛠️ Ejemplos
96
-
97
- Ver carpeta [`/examples`](./examples):
98
- - [`autenticacion.ts`](./examples/autenticacion.ts) - Obtener ticket WSAA
99
- - [`quick-start.ts`](./examples/quick-start.ts) - Inicio rápido
100
-
101
- ---
102
-
103
- ## 🧪 Testing
104
- ```bash
105
- # Tests unitarios
106
- bun test
107
-
108
- # Build
109
- bun run build
41
+ console.log('CAE:', result.cae);
42
+ console.log('QR URL:', result.urlQr); // ← Ya viene integrado!
110
43
  ```
111
44
 
112
45
  ---
113
46
 
114
- ## 📝 TypeScript
115
-
116
- La SDK exporta todos los tipos:
117
- ```typescript
118
- import type {
119
- LoginTicket,
120
- WsaaConfig,
121
- Environment
122
- } from 'arca-sdk';
123
- ```
124
-
125
- Autocomplete completo en tu IDE. ✨
126
-
127
- ---
47
+ ## 👑 God Mode: Persistencia Automática
48
+ No manejes tickets manualmente. Pasale un `storage` al SDK y se encargará de guardar, recuperar y renovar el token solo cuando expire.
128
49
 
129
- ## ⚠️ Manejo de Errores
130
50
  ```typescript
131
- import { ArcaAuthError, ArcaValidationError } from 'arca-sdk';
132
-
133
- try {
134
- const ticket = await wsaa.login();
135
- } catch (error) {
136
- if (error instanceof ArcaAuthError) {
137
- console.error('Error de autenticación:', error.message);
138
- }
139
-
140
- if (error instanceof ArcaValidationError) {
141
- console.error('Configuración inválida:', error.message);
142
- }
143
- }
144
- ```
145
-
146
- Los errores incluyen contexto útil en `error.details`.
147
-
148
- #### Timeouts
149
- Por defecto, las peticiones tienen un timeout de **15 segundos**. Podés ajustarlo en la configuración:
150
- ```typescript
151
- const wsfe = new WsfeService({
51
+ const wsaa = new WsaaService({
152
52
  ...config,
153
- timeout: 30000 // 30 segundos
53
+ service: 'wsfe',
54
+ storage: {
55
+ get: async (key) => await db.token.findUnique({ where: { key } }),
56
+ save: async (key, data) => await db.token.upsert({ ... }),
57
+ }
154
58
  });
59
+
60
+ // El SDK chequea el storage antes de pedir un nuevo ticket a ARCA
61
+ const ticket = await wsaa.login();
155
62
  ```
156
63
 
157
64
  ---
158
65
 
159
- ### WSFE - Facturación Electrónica
66
+ ## 🔍 Consulta de Padrón (A13)
67
+ Obtené los datos de un cliente (Nombre, Domicilio, IVA) solo con su CUIT. Ideal para POS.
160
68
 
161
- #### Ticket C Simple (solo total)
162
69
  ```typescript
163
- import { WsaaService, WsfeService } from 'arca-sdk';
70
+ import { PadronService } from 'arca-sdk';
164
71
 
165
- // 1. Autenticar
166
- const wsaa = new WsaaService({ ... });
167
- const ticket = await wsaa.login();
72
+ const padron = new PadronService(config);
73
+ const { persona, error } = await padron.getPersona('30111111118');
168
74
 
169
- // 2. Crear servicio WSFE
170
- const wsfe = new WsfeService({
171
- environment: 'homologacion',
172
- cuit: '20123456789',
173
- ticket,
174
- puntoVenta: 4,
175
- });
176
-
177
- // 3. Emitir ticket (modo simple)
178
- const cae = await wsfe.emitirTicketCSimple({
179
- total: 3500
180
- });
181
-
182
- console.log('CAE:', cae.cae);
75
+ if (persona) {
76
+ console.log('Razón Social:', persona.razonSocial || `${persona.nombre} ${persona.apellido}`);
77
+ console.log('Provincia:', persona.domicilio[0].descripcionProvincia);
78
+ console.log('¿Es Inscripto?:', persona.esInscriptoIVA);
79
+ }
183
80
  ```
184
81
 
185
- #### Ticket C con Items
186
- ```typescript
187
- // Modo completo: con detalle de items
188
- const cae = await wsfe.emitirTicketC({
189
- items: [
190
- { descripcion: 'Producto 1', cantidad: 2, precioUnitario: 500 },
191
- { descripcion: 'Producto 2', cantidad: 1, precioUnitario: 1000 },
192
- ],
193
- });
82
+ ---
194
83
 
195
- // Los items NO se envían a ARCA
196
- // Pero se retornan en la respuesta para que los guardes
197
- console.log('Items:', cae.items);
198
- ```
84
+ ## 📱 Generador de QR Oficial
85
+ AFIP exige que los comprobantes impresos tengan un código QR. El SDK lo genera cumpliendo estrictamente con el formato oficial (JSON ordenado, Base64 URL-safe, etc).
199
86
 
200
- #### Factura B (IVA discriminado)
201
87
  ```typescript
202
- import { TipoDocumento } from 'arca-sdk';
203
-
204
- const cae = await wsfe.emitirFacturaB({
205
- items: [
206
- {
207
- descripcion: 'Servicio',
208
- cantidad: 10,
209
- precioUnitario: 1000,
210
- alicuotaIva: 21, // ← OBLIGATORIO
211
- },
212
- ],
213
- comprador: {
214
- tipoDocumento: TipoDocumento.CUIT,
215
- nroDocumento: '20987654321',
216
- },
217
- });
88
+ // 1. Integrado en WsfeService (Recomendado)
89
+ const result = await wsfe.emitirTicketCSimple({ total: 1500 });
90
+ console.log(result.urlQr);
218
91
 
219
- console.log('CAE:', cae.cae);
220
- console.log('IVA:', cae.iva);
92
+ // 2. O manual si lo necesitás por separado
93
+ import { generarUrlQR } from 'arca-sdk';
94
+ const urlQr = generarUrlQR(caeResponse, '20123456789', 1500);
221
95
  ```
222
96
 
223
- #### Factura A (RI a RI)
224
- ```typescript
225
- const cae = await wsfe.emitirFacturaA({
226
- items: [
227
- { descripcion: 'Producto', cantidad: 5, precioUnitario: 2000, alicuotaIva: 21 },
228
- ],
229
- comprador: {
230
- tipoDocumento: TipoDocumento.CUIT,
231
- nroDocumento: '20111111119',
232
- },
233
- });
234
- ```
97
+ ---
235
98
 
236
- #### Precios con IVA Incluido
237
- Si tus precios ya tienen el IVA (típico en venta minorista/POS), podés usar el flag `incluyeIva`:
238
- ```typescript
239
- const cae = await wsfe.emitirFacturaB({
240
- incluyeIva: true, // ← El SDK calculará el neto y el IVA automáticamente
241
- items: [
242
- { descripcion: 'Producto', cantidad: 1, precioUnitario: 1210, alicuotaIva: 21 },
243
- ],
244
- // ... comprador
245
- });
246
- // Internamente enviará: Subtotal: 1000, IVA: 210, Total: 1210
247
- ```
99
+ ## 🖨️ Generación de PDF
100
+ Para mantener la SDK ligera, no incluimos generadores de PDF (como `jspdf`) en el core.
101
+ **Tip:** La URL del QR (`urlQr`) apunta a la vista oficial de ARCA que ya es 100% imprimible y legal. Si necesitás PDF local, podés usar los datos de `CAEResponse` con tu librería favorita.
248
102
 
249
103
  ---
250
104
 
251
- ## 📱 Generador de QR Oficial
252
- AFIP exige que los comprobantes impresos tengan un código QR con los datos fiscales. La SDK lo genera por vos:
105
+ ## 🩺 Chequeo de Salud
106
+ Verificá si los servidores de ARCA están online antes de intentar facturar.
253
107
 
254
108
  ```typescript
255
- import { generarUrlQR } from 'arca-sdk';
256
-
257
- // Usá la respuesta del CAE para generar la URL del QR
258
- const urlQr = generarUrlQR(cae);
259
-
260
- console.log('URL para QR:', urlQr);
261
- // Output: https://www.afip.gob.ar/fe/qr/?p=eyJ2ZXIiOjEsImZlY2hh...
109
+ const status = await wsfe.checkStatus();
110
+ console.log('AppServer:', status.appServer); // 'OK'
262
111
  ```
263
112
 
264
- Esta URL la podés pasar a cualquier librería de generación de imágenes QR.
265
-
266
- ---
267
-
268
113
  ---
269
114
 
270
- ## 🎯 Tipos de Comprobante
115
+ ## 📝 Servicios y Comprobantes
271
116
 
272
- | Tipo | Uso | IVA Discriminado | Items requeridos |
273
- |------|-----|------------------|------------------|
274
- | **Ticket C** | Consumidor final | No | Opcional (solo local) |
275
- | **Factura C** | Consumidor final | No | Opcional |
276
- | **Factura B** | Monotributo RI | | **Obligatorio** |
277
- | **Factura A** | RI → RI | Sí | **Obligatorio** |
117
+ | Clase | Servicio ARCA | Descripción |
118
+ |-------|---------------|-------------|
119
+ | `WsaaService` | `wsaa` | Autenticación y Autorización |
120
+ | `WsfeService` | `wsfev1` | Facturación Electrónica (A, B, C) |
121
+ | `PadronService` | `ws_sr_padron_a13` | Consulta de datos de contribuyentes |
278
122
 
279
- **Importante:** Factura B y A requieren `alicuotaIva` en cada item.
123
+ ### Comprobantes soportados en `WsfeService`:
124
+ - `emitirTicketCSimple()`: Rápido para Consumidor Final.
125
+ - `emitirTicketC()`: Con detalle de items.
126
+ - `emitirFacturaB()`: Para Responsables Inscriptos o Facturas > $ limite.
127
+ - `emitirFacturaA()`: Con discriminación de IVA.
280
128
 
281
129
  ---
282
130
 
283
- ## 🤝 Contribuir
131
+ ## 🛠️ Desarrollo y Tests
132
+ ```bash
133
+ # Correr tests con mocks de ARCA
134
+ bun test
135
+
136
+ # Verificar tipos
137
+ bun run lint
284
138
 
285
- Contribuciones bienvenidas! Ver [CONTRIBUTING.md](./CONTRIBUTING.md)
139
+ # Build para producción (CJS + ESM)
140
+ bun run build
141
+ ```
286
142
 
287
143
  ---
288
144
 
289
145
  ## 📄 Licencia
290
-
291
146
  MIT © [Marcela Borgarello](https://github.com/marcelaborgarello)
292
147
 
293
148
  ---
294
149
 
295
- ## 🔗 Links
296
-
297
- - [Documentación ARCA](https://www.afip.gob.ar/ws/)
298
- - [Issues](https://github.com/marcelaborgarello/arca-sdk/issues)
299
- - [NPM](https://www.npmjs.com/package/arca-sdk)
300
-
301
- ---
302
-
303
150
  **Hecho con ❤️ en Argentina 🇦🇷**
304
-
305
151
  *Porque integrar con ARCA no tiene por qué ser un infierno.*
package/dist/index.cjs CHANGED
@@ -68,10 +68,11 @@ function getPadronEndpoint(environment) {
68
68
 
69
69
  // src/types/common.ts
70
70
  var ArcaError = class extends Error {
71
- constructor(message, code, details) {
71
+ constructor(message, code, details, hint) {
72
72
  super(message);
73
73
  this.code = code;
74
74
  this.details = details;
75
+ this.hint = hint;
75
76
  this.name = "ArcaError";
76
77
  }
77
78
  };
@@ -549,6 +550,54 @@ function redondear(valor) {
549
550
  return Math.round(valor * 100) / 100;
550
551
  }
551
552
 
553
+ // src/utils/qr.ts
554
+ function generarUrlQR(caeResponse, cuitEmisor, total, comprador) {
555
+ const cleanCuit = cuitEmisor.replace(/\D/g, "");
556
+ const cleanCae = caeResponse.cae.replace(/\D/g, "");
557
+ const fDate = caeResponse.fecha;
558
+ const fechaFormat = fDate.length === 8 ? `${fDate.substring(0, 4)}-${fDate.substring(4, 6)}-${fDate.substring(6, 8)}` : fDate;
559
+ const docTipo = comprador?.tipoDocumento || 99 /* CONSUMIDOR_FINAL */;
560
+ const docNro = comprador?.nroDocumento ? comprador.nroDocumento.replace(/\D/g, "") : "0";
561
+ const qrObj = {
562
+ ver: 1,
563
+ fecha: fechaFormat,
564
+ cuit: Number(cleanCuit),
565
+ ptoVta: Number(caeResponse.puntoVenta),
566
+ tipoCmp: Number(caeResponse.tipoComprobante),
567
+ nroCmp: Number(caeResponse.nroComprobante),
568
+ importe: Number(parseFloat(total.toFixed(2))),
569
+ moneda: "PES",
570
+ ctz: 1
571
+ };
572
+ if (docTipo !== 99 /* CONSUMIDOR_FINAL */ || Number(docNro) > 0) {
573
+ qrObj.tipoDocRec = Number(docTipo);
574
+ qrObj.nroDocRec = Number(docNro);
575
+ }
576
+ qrObj.tipoCodAut = "E";
577
+ qrObj.codAut = Number(cleanCae);
578
+ const jsonString = JSON.stringify(qrObj);
579
+ let base64 = typeof Buffer !== "undefined" ? Buffer.from(jsonString).toString("base64") : btoa(jsonString);
580
+ return `https://www.afip.gob.ar/fe/qr/?p=${encodeURIComponent(base64)}`;
581
+ }
582
+
583
+ // src/constants/errors.ts
584
+ var ARCA_ERROR_HINTS = {
585
+ // Generales / Auth
586
+ 501: "Error de autenticaci\xF3n: El certificado puede haber expirado o la relaci\xF3n CUIT/Servicio no est\xE1 dada de alta en la web de AFIP.",
587
+ 502: "Error de autenticaci\xF3n: El ticket de acceso (TA) ya no es v\xE1lido o est\xE1 mal formado.",
588
+ 1e3: "El CUIT informado no es v\xE1lido o no corresponde al certificado usado.",
589
+ // WSFE (Facturación)
590
+ 10015: "Factura B: El importe total es superior al l\xEDmite para consumidores finales an\xF3nimos. Identific\xE1 al comprador.",
591
+ 10016: "Factura C: El CUIT informado como receptor no es v\xE1lido.",
592
+ 10048: 'Punto de Venta inv\xE1lido: Asegurate de que el punto de venta est\xE9 dado de alta como "Factuweb" o "Webservice" en AFIP.',
593
+ 600: "No se pudo autorizar el comprobante. Revis\xE1 las observaciones para m\xE1s detalle.",
594
+ // Padrón
595
+ "PADRON_ERROR": "El servicio de Padr\xF3n suele ser inestable en homologaci\xF3n. Reintent\xE1 en unos minutos."
596
+ };
597
+ function getArcaHint(code) {
598
+ return ARCA_ERROR_HINTS[code];
599
+ }
600
+
552
601
  // src/services/wsfe.ts
553
602
  var WsfeService = class _WsfeService {
554
603
  config;
@@ -797,10 +846,17 @@ var WsfeService = class _WsfeService {
797
846
  }
798
847
  const responseXml = await response.text();
799
848
  const result = await this.parseCAEResponse(responseXml);
849
+ const urlQr = generarUrlQR(
850
+ result,
851
+ this.config.cuit,
852
+ total,
853
+ request.comprador
854
+ );
800
855
  return {
801
856
  ...result,
802
857
  items: request.items,
803
- iva: request.ivaData
858
+ iva: request.ivaData,
859
+ urlQr
804
860
  };
805
861
  }
806
862
  /**
@@ -826,7 +882,13 @@ var WsfeService = class _WsfeService {
826
882
  const data = result?.Envelope?.Body?.FECompUltimoAutorizadoResponse?.FECompUltimoAutorizadoResult;
827
883
  if (data?.Errors) {
828
884
  const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
829
- throw new ArcaError(`Error ARCA: ${error?.Msg || "Error desconocido"}`, "ARCA_ERROR", data.Errors);
885
+ const code = error?.Code || "UNKNOWN";
886
+ throw new ArcaError(
887
+ `Error ARCA: ${error?.Msg || "Error desconocido"}`,
888
+ "ARCA_ERROR",
889
+ data.Errors,
890
+ getArcaHint(code)
891
+ );
830
892
  }
831
893
  const nro = data?.CbteNro;
832
894
  return typeof nro === "number" ? nro + 1 : 1;
@@ -935,7 +997,13 @@ var WsfeService = class _WsfeService {
935
997
  }
936
998
  if (data.Errors) {
937
999
  const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
938
- throw new ArcaError(`Error ARCA: ${error?.Msg || "Error desconocido"}`, "ARCA_ERROR", data.Errors);
1000
+ const code = error?.Code || "UNKNOWN";
1001
+ throw new ArcaError(
1002
+ `Error ARCA: ${error?.Msg || "Error desconocido"}`,
1003
+ "ARCA_ERROR",
1004
+ data.Errors,
1005
+ getArcaHint(code)
1006
+ );
939
1007
  }
940
1008
  const cab = data.FeCabResp;
941
1009
  const det = Array.isArray(data.FeDetResp.FECAEDetResponse) ? data.FeDetResp.FECAEDetResponse[0] : data.FeDetResp.FECAEDetResponse;
@@ -1034,7 +1102,7 @@ var PadronService = class {
1034
1102
  if (!response) {
1035
1103
  const fault = body.Fault;
1036
1104
  if (fault) {
1037
- return { error: fault.faultstring || "Error desconocido en AFIP" };
1105
+ return { error: fault.faultstring || "Error desconocido en ARCA" };
1038
1106
  }
1039
1107
  return { error: "No se encontraron datos para el CUIT informado" };
1040
1108
  }
@@ -1053,6 +1121,8 @@ var PadronService = class {
1053
1121
  razonSocial: p.razonSocial,
1054
1122
  estadoClave: p.estadoClave,
1055
1123
  domicilio: this.mapDomicilios(p.domicilio),
1124
+ actividad: this.mapActividades(p.actividad),
1125
+ impuesto: this.mapImpuestos(p.impuesto),
1056
1126
  descripcionActividadPrincipal: p.descripcionActividadPrincipal,
1057
1127
  esInscriptoIVA: this.checkImpuesto(p, 30),
1058
1128
  // 30 = IVA
@@ -1065,7 +1135,7 @@ var PadronService = class {
1065
1135
  }
1066
1136
  mapDomicilios(d) {
1067
1137
  if (!d) return [];
1068
- const list = Array.isArray(d) ? d : [d];
1138
+ const list = this.ensureArray(d);
1069
1139
  return list.map((item) => ({
1070
1140
  direccion: item.direccion,
1071
1141
  localidad: item.localidad,
@@ -1075,43 +1145,39 @@ var PadronService = class {
1075
1145
  tipoDomicilio: item.tipoDomicilio
1076
1146
  }));
1077
1147
  }
1148
+ mapActividades(a) {
1149
+ if (!a) return [];
1150
+ const list = this.ensureArray(a);
1151
+ return list.map((item) => ({
1152
+ idActividad: Number(item.idActividad),
1153
+ descripcion: item.descripcion,
1154
+ orden: Number(item.orden),
1155
+ periodo: Number(item.periodo)
1156
+ }));
1157
+ }
1158
+ mapImpuestos(i) {
1159
+ if (!i) return [];
1160
+ const list = this.ensureArray(i);
1161
+ return list.map((item) => ({
1162
+ idImpuesto: Number(item.idImpuesto),
1163
+ descripcion: item.descripcion,
1164
+ periodo: Number(item.periodo)
1165
+ }));
1166
+ }
1078
1167
  checkImpuesto(p, id) {
1079
1168
  const impuestos = p.impuesto;
1080
1169
  if (!impuestos) return false;
1081
- const list = Array.isArray(impuestos) ? impuestos : [impuestos];
1170
+ const list = this.ensureArray(impuestos);
1082
1171
  return list.some((i) => Number(i.idImpuesto) === id);
1083
1172
  }
1084
- };
1085
-
1086
- // src/utils/qr.ts
1087
- function generarUrlQR(caeResponse, cuitEmisor, total, comprador) {
1088
- const cleanCuit = cuitEmisor.replace(/\D/g, "");
1089
- const cleanCae = caeResponse.cae.replace(/\D/g, "");
1090
- const fDate = caeResponse.fecha;
1091
- const fechaFormat = fDate.length === 8 ? `${fDate.substring(0, 4)}-${fDate.substring(4, 6)}-${fDate.substring(6, 8)}` : fDate;
1092
- const docTipo = comprador?.tipoDocumento || 99 /* CONSUMIDOR_FINAL */;
1093
- const docNro = comprador?.nroDocumento ? comprador.nroDocumento.replace(/\D/g, "") : "0";
1094
- const qrObj = {
1095
- ver: 1,
1096
- fecha: fechaFormat,
1097
- cuit: Number(cleanCuit),
1098
- ptoVta: Number(caeResponse.puntoVenta),
1099
- tipoCmp: Number(caeResponse.tipoComprobante),
1100
- nroCmp: Number(caeResponse.nroComprobante),
1101
- importe: Number(parseFloat(total.toFixed(2))),
1102
- moneda: "PES",
1103
- ctz: 1
1104
- };
1105
- if (docTipo !== 99 /* CONSUMIDOR_FINAL */ || Number(docNro) > 0) {
1106
- qrObj.tipoDocRec = Number(docTipo);
1107
- qrObj.nroDocRec = Number(docNro);
1173
+ /**
1174
+ * Helper para normalizar la respuesta de XML que puede ser objeto único o array
1175
+ */
1176
+ ensureArray(data) {
1177
+ if (data === void 0 || data === null) return [];
1178
+ return Array.isArray(data) ? data : [data];
1108
1179
  }
1109
- qrObj.tipoCodAut = "E";
1110
- qrObj.codAut = Number(cleanCae);
1111
- const jsonString = JSON.stringify(qrObj);
1112
- let base64 = typeof Buffer !== "undefined" ? Buffer.from(jsonString).toString("base64") : btoa(jsonString);
1113
- return `https://www.afip.gob.ar/fe/qr/?p=${encodeURIComponent(base64)}`;
1114
- }
1180
+ };
1115
1181
  // Annotate the CommonJS export names for ESM import in node:
1116
1182
  0 && (module.exports = {
1117
1183
  ArcaAuthError,