arca-sdk 1.0.1 → 1.0.3

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,44 +1,69 @@
1
+ <div align="center">
2
+
1
3
  # 🇦🇷 arca-sdk
2
4
 
3
- **La SDK moderna de ARCA (ex-AFIP) que no te rompe las bolas.**
5
+ **La SDK de ARCA (ex-AFIP) que querías que alguien hiciera.**
6
+
7
+ TypeScript nativo · API limpia en inglés · Tokens automáticos · QR oficial · Padrón A13
8
+
9
+ [![npm version](https://img.shields.io/npm/v/arca-sdk?color=CB3837&label=npm)](https://www.npmjs.com/package/arca-sdk)
10
+ [![npm downloads](https://img.shields.io/npm/dm/arca-sdk?color=CB3837)](https://www.npmjs.com/package/arca-sdk)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
12
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
13
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## ¿Por qué arca-sdk?
20
+
21
+ La mayoría de las librerías de AFIP/ARCA para Node.js son:
22
+ - Ports de código PHP/Java sin tipos
23
+ - Sin soporte para el Padrón A13
24
+ - Sin manejo de tokens (te obligan a gestionarlos a mano)
25
+ - Sin QR oficial
26
+ - Sin mantenimiento activo
4
27
 
5
- SDK en TypeScript para integración con servicios de ARCA:
6
- - ✅ **Type-safe**: TypeScript strict mode, 100% tipado
7
- - ✅ **Simple**: API en inglés, sin XML manual, sin magia negra
8
- - ✅ **Automático**: Cache de tokens + persistencia opcional (God Mode)
9
- - ✅ **Fiscal**: Generador de QR oficial de ARCA ultra-robusto
10
- - ✅ **Padrón**: Consulta de CUIT (A13) para autocompletado de datos
11
- - ✅ **Resiliente**: Maneja errores SSL ("dh key too small") y timeouts
12
- - ✅ **Moderno**: ESM + CJS nativo, Node.js 18+, Bun compatible
28
+ `arca-sdk` fue construida desde cero en TypeScript moderno, probada contra producción real, y documenta quirks del spec oficial que ninguna otra librería menciona (como el `encodeURIComponent` que rompe el scanner de ARCA).
13
29
 
14
30
  ---
15
31
 
16
- ## 🚀 Instalación
32
+ ## Instalación
33
+
17
34
  ```bash
35
+ # npm
18
36
  npm install arca-sdk
19
- # o
37
+
38
+ # bun
20
39
  bun add arca-sdk
40
+
41
+ # pnpm
42
+ pnpm add arca-sdk
21
43
  ```
22
44
 
45
+ **Requisitos:** Node.js 18+ · TypeScript 5+ (optional but recommended) · Bun compatible
46
+
23
47
  ---
24
48
 
25
- ## Quick Start
49
+ ## Quick Start — 5 minutos y estás facturando
50
+
26
51
  ```typescript
27
- import { WsaaService, WsfeService } from 'arca-sdk';
28
52
  import * as fs from 'fs';
53
+ import { WsaaService, WsfeService } from 'arca-sdk';
29
54
 
30
- // 1. Autenticar con WSAA
55
+ // 1. Autenticación con WSAA (se renueva automáticamente)
31
56
  const wsaa = new WsaaService({
32
- environment: 'homologacion',
57
+ environment: 'homologacion', // 'produccion' cuando estés listo
33
58
  cuit: '20123456789',
34
59
  cert: fs.readFileSync('cert.pem', 'utf-8'),
35
- key: fs.readFileSync('key.pem', 'utf-8'),
60
+ key: fs.readFileSync('key.pem', 'utf-8'),
36
61
  service: 'wsfe',
37
62
  });
38
63
 
39
64
  const ticket = await wsaa.login();
40
65
 
41
- // 2. Crear servicio de facturación
66
+ // 2. Servicio de facturación
42
67
  const wsfe = new WsfeService({
43
68
  environment: 'homologacion',
44
69
  cuit: '20123456789',
@@ -46,183 +71,276 @@ const wsfe = new WsfeService({
46
71
  pointOfSale: 4,
47
72
  });
48
73
 
49
- // 3. Emitir un Ticket C
74
+ // 3. Emitir Ticket C — una línea
50
75
  const result = await wsfe.issueSimpleReceipt({ total: 1500 });
51
76
 
52
- console.log('CAE:', result.cae);
53
- console.log('QR URL:', result.qrUrl); // ← Ya viene integrado!
77
+ console.log('CAE:', result.cae); // '75157992335329'
78
+ console.log('Vto:', result.caeExpiry); // '20260302'
79
+ console.log('QR:', result.qrUrl); // 'https://www.afip.gob.ar/fe/qr/?p=...'
54
80
  ```
55
81
 
82
+ > Los certificados se obtienen en el [portal de ARCA](https://auth.afip.gob.ar/contribuyente_/login.xhtml) (CLAVE FISCAL nivel 3+).
83
+
56
84
  ---
57
85
 
58
- ## 👑 God Mode: Persistencia Automática
59
- No manejes tokens manualmente. Pasale un `storage` al SDK y se encargará de guardar, recuperar y renovar el TA solo cuando expire.
86
+ ## Funcionalidades
60
87
 
61
- ```typescript
62
- const wsaa = new WsaaService({
63
- ...config,
64
- service: 'wsfe',
65
- storage: {
66
- get: async (cuit, env) => await db.token.findUnique({ where: { cuit, env } }),
67
- save: async (cuit, env, ticket) => await db.token.upsert({ ... }),
68
- }
69
- });
88
+ ### ✅ Servicios soportados
70
89
 
71
- // El SDK chequea el storage antes de pedir un nuevo ticket a ARCA
72
- const ticket = await wsaa.login();
73
- ```
74
-
75
- ---
90
+ | Servicio | Descripción | Estado |
91
+ |----------|-------------|--------|
92
+ | **WSAA** | Autenticación y Autorización | ✅ Completo |
93
+ | **WSFE v1** | Facturación Electrónica (A, B, C) | ✅ Completo |
94
+ | **Padrón A13** | Consulta de datos de contribuyentes | ✅ Completo |
76
95
 
77
- ## 🔍 Consulta de Padrón (A13)
78
- Obtené los datos de un contribuyente (nombre, domicilio, condición IVA) solo con su CUIT.
96
+ ### Tipos de comprobantes
79
97
 
80
- ```typescript
81
- import { PadronService } from 'arca-sdk';
98
+ | Método | Comprobante | Cuándo usarlo |
99
+ |--------|-------------|---------------|
100
+ | `issueSimpleReceipt()` | Ticket C | Monto total, sin detalle. Ideal para POS simple |
101
+ | `issueReceipt()` | Ticket C + items | Con detalle de productos guardado localmente |
102
+ | `issueInvoiceC()` | Factura C | Monotributistas a consumidor final |
103
+ | `issueInvoiceB()` | Factura B | Responsable Inscripto a consumidor final / Monotributo |
104
+ | `issueInvoiceA()` | Factura A | Responsable Inscripto a Responsable Inscripto |
82
105
 
83
- const padron = new PadronService(config);
84
- const { taxpayer, error } = await padron.getTaxpayer('30111111118');
106
+ ### Consultas disponibles
85
107
 
86
- if (taxpayer) {
87
- const name = taxpayer.companyName || `${taxpayer.firstName} ${taxpayer.lastName}`;
88
- console.log('Nombre:', name);
89
- console.log('Provincia:', taxpayer.addresses[0].province);
90
- console.log('¿Inscripto IVA?:', taxpayer.isVATRegistered);
91
- console.log('¿Monotributista?:', taxpayer.isMonotax);
92
- }
93
- ```
108
+ | Método | Descripción |
109
+ |--------|-------------|
110
+ | `wsfe.getInvoice(type, n)` | Consulta un comprobante ya emitido (FECompConsultar) |
111
+ | `wsfe.getPointsOfSale()` | Lista puntos de venta habilitados (FEParamGetPtosVenta) |
112
+ | `WsfeService.checkStatus()` | Estado de los servidores de ARCA (FEDummy) |
113
+ | `padron.getTaxpayer(cuit)` | Datos del contribuyente — nombre, domicilio, condición IVA |
94
114
 
95
115
  ---
96
116
 
97
- ## 📱 Comprobantes soportados
117
+ ## Ejemplos
98
118
 
99
- ```typescript
100
- // Ticket C — Consumidor Final (solo total)
101
- const cae = await wsfe.issueSimpleReceipt({ total: 1500 });
119
+ ### Ticket C con detalle de items
102
120
 
103
- // Ticket C — con detalle de items (los items se guardan localmente, no van a ARCA)
104
- const cae = await wsfe.issueReceipt({
121
+ ```typescript
122
+ const result = await wsfe.issueReceipt({
105
123
  items: [
106
- { description: 'Café con leche', quantity: 2, unitPrice: 750 },
107
- { description: 'Medialunas x3', quantity: 1, unitPrice: 500 },
124
+ { description: 'Café con leche', quantity: 2, unitPrice: 750 },
125
+ { description: 'Medialunas x4', quantity: 1, unitPrice: 600 },
108
126
  ],
109
127
  });
110
128
 
111
- // Factura C Consumidor Final con items
112
- const cae = await wsfe.issueInvoiceC({ items: [...] });
129
+ console.log('Items en respuesta:', result.items?.length); // 2
130
+ console.log('QR URL:', result.qrUrl);
131
+ ```
132
+
133
+ ### Factura B con IVA discriminado
134
+
135
+ ```typescript
136
+ import { TaxIdType } from 'arca-sdk';
113
137
 
114
- // Factura B con IVA discriminado (requiere vatRate en cada item)
115
- const cae = await wsfe.issueInvoiceB({
138
+ const result = await wsfe.issueInvoiceB({
116
139
  items: [
117
140
  { description: 'Servicio de diseño', quantity: 10, unitPrice: 1000, vatRate: 21 },
141
+ { description: 'Hosting mensual', quantity: 1, unitPrice: 5000, vatRate: 21 },
118
142
  ],
119
- buyer: { docType: TaxIdType.CUIT, docNumber: '20987654321' },
143
+ buyer: {
144
+ docType: TaxIdType.CUIT,
145
+ docNumber: '20987654321',
146
+ },
120
147
  });
121
148
 
122
- // Factura A entre Responsables Inscriptos
123
- const cae = await wsfe.issueInvoiceA({
124
- items: [...],
125
- buyer: { docType: TaxIdType.CUIT, docNumber: '30123456789' },
149
+ // VAT breakdown (required by ARCA for A/B invoices)
150
+ result.vat?.forEach(v => {
151
+ console.log(`IVA ${v.rate}%: base $${v.taxBase} → $${v.amount}`);
126
152
  });
127
153
  ```
128
154
 
129
- ---
130
-
131
- ## 🔎 Consultar un comprobante ya emitido
155
+ ### Consulta de Padrón A13
132
156
 
133
157
  ```typescript
134
- const invoice = await wsfe.getInvoice(InvoiceType.TICKET_C, 42);
135
- console.log('Total:', invoice.total);
136
- console.log('CAE:', invoice.cae);
158
+ import { PadronService } from 'arca-sdk';
159
+
160
+ const padron = new PadronService({
161
+ environment: 'homologacion',
162
+ cuit: '20123456789',
163
+ cert: fs.readFileSync('cert.pem', 'utf-8'),
164
+ key: fs.readFileSync('key.pem', 'utf-8'),
165
+ });
166
+
167
+ const { taxpayer, error } = await padron.getTaxpayer('30111111118');
168
+ if (taxpayer) {
169
+ const name = taxpayer.companyName || `${taxpayer.firstName} ${taxpayer.lastName}`;
170
+ console.log('Nombre:', name);
171
+ console.log('Provincia:', taxpayer.addresses[0]?.province);
172
+ console.log('¿IVA?:', taxpayer.isVATRegistered);
173
+ console.log('¿Mono?:', taxpayer.isMonotax);
174
+ }
137
175
  ```
138
176
 
139
- ---
177
+ ### God Mode: persistencia automática de tokens
140
178
 
141
- ## 🏪 Listar puntos de venta
179
+ Pasale un adaptador `storage` y el SDK gestiona el ciclo de vida del ticket solo — incluyendo renovación automática al expirar.
142
180
 
143
181
  ```typescript
144
- const points = await wsfe.getPointsOfSale();
145
- points.forEach(p => {
146
- console.log(`PdV ${p.number}: ${p.type} — ${p.isBlocked ? '🔴 Bloqueado' : '✅ Activo'}`);
182
+ const wsaa = new WsaaService({
183
+ ...config,
184
+ service: 'wsfe',
185
+ storage: {
186
+ // Leé el ticket guardado (de DB, Redis, filesystem, lo que sea)
187
+ get: async (cuit, env) => {
188
+ const row = await db.token.findUnique({ where: { cuit, env } });
189
+ return row ? { token: row.token, sign: row.sign, ... } : null;
190
+ },
191
+ // Guardá el ticket nuevo
192
+ save: async (cuit, env, ticket) => {
193
+ await db.token.upsert({ ... });
194
+ },
195
+ }
147
196
  });
148
- ```
149
197
 
150
- ---
198
+ // Desde ahora, .login() va a buscar el token en la DB antes de pedirle uno a ARCA
199
+ const ticket = await wsaa.login();
200
+ ```
151
201
 
152
- ## 📱 QR Oficial de ARCA
202
+ ### QR oficial de ARCA
153
203
 
154
204
  ```typescript
155
- // 1. Integrado automáticamente en todos los métodos de emisión
205
+ import { generateQRUrl } from 'arca-sdk';
206
+
207
+ // 1. Ya viene integrado en todos los métodos de emisión:
156
208
  const result = await wsfe.issueSimpleReceipt({ total: 1500 });
157
- console.log(result.qrUrl);
209
+ console.log(result.qrUrl); // listo para embeber en un generador de QR
158
210
 
159
- // 2. O generalo manualmente
160
- import { generateQRUrl } from 'arca-sdk';
161
- const url = generateQRUrl(caeResponse, '20123456789', 1500);
211
+ // 2. O generalo a mano si ya tenés la respuesta:
212
+ const url = generateQRUrl(caeResponse, '20123456789', 1500.00);
162
213
  ```
163
214
 
164
- ---
215
+ > **Nota:** La URL usa base64 crudo sin `encodeURIComponent`. Es un quirk documentado del spec de ARCA — su scanner no acepta caracteres URL-encoded.
165
216
 
166
- ## 🩺 Estado de los servidores
217
+ ### Manejo de errores
218
+
219
+ Todos los errores son instancias tipadas de `ArcaError`, con un campo `hint` que te dice qué hacer:
167
220
 
168
221
  ```typescript
169
- // Sin necesidad de autenticación
170
- const status = await WsfeService.checkStatus('produccion');
171
- console.log('AppServer:', status.appServer); // 'OK'
172
- console.log('DbServer:', status.dbServer);
173
- console.log('AuthServer:', status.authServer);
222
+ import { ArcaError, ArcaAuthError, ArcaValidationError, ArcaNetworkError } from 'arca-sdk';
223
+
224
+ try {
225
+ const result = await wsfe.issueSimpleReceipt({ total: 1500 });
226
+ } catch (error) {
227
+ if (error instanceof ArcaAuthError) {
228
+ // Token expirado, certificado inválido, etc.
229
+ console.error('Auth error:', error.message);
230
+ console.log('Hint:', error.hint); // → "El certificado puede haber expirado..."
231
+ } else if (error instanceof ArcaValidationError) {
232
+ // Datos inválidos antes de llamar a ARCA
233
+ console.error('Validation:', error.message, error.details);
234
+ } else if (error instanceof ArcaNetworkError) {
235
+ // Timeout, error HTTP
236
+ console.error('Network:', error.message);
237
+ } else if (error instanceof ArcaError) {
238
+ // Error semántico de ARCA (código de error en la respuesta SOAP)
239
+ console.error(`ARCA Error [${error.code}]:`, error.message);
240
+ console.log('Hint:', error.hint); // Pista específica por código de error
241
+ }
242
+ }
174
243
  ```
175
244
 
176
245
  ---
177
246
 
178
- ## 📝 Referencia de Servicios
247
+ ## Compatibilidad
179
248
 
180
- | Clase | Servicio ARCA | Descripción |
181
- |-------|---------------|-------------|
182
- | `WsaaService` | `wsaa` | Autenticación y Autorización |
183
- | `WsfeService` | `wsfev1` | Facturación Electrónica (A, B, C) |
184
- | `PadronService` | `ws_sr_padron_a13` | Consulta de datos de contribuyentes |
249
+ | Runtime | Versión mínima | Estado |
250
+ |---------|---------------|--------|
251
+ | Node.js | 18 LTS | Soportado |
252
+ | Node.js | 20 LTS | Soportado |
253
+ | Node.js | 22 LTS | Soportado |
254
+ | Bun | 1.x | ✅ Soportado |
255
+ | Deno | — | ⚠️ Sin probar |
256
+ | Browser | — | ❌ No soportado (requiere `node:https`) |
257
+
258
+ > El SDK maneja automáticamente los errores SSL de los servidores de ARCA ("dh key too small") mediante configuración custom del `https.Agent`.
259
+
260
+ ---
185
261
 
186
- ### Enums disponibles
262
+ ## Tipos exportados
187
263
 
188
264
  ```typescript
265
+ // Servicios
266
+ import { WsaaService, WsfeService, PadronService } from 'arca-sdk';
267
+
268
+ // Enums
189
269
  import { InvoiceType, BillingConcept, TaxIdType } from 'arca-sdk';
190
270
 
191
- InvoiceType.FACTURA_A // 1
192
- InvoiceType.FACTURA_B // 6
193
- InvoiceType.FACTURA_C // 11
194
- InvoiceType.TICKET_C // 83
271
+ // Tipos de configuración
272
+ import type { WsaaConfig, WsfeConfig, TaxpayerServiceConfig } from 'arca-sdk';
273
+
274
+ // Tipos de respuesta
275
+ import type { CAEResponse, InvoiceDetails, PointOfSale, ServiceStatus } from 'arca-sdk';
276
+ import type { TaxpayerResponse, Taxpayer, Address, Activity, TaxRecord } from 'arca-sdk';
277
+
278
+ // Items de factura
279
+ import type { InvoiceItem, Buyer, IssueInvoiceRequest } from 'arca-sdk';
195
280
 
196
- BillingConcept.PRODUCTS // 1
197
- BillingConcept.SERVICES // 2
198
- BillingConcept.PRODUCTS_AND_SERVICES // 3
281
+ // Storage
282
+ import type { TokenStorage, LoginTicket } from 'arca-sdk';
199
283
 
200
- TaxIdType.CUIT // 80
201
- TaxIdType.DNI // 96
202
- TaxIdType.FINAL_CONSUMER // 99
284
+ // Errores
285
+ import { ArcaError, ArcaAuthError, ArcaValidationError, ArcaNetworkError } from 'arca-sdk';
286
+
287
+ // QR
288
+ import { generateQRUrl } from 'arca-sdk';
203
289
  ```
204
290
 
205
291
  ---
206
292
 
207
- ## 🛠️ Desarrollo y Tests
293
+ ## Desarrollo
208
294
 
209
295
  ```bash
210
- # Correr tests (unit + mocks de ARCA)
296
+ # Clonar e instalar
297
+ git clone https://github.com/marcelaborgarello/arca-sdk
298
+ cd arca-sdk
299
+ bun install
300
+
301
+ # Tests
211
302
  bun test
212
303
 
213
- # Verificar tipos TypeScript
304
+ # Verificar tipos
214
305
  bun run lint
215
306
 
216
- # Build para producción (CJS + ESM + .d.ts)
307
+ # Build (CJS + ESM + .d.ts)
217
308
  bun run build
218
309
  ```
219
310
 
311
+ ### Tests disponibles
312
+
313
+ | Suite | Archivo | Qué cubre |
314
+ |-------|---------|-----------|
315
+ | Cálculos | `calculations.test.ts` | IVA, subtotales, totales con y sin IVA incluido |
316
+ | QR | `qr.test.ts` | Generación de URL, limpieza de CUIT/CAE, campo comprador |
317
+ | XML | `xml.test.ts` | Construcción de TRA, parsing WSAA, validación de CUIT |
318
+ | Padrón | `padron.test.ts` | Parsing de respuesta, CUIT not found, condición IVA |
319
+ | WSFE | `wsfe.test.ts` | issueSimpleReceipt, issueReceipt, issueInvoiceB, checkStatus |
320
+
321
+ ---
322
+
323
+ ## Roadmap
324
+
325
+ - [ ] Soporte WSMTXCA (Factura de Crédito Electrónica MiPyME)
326
+ - [ ] Soporte WSCT (Turismo)
327
+ - [ ] Método `consultar()` para servicios adicionales del Padrón
328
+ - [ ] Opción de exportar a PDF (recibo y factura)
329
+
220
330
  ---
221
331
 
222
- ## 📄 Licencia
332
+ ## Licencia
333
+
223
334
  MIT © [Marcela Borgarello](https://github.com/marcelaborgarello)
224
335
 
225
336
  ---
226
337
 
338
+ <div align="center">
339
+
227
340
  **Hecho con ❤️ en Argentina 🇦🇷**
341
+
228
342
  *Porque integrar con ARCA no tiene por qué ser un infierno.*
343
+
344
+ [npm](https://www.npmjs.com/package/arca-sdk) · [GitHub](https://github.com/marcelaborgarello/arca-sdk) · [CHANGELOG](CHANGELOG.md)
345
+
346
+ </div>
package/dist/index.cjs CHANGED
@@ -555,7 +555,7 @@ function round(value) {
555
555
  function generateQRUrl(caeResponse, issuerCUIT, total, buyer) {
556
556
  const cleanCUIT = issuerCUIT.replace(/\D/g, "");
557
557
  const cleanCAE = caeResponse.cae.replace(/\D/g, "");
558
- const rawDate = caeResponse.date;
558
+ const rawDate = String(caeResponse.date);
559
559
  const formattedDate = rawDate.length === 8 ? `${rawDate.substring(0, 4)}-${rawDate.substring(4, 6)}-${rawDate.substring(6, 8)}` : rawDate;
560
560
  const docType = buyer?.docType || 99 /* FINAL_CONSUMER */;
561
561
  const docNumber = buyer?.docNumber ? buyer.docNumber.replace(/\D/g, "") : "0";
@@ -1156,10 +1156,10 @@ var WsfeService = class _WsfeService {
1156
1156
  obsArray.forEach((o) => observations.push(o.Msg));
1157
1157
  }
1158
1158
  return {
1159
- invoiceType: cab.CbteTipo,
1160
- pointOfSale: cab.PtoVta,
1159
+ invoiceType: Number(cab.CbteTipo),
1160
+ pointOfSale: Number(cab.PtoVta),
1161
1161
  invoiceNumber: Number(det.CbteDesde),
1162
- date: det.CbteFch,
1162
+ date: String(det.CbteFch),
1163
1163
  cae: String(det.CAE),
1164
1164
  caeExpiry: String(det.CAEFchVto),
1165
1165
  result: det.Resultado,