@ramiidv/arca-sdk 0.2.1 → 1.0.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/llm.txt ADDED
@@ -0,0 +1,466 @@
1
+ # @ramiidv/arca-sdk
2
+
3
+ > TypeScript SDK for ARCA (formerly AFIP) electronic invoicing in Argentina.
4
+ > Wraps WSFEv1, WSFEX, and WSAA SOAP web services.
5
+
6
+ ## What this SDK does
7
+
8
+ - Authenticates against ARCA's WSAA using X.509 certificates (PKCS#7/CMS signing)
9
+ - Creates electronic invoices (facturas), credit notes, and debit notes via WSFEv1
10
+ - Handles all comprobante types: A, B, C, E, M, and MiPyME FCE
11
+ - Auto-calculates IVA, totals, and comprobante numbers
12
+ - Queries ARCA parameters (document types, IVA rates, currencies, exchange rates, etc.)
13
+ - Export invoices via WSFEX (Factura de Exportación)
14
+ - Retry with exponential backoff on transient errors
15
+ - Event system for logging/debugging (auth, requests, retries)
16
+ - QR code URL generation for printed invoices
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install @ramiidv/arca-sdk
22
+ ```
23
+
24
+ Requires Node.js >= 18. Uses native `fetch` (no axios/node-fetch dependency).
25
+
26
+ ## Quick start
27
+
28
+ ```typescript
29
+ import fs from "fs";
30
+ import { Arca, CbteTipo, IvaTipo, DocTipo, Moneda } from "@ramiidv/arca-sdk";
31
+
32
+ const arca = new Arca({
33
+ cuit: 20123456789,
34
+ cert: fs.readFileSync("./cert.crt", "utf-8"),
35
+ key: fs.readFileSync("./key.key", "utf-8"),
36
+ production: false,
37
+ });
38
+
39
+ const result = await arca.facturar({
40
+ ptoVta: 1,
41
+ cbteTipo: CbteTipo.FACTURA_B,
42
+ items: [{ neto: 1000, iva: IvaTipo.IVA_21 }],
43
+ });
44
+ // result: { aprobada: true, cae: "...", cbteNro: 42, importes: { total: 1210, neto: 1000, iva: 210, ... } }
45
+ ```
46
+
47
+ ## Architecture
48
+
49
+ The SDK has two API levels:
50
+
51
+ 1. **Simplified API** (`facturar`, `notaCredito`, `notaDebito`) — auto-calculates IVA, totals, comprobante number, dates. Returns a clean `FacturaResult`.
52
+ 2. **Raw API** (`crearFactura`, `crearFacturaAuto`) — maps 1:1 to AFIP's WSFEv1 SOAP methods. Returns raw AFIP XML responses parsed to JS objects.
53
+
54
+ Internally:
55
+ - `Arca` (main class) — orchestrator, holds config, exposes all methods
56
+ - `WsaaClient` — handles WSAA authentication, token caching with dedup and expiry margin
57
+ - `WsfeClient` — wraps all WSFEv1 SOAP calls
58
+ - `facturacion.ts` — pure functions for IVA/total calculation and invoice building
59
+ - `soap-client.ts` — generic SOAP/XML utilities (fetch with timeout, XML parsing via fast-xml-parser)
60
+ - `errors.ts` — `ArcaAuthError`, `ArcaWSFEError` (with error codes), `ArcaSoapError` (with HTTP status)
61
+
62
+ ## Configuration
63
+
64
+ ```typescript
65
+ new Arca({
66
+ cuit: number, // CUIT without hyphens
67
+ cert: string, // X.509 certificate PEM content
68
+ key: string, // Private key PEM content
69
+ production?: boolean, // Default: false (uses homologación/testing endpoints)
70
+ tokenTTLMinutes?: number, // Default: 720 (12 hours)
71
+ requestTimeoutMs?: number, // Default: 30000 (30 seconds). Uses AbortController.
72
+ retries?: number, // Default: 1. Retries on transient errors (timeout, 5xx).
73
+ retryDelayMs?: number, // Default: 1000. Exponential backoff: delay * 2^attempt.
74
+ onEvent?: (event: ArcaEvent) => void, // Callback for all SDK events
75
+ })
76
+ ```
77
+
78
+ ## Simplified API — facturar()
79
+
80
+ Automatically: gets next comprobante number, calculates all totals from line items, formats dates in Argentina timezone, defaults to Consumidor Final + Pesos + Productos.
81
+
82
+ ```typescript
83
+ interface FacturarOpts {
84
+ ptoVta: number; // Punto de venta
85
+ cbteTipo: number; // CbteTipo enum
86
+ items: LineItem[]; // Line items (see below)
87
+ concepto?: number; // Default: PRODUCTOS. Auto-detects SERVICIOS if `servicio` provided
88
+ docTipo?: number; // Default: DocTipo.CONSUMIDOR_FINAL (99)
89
+ docNro?: number; // Default: 0
90
+ fecha?: Date | string; // Default: today (Argentina timezone). String = "YYYYMMDD"
91
+ servicio?: { desde, hasta, vtoPago }; // Required for services. Accepts Date or "YYYYMMDD"
92
+ moneda?: string; // Default: "PES"
93
+ cotizacion?: number; // Default: 1
94
+ tributos?: Tributo[];
95
+ opcionales?: Opcional[]; // E.g., CBU for FCE: [{ Id: "2101", Valor: "..." }]
96
+ }
97
+
98
+ interface LineItem {
99
+ neto: number; // Net amount (without IVA)
100
+ iva?: number; // IvaTipo enum. If set, item is "gravado" and IVA is calculated
101
+ exento?: boolean; // If true, amount goes to ImpOpEx (exempt)
102
+ // If neither iva nor exento: amount goes to ImpTotConc (no gravado)
103
+ }
104
+ ```
105
+
106
+ **IVA calculation rules:**
107
+ - Items with `iva` set → added to ImpNeto. IVA calculated using IVA_RATES and added to ImpIVA. Grouped by aliquot in the Iva array.
108
+ - Items with `exento: true` → added to ImpOpEx.
109
+ - Items with neither → added to ImpTotConc (no gravado).
110
+ - ImpTotal = ImpNeto + ImpIVA + ImpOpEx + ImpTotConc + ImpTrib.
111
+ - All amounts rounded to 2 decimal places.
112
+ - For CbteTipo C (monotributista): IVA is NOT discriminated. All items go to ImpNeto, ImpIVA = 0.
113
+
114
+ **Return type:**
115
+
116
+ ```typescript
117
+ interface FacturaResult {
118
+ aprobada: boolean;
119
+ cae?: string;
120
+ caeVencimiento?: string; // "YYYYMMDD"
121
+ cbteNro: number;
122
+ ptoVta: number;
123
+ cbteTipo: number;
124
+ importes: {
125
+ total: number;
126
+ neto: number;
127
+ iva: number;
128
+ exento: number;
129
+ noGravado: number;
130
+ tributos: number;
131
+ };
132
+ observaciones: { code: number; msg: string }[];
133
+ raw: FECAESolicitarResult; // Full AFIP response
134
+ }
135
+ ```
136
+
137
+ ## Simplified API — notaCredito() / notaDebito()
138
+
139
+ Same as `facturar` but with `comprobanteOriginal` instead of `cbteTipo`. The NC/ND type is inferred automatically (FACTURA_B → NOTA_CREDITO_B, etc.).
140
+
141
+ ```typescript
142
+ await arca.notaCredito({
143
+ ptoVta: 1,
144
+ comprobanteOriginal: { tipo: CbteTipo.FACTURA_B, ptoVta: 1, nro: 150 },
145
+ items: [{ neto: 100, iva: IvaTipo.IVA_21 }],
146
+ });
147
+
148
+ await arca.notaDebito({
149
+ ptoVta: 1,
150
+ comprobanteOriginal: { tipo: CbteTipo.FACTURA_A, ptoVta: 1, nro: 200, fecha: "20260301" },
151
+ docTipo: DocTipo.CUIT,
152
+ docNro: 30712345678,
153
+ items: [{ neto: 500, iva: IvaTipo.IVA_21 }],
154
+ });
155
+ ```
156
+
157
+ **Supported CbteTipo mappings:**
158
+ - FACTURA_A (1) → NC_A (3), ND_A (2)
159
+ - FACTURA_B (6) → NC_B (8), ND_B (7)
160
+ - FACTURA_C (11) → NC_C (13), ND_C (12)
161
+ - FACTURA_E (19) → NC_E (21), ND_E (20)
162
+ - FACTURA_M (51) → NC_M (53), ND_M (52)
163
+ - FCE_FACTURA_A (201) → FCE_NC_A (203), FCE_ND_A (202)
164
+ - FCE_FACTURA_B (206) → FCE_NC_B (208), FCE_ND_B (207)
165
+ - FCE_FACTURA_C (211) → FCE_NC_C (213), FCE_ND_C (212)
166
+
167
+ ## Static utilities
168
+
169
+ ```typescript
170
+ // Preview totals without submitting to ARCA
171
+ const { importes, iva } = Arca.calcularTotales(
172
+ [{ neto: 1000, iva: IvaTipo.IVA_21 }],
173
+ { tributos: [...], tipoC: false }
174
+ );
175
+
176
+ // Format date to YYYYMMDD in Argentina timezone
177
+ Arca.formatDate(new Date()); // "20260328"
178
+ Arca.formatDate("20260328"); // passthrough
179
+
180
+ // Extract CAE from raw result (for raw API users)
181
+ const { approved, cae, caeFchVto, details } = Arca.extractCAE(rawResult);
182
+ ```
183
+
184
+ ## Raw API (advanced)
185
+
186
+ Maps directly to WSFEv1 SOAP methods. No auto-calculation. User must provide all fields.
187
+
188
+ ```typescript
189
+ // Get next number manually
190
+ const nextNum = await arca.siguienteComprobante(1, CbteTipo.FACTURA_B);
191
+
192
+ // Create invoice with full control
193
+ const rawResult = await arca.crearFactura({
194
+ PtoVta: 1,
195
+ CbteTipo: CbteTipo.FACTURA_B,
196
+ invoices: [{
197
+ Concepto: 1, DocTipo: 99, DocNro: 0,
198
+ CbteDesde: nextNum, CbteHasta: nextNum,
199
+ CbteFch: "20260328",
200
+ ImpTotal: 121, ImpTotConc: 0, ImpNeto: 100,
201
+ ImpOpEx: 0, ImpTrib: 0, ImpIVA: 21,
202
+ MonId: "PES", MonCotiz: 1,
203
+ Iva: [{ Id: 5, BaseImp: 100, Importe: 21 }],
204
+ }],
205
+ });
206
+
207
+ // Or with auto-numbering (still no auto-calc)
208
+ const rawResult2 = await arca.crearFacturaAuto(1, CbteTipo.FACTURA_B, { ...invoiceDetail });
209
+ ```
210
+
211
+ ## Query methods
212
+
213
+ ```typescript
214
+ await arca.serverStatus(); // { AppServer, DbServer, AuthServer }
215
+ await arca.ultimoComprobante(ptoVta, cbteTipo); // number
216
+ await arca.consultarComprobante(cbteTipo, ptoVta, cbteNro);
217
+ await arca.getPuntosVenta();
218
+ await arca.getCotizacion(Moneda.DOLARES); // { MonId, MonCotiz, FchCotiz }
219
+ await arca.getTiposComprobante();
220
+ await arca.getTiposConcepto();
221
+ await arca.getTiposDocumento();
222
+ await arca.getTiposIva();
223
+ await arca.getMonedas();
224
+ await arca.getTiposTributo();
225
+ await arca.getTiposOpcional();
226
+ await arca.getCantMaxRegistros();
227
+ ```
228
+
229
+ ## Error handling
230
+
231
+ ```typescript
232
+ import { ArcaAuthError, ArcaWSFEError, ArcaSoapError } from "@ramiidv/arca-sdk";
233
+
234
+ try {
235
+ await arca.facturar({ ... });
236
+ } catch (e) {
237
+ if (e instanceof ArcaAuthError) {
238
+ // WSAA login failed (bad cert, expired, etc.)
239
+ arca.clearAuthCache();
240
+ }
241
+ if (e instanceof ArcaWSFEError) {
242
+ // AFIP business error with codes
243
+ e.errors; // { code: number, msg: string }[]
244
+ }
245
+ if (e instanceof ArcaSoapError) {
246
+ // HTTP/SOAP transport error (timeout, 500, SOAP fault)
247
+ e.statusCode; // number | undefined
248
+ }
249
+ }
250
+ ```
251
+
252
+ All error classes extend `ArcaError` which extends `Error`.
253
+
254
+ ## Enums reference
255
+
256
+ ```typescript
257
+ // Comprobante types
258
+ CbteTipo.FACTURA_A // 1
259
+ CbteTipo.FACTURA_B // 6
260
+ CbteTipo.FACTURA_C // 11
261
+ CbteTipo.FACTURA_E // 19 (export)
262
+ CbteTipo.FCE_FACTURA_A // 201 (MiPyME)
263
+ // ... and NOTA_DEBITO_*, NOTA_CREDITO_*, RECIBO_* variants
264
+
265
+ // Concepto
266
+ Concepto.PRODUCTOS // 1
267
+ Concepto.SERVICIOS // 2
268
+ Concepto.PRODUCTOS_Y_SERVICIOS // 3
269
+
270
+ // Document types
271
+ DocTipo.CUIT // 80
272
+ DocTipo.DNI // 96
273
+ DocTipo.CONSUMIDOR_FINAL // 99
274
+ // ... CUIL (86), CDI (87), Pasaporte (94), etc.
275
+
276
+ // IVA rates
277
+ IvaTipo.IVA_0 // 3 → 0%
278
+ IvaTipo.IVA_2_5 // 9 → 2.5%
279
+ IvaTipo.IVA_5 // 8 → 5%
280
+ IvaTipo.IVA_10_5 // 4 → 10.5%
281
+ IvaTipo.IVA_21 // 5 → 21%
282
+ IvaTipo.IVA_27 // 6 → 27%
283
+
284
+ // IVA_RATES maps IvaTipo to percentage: IVA_RATES[IvaTipo.IVA_21] === 21
285
+
286
+ // Currency
287
+ Moneda.PESOS // "PES"
288
+ Moneda.DOLARES // "DOL"
289
+ Moneda.EUROS // "060"
290
+ // ... and more
291
+
292
+ // Tributo types
293
+ TributoTipo.IIBB // 5
294
+ TributoTipo.PERCEP_IVA // 6
295
+ // ... and more
296
+ ```
297
+
298
+ ## Common patterns
299
+
300
+ ### Service invoice with tributos
301
+
302
+ ```typescript
303
+ await arca.facturar({
304
+ ptoVta: 1,
305
+ cbteTipo: CbteTipo.FACTURA_A,
306
+ docTipo: DocTipo.CUIT,
307
+ docNro: 30712345678,
308
+ items: [{ neto: 10000, iva: IvaTipo.IVA_21 }],
309
+ servicio: {
310
+ desde: new Date("2026-03-01"),
311
+ hasta: new Date("2026-03-31"),
312
+ vtoPago: new Date("2026-04-15"),
313
+ },
314
+ tributos: [{
315
+ Id: TributoTipo.IIBB,
316
+ Desc: "IIBB CABA",
317
+ BaseImp: 10000,
318
+ Alic: 3,
319
+ Importe: 300,
320
+ }],
321
+ });
322
+ ```
323
+
324
+ ### Batch invoice (raw API, multiple comprobantes)
325
+
326
+ ```typescript
327
+ const result = await arca.crearFactura({
328
+ PtoVta: 1,
329
+ CbteTipo: CbteTipo.FACTURA_B,
330
+ invoices: [invoice1, invoice2, invoice3], // up to getCantMaxRegistros()
331
+ });
332
+ ```
333
+
334
+ ### Currency with exchange rate
335
+
336
+ ```typescript
337
+ const cotiz = await arca.getCotizacion(Moneda.DOLARES);
338
+ await arca.facturar({
339
+ ptoVta: 1,
340
+ cbteTipo: CbteTipo.FACTURA_A,
341
+ docTipo: DocTipo.CUIT,
342
+ docNro: 30712345678,
343
+ items: [{ neto: 1000, iva: IvaTipo.IVA_21 }],
344
+ moneda: Moneda.DOLARES,
345
+ cotizacion: cotiz.MonCotiz,
346
+ });
347
+ ```
348
+
349
+ ## Events / Logging
350
+
351
+ Two ways to subscribe:
352
+
353
+ ```typescript
354
+ // 1. Config callback (receives all events)
355
+ const arca = new Arca({
356
+ ...,
357
+ onEvent: (e) => console.log(e.type, e),
358
+ });
359
+
360
+ // 2. Typed .on()/.off() (filter by event type)
361
+ arca.on("request:end", (e) => console.log(`${e.method} took ${e.durationMs}ms`));
362
+ arca.on("auth:login", (e) => console.log(`Login ${e.service} in ${e.durationMs}ms`));
363
+ arca.on("request:retry", (e) => console.warn(`Retry #${e.attempt}: ${e.error}`));
364
+ ```
365
+
366
+ Event types (discriminated union `ArcaEvent`):
367
+ - `auth:login` — new token obtained. Fields: `service`, `durationMs`
368
+ - `auth:cache-hit` — cached token reused. Fields: `service`
369
+ - `request:start` — SOAP call starting. Fields: `method`, `endpoint`
370
+ - `request:end` — SOAP call completed. Fields: `method`, `durationMs`
371
+ - `request:retry` — retrying after error. Fields: `method`, `attempt`, `delayMs`, `error`
372
+ - `request:error` — request failed. Fields: `method`, `error`
373
+
374
+ ## Retry
375
+
376
+ Enabled by default (`retries: 1`). Only retries on transient errors (timeout, 5xx, network). Does NOT retry on 4xx or business errors. Exponential backoff: `retryDelayMs * 2^attempt`.
377
+
378
+ ## QR Code URL
379
+
380
+ ARCA requires a QR code on printed invoices. `generateQRUrl` produces the official URL:
381
+
382
+ ```typescript
383
+ const url = Arca.generateQRUrl({
384
+ fecha: "2026-03-28", // YYYY-MM-DD
385
+ cuit: 20123456789,
386
+ ptoVta: 1,
387
+ tipoCmp: CbteTipo.FACTURA_B,
388
+ nroCmp: 150,
389
+ importe: 121,
390
+ moneda: "PES",
391
+ ctz: 1,
392
+ tipoDocRec: DocTipo.CONSUMIDOR_FINAL,
393
+ nroDocRec: 0,
394
+ codAut: 73429843294823, // CAE number
395
+ });
396
+ // → "https://www.afip.gob.ar/fe/qr/?p=<base64>"
397
+ ```
398
+
399
+ ## WSFEX (Export invoices)
400
+
401
+ For export invoices (`Factura E`, `Nota de Crédito E`, etc.). Uses a separate ARCA web service (WSFEX) with different endpoints and schema.
402
+
403
+ ```typescript
404
+ // Get next ID and number
405
+ const nextId = (await arca.ultimoIdExpo()) + 1;
406
+ const nextNum = await arca.siguienteComprobanteExpo(1, CbteTipo.FACTURA_E);
407
+
408
+ // Authorize export invoice
409
+ const result = await arca.crearFacturaExportacion({
410
+ Id: nextId,
411
+ Cbte_Tipo: CbteTipo.FACTURA_E,
412
+ Fecha_cbte: "20260328",
413
+ Punto_vta: 1,
414
+ Cbte_nro: nextNum,
415
+ Tipo_expo: 1, // 1=Bienes, 2=Servicios, 4=Otros
416
+ Permiso_existente: "N",
417
+ Dst_cmp: 203, // Country code (203 = USA)
418
+ Cliente: "ACME Corp",
419
+ Cuit_pais_cliente: 50000000016, // CUIT del país
420
+ Domicilio_cliente: "123 Main St, NY",
421
+ Id_impositivo: "12-3456789",
422
+ Moneda_Id: "DOL",
423
+ Moneda_ctz: 1200,
424
+ Idioma_cbte: 2, // 1=ES, 2=EN, 3=PT
425
+ Forma_pago: "Wire Transfer",
426
+ Incoterms: "FOB",
427
+ Items: [{
428
+ Pro_codigo: "SKU001",
429
+ Pro_ds: "Widget",
430
+ Pro_qty: 100,
431
+ Pro_umed: 7, // Unit of measure code
432
+ Pro_precio_uni: 10,
433
+ Pro_bonificacion: 0,
434
+ Pro_total_item: 1000,
435
+ }],
436
+ });
437
+
438
+ if (result.FEXResultAuth?.Resultado === "A") {
439
+ console.log("CAE:", result.FEXResultAuth.Cae);
440
+ }
441
+
442
+ // Query methods
443
+ await arca.consultarComprobanteExpo(CbteTipo.FACTURA_E, 1, nextNum);
444
+ await arca.serverStatusExpo();
445
+ await arca.getPaisesExpo();
446
+ await arca.getIncotermsExpo();
447
+ await arca.getUMedExpo();
448
+ await arca.getTiposExpo();
449
+ await arca.getMonedasExpo();
450
+ await arca.getIdiomasExpo();
451
+ await arca.getCuitsPaisExpo();
452
+ await arca.getTiposCbteExpo();
453
+ ```
454
+
455
+ ## Key domain concepts
456
+
457
+ - **CUIT**: Argentine tax ID (11 digits, no hyphens). Used for business identification.
458
+ - **Punto de venta (PtoVta)**: Sales point number (1-99999). Must be pre-registered with ARCA.
459
+ - **CAE**: Código de Autorización Electrónica. Unique code ARCA returns for each approved invoice.
460
+ - **Comprobante**: Generic term for any fiscal document (factura, nota de crédito/débito, recibo).
461
+ - **WSFEv1**: ARCA's SOAP web service for electronic invoicing.
462
+ - **WSAA**: ARCA's authentication web service. Uses CMS/PKCS#7 signed tickets.
463
+ - **Homologación**: ARCA's testing/sandbox environment. Use `production: false`.
464
+ - **FCE MiPyME**: Factura de Crédito Electrónica for small/medium businesses. Requires CBU in opcionales.
465
+ - **Tipo C**: Invoices issued by monotributistas (simplified tax regime). No IVA discrimination.
466
+ - **WSFEX**: ARCA's web service for export invoices. Separate from WSFEv1, different schema.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramiidv/arca-sdk",
3
- "version": "0.2.1",
3
+ "version": "1.0.0",
4
4
  "description": "SDK para interactuar con los Web Services de ARCA (ex AFIP) - Facturación Electrónica Argentina",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,8 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "README.md",
17
- "LICENSE"
17
+ "LICENSE",
18
+ "llm.txt"
18
19
  ],
19
20
  "scripts": {
20
21
  "build": "tsc",
@@ -49,12 +50,12 @@
49
50
  "node": ">=18.0.0"
50
51
  },
51
52
  "dependencies": {
52
- "node-forge": "^1.3.1",
53
- "fast-xml-parser": "^5.1.0"
53
+ "fast-xml-parser": "^5.5.9",
54
+ "node-forge": "^1.4.0"
54
55
  },
55
56
  "devDependencies": {
56
- "@types/node": "^22.0.0",
57
- "@types/node-forge": "^1.3.11",
58
- "typescript": "^5.7.0"
57
+ "@types/node": "^25.5.0",
58
+ "@types/node-forge": "^1.3.14",
59
+ "typescript": "^6.0.2"
59
60
  }
60
61
  }