arca-sdk 0.3.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 +70 -224
- package/dist/index.cjs +230 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -3
- package/dist/index.d.ts +101 -3
- package/dist/index.js +229 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
9
|
-
- ✅ **Fiscal**: Generador de QR oficial AFIP
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
```typescript
|
|
37
|
+
// 2. Emitir un Ticket C en dos líneas
|
|
70
38
|
const wsfe = new WsfeService(config);
|
|
71
|
-
const
|
|
72
|
-
console.log('CAE:', cae.cae);
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
---
|
|
39
|
+
const result = await wsfe.emitirTicketCSimple({ total: 1500 });
|
|
76
40
|
|
|
77
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
70
|
+
import { PadronService } from 'arca-sdk';
|
|
164
71
|
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const ticket = await wsaa.login();
|
|
72
|
+
const padron = new PadronService(config);
|
|
73
|
+
const { persona, error } = await padron.getPersona('30111111118');
|
|
168
74
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
##
|
|
252
|
-
|
|
105
|
+
## 🩺 Chequeo de Salud
|
|
106
|
+
Verificá si los servidores de ARCA están online antes de intentar facturar.
|
|
253
107
|
|
|
254
108
|
```typescript
|
|
255
|
-
|
|
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
|
-
##
|
|
115
|
+
## 📝 Servicios y Comprobantes
|
|
271
116
|
|
|
272
|
-
|
|
|
273
|
-
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
ArcaError: () => ArcaError,
|
|
35
35
|
ArcaValidationError: () => ArcaValidationError,
|
|
36
36
|
Concepto: () => Concepto,
|
|
37
|
+
PadronService: () => PadronService,
|
|
37
38
|
TipoComprobante: () => TipoComprobante,
|
|
38
39
|
TipoDocumento: () => TipoDocumento,
|
|
39
40
|
WsaaService: () => WsaaService,
|
|
@@ -51,19 +52,27 @@ var WSFE_ENDPOINTS = {
|
|
|
51
52
|
homologacion: "https://wswhomo.afip.gov.ar/wsfev1/service.asmx",
|
|
52
53
|
produccion: "https://servicios1.afip.gov.ar/wsfev1/service.asmx"
|
|
53
54
|
};
|
|
55
|
+
var PADRON_A13_ENDPOINTS = {
|
|
56
|
+
homologacion: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA13",
|
|
57
|
+
produccion: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA13"
|
|
58
|
+
};
|
|
54
59
|
function getWsaaEndpoint(environment) {
|
|
55
60
|
return WSAA_ENDPOINTS[environment];
|
|
56
61
|
}
|
|
57
62
|
function getWsfeEndpoint(environment) {
|
|
58
63
|
return WSFE_ENDPOINTS[environment];
|
|
59
64
|
}
|
|
65
|
+
function getPadronEndpoint(environment) {
|
|
66
|
+
return PADRON_A13_ENDPOINTS[environment];
|
|
67
|
+
}
|
|
60
68
|
|
|
61
69
|
// src/types/common.ts
|
|
62
70
|
var ArcaError = class extends Error {
|
|
63
|
-
constructor(message, code, details) {
|
|
71
|
+
constructor(message, code, details, hint) {
|
|
64
72
|
super(message);
|
|
65
73
|
this.code = code;
|
|
66
74
|
this.details = details;
|
|
75
|
+
this.hint = hint;
|
|
67
76
|
this.name = "ArcaError";
|
|
68
77
|
}
|
|
69
78
|
};
|
|
@@ -541,6 +550,54 @@ function redondear(valor) {
|
|
|
541
550
|
return Math.round(valor * 100) / 100;
|
|
542
551
|
}
|
|
543
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
|
+
|
|
544
601
|
// src/services/wsfe.ts
|
|
545
602
|
var WsfeService = class _WsfeService {
|
|
546
603
|
config;
|
|
@@ -789,10 +846,17 @@ var WsfeService = class _WsfeService {
|
|
|
789
846
|
}
|
|
790
847
|
const responseXml = await response.text();
|
|
791
848
|
const result = await this.parseCAEResponse(responseXml);
|
|
849
|
+
const urlQr = generarUrlQR(
|
|
850
|
+
result,
|
|
851
|
+
this.config.cuit,
|
|
852
|
+
total,
|
|
853
|
+
request.comprador
|
|
854
|
+
);
|
|
792
855
|
return {
|
|
793
856
|
...result,
|
|
794
857
|
items: request.items,
|
|
795
|
-
iva: request.ivaData
|
|
858
|
+
iva: request.ivaData,
|
|
859
|
+
urlQr
|
|
796
860
|
};
|
|
797
861
|
}
|
|
798
862
|
/**
|
|
@@ -818,7 +882,13 @@ var WsfeService = class _WsfeService {
|
|
|
818
882
|
const data = result?.Envelope?.Body?.FECompUltimoAutorizadoResponse?.FECompUltimoAutorizadoResult;
|
|
819
883
|
if (data?.Errors) {
|
|
820
884
|
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
821
|
-
|
|
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
|
+
);
|
|
822
892
|
}
|
|
823
893
|
const nro = data?.CbteNro;
|
|
824
894
|
return typeof nro === "number" ? nro + 1 : 1;
|
|
@@ -927,7 +997,13 @@ var WsfeService = class _WsfeService {
|
|
|
927
997
|
}
|
|
928
998
|
if (data.Errors) {
|
|
929
999
|
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
930
|
-
|
|
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
|
+
);
|
|
931
1007
|
}
|
|
932
1008
|
const cab = data.FeCabResp;
|
|
933
1009
|
const det = Array.isArray(data.FeDetResp.FECAEDetResponse) ? data.FeDetResp.FECAEDetResponse[0] : data.FeDetResp.FECAEDetResponse;
|
|
@@ -952,41 +1028,163 @@ var WsfeService = class _WsfeService {
|
|
|
952
1028
|
}
|
|
953
1029
|
};
|
|
954
1030
|
|
|
955
|
-
// src/
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
importe: Number(parseFloat(total.toFixed(2))),
|
|
971
|
-
moneda: "PES",
|
|
972
|
-
ctz: 1
|
|
973
|
-
};
|
|
974
|
-
if (docTipo !== 99 /* CONSUMIDOR_FINAL */ || Number(docNro) > 0) {
|
|
975
|
-
qrObj.tipoDocRec = Number(docTipo);
|
|
976
|
-
qrObj.nroDocRec = Number(docNro);
|
|
1031
|
+
// src/services/padron.ts
|
|
1032
|
+
var import_fast_xml_parser2 = require("fast-xml-parser");
|
|
1033
|
+
var PadronService = class {
|
|
1034
|
+
wsaa;
|
|
1035
|
+
config;
|
|
1036
|
+
constructor(config) {
|
|
1037
|
+
this.config = config;
|
|
1038
|
+
this.wsaa = new WsaaService({
|
|
1039
|
+
environment: config.environment,
|
|
1040
|
+
cuit: config.cuit,
|
|
1041
|
+
cert: config.cert,
|
|
1042
|
+
key: config.key,
|
|
1043
|
+
service: "ws_sr_padron_a13",
|
|
1044
|
+
storage: config.storage
|
|
1045
|
+
});
|
|
977
1046
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1047
|
+
/**
|
|
1048
|
+
* Consulta los datos de una persona por CUIT
|
|
1049
|
+
*
|
|
1050
|
+
* @param idPersona CUIT a consultar
|
|
1051
|
+
* @returns Datos de la persona o error
|
|
1052
|
+
*/
|
|
1053
|
+
async getPersona(idPersona) {
|
|
1054
|
+
const ticket = await this.wsaa.login();
|
|
1055
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1056
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1057
|
+
xmlns:a13="http://a13.soap.ws.server.puc.sr/">
|
|
1058
|
+
<soapenv:Header/>
|
|
1059
|
+
<soapenv:Body>
|
|
1060
|
+
<a13:getPersona>
|
|
1061
|
+
<token>${ticket.token}</token>
|
|
1062
|
+
<sign>${ticket.sign}</sign>
|
|
1063
|
+
<cuitRepresentada>${this.config.cuit}</cuitRepresentada>
|
|
1064
|
+
<idPersona>${idPersona}</idPersona>
|
|
1065
|
+
</a13:getPersona>
|
|
1066
|
+
</soapenv:Body>
|
|
1067
|
+
</soapenv:Envelope>`;
|
|
1068
|
+
const endpoint = getPadronEndpoint(this.config.environment);
|
|
1069
|
+
const response = await callArcaApi(endpoint, {
|
|
1070
|
+
method: "POST",
|
|
1071
|
+
headers: {
|
|
1072
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1073
|
+
"SOAPAction": ""
|
|
1074
|
+
// A13 no suele requerir SOAPAction específica en el header
|
|
1075
|
+
},
|
|
1076
|
+
body: soapRequest,
|
|
1077
|
+
timeout: this.config.timeout || 15e3
|
|
1078
|
+
});
|
|
1079
|
+
if (!response.ok) {
|
|
1080
|
+
throw new ArcaNetworkError(
|
|
1081
|
+
`Error HTTP al comunicarse con Padron A13: ${response.status}`,
|
|
1082
|
+
{ status: response.status }
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
const xml = await response.text();
|
|
1086
|
+
return this.parseResponse(xml);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Parsear la respuesta XML de getPersona
|
|
1090
|
+
*/
|
|
1091
|
+
parseResponse(xml) {
|
|
1092
|
+
const parser = new import_fast_xml_parser2.XMLParser({
|
|
1093
|
+
ignoreAttributes: false,
|
|
1094
|
+
removeNSPrefix: true
|
|
1095
|
+
});
|
|
1096
|
+
const result = parser.parse(xml);
|
|
1097
|
+
const body = result.Envelope?.Body;
|
|
1098
|
+
if (!body) {
|
|
1099
|
+
throw new ArcaError("Respuesta de Padr\xF3n inv\xE1lida: No se encontr\xF3 Body", "PADRON_ERROR");
|
|
1100
|
+
}
|
|
1101
|
+
const response = body.getPersonaResponse?.personaReturn;
|
|
1102
|
+
if (!response) {
|
|
1103
|
+
const fault = body.Fault;
|
|
1104
|
+
if (fault) {
|
|
1105
|
+
return { error: fault.faultstring || "Error desconocido en ARCA" };
|
|
1106
|
+
}
|
|
1107
|
+
return { error: "No se encontraron datos para el CUIT informado" };
|
|
1108
|
+
}
|
|
1109
|
+
if (response.errorConstancia) {
|
|
1110
|
+
return { error: response.errorConstancia };
|
|
1111
|
+
}
|
|
1112
|
+
const p = response.persona;
|
|
1113
|
+
if (!p) {
|
|
1114
|
+
return { error: "CUIT no encontrado" };
|
|
1115
|
+
}
|
|
1116
|
+
const persona = {
|
|
1117
|
+
idPersona: Number(p.idPersona),
|
|
1118
|
+
tipoPersona: p.tipoPersona,
|
|
1119
|
+
nombre: p.nombre,
|
|
1120
|
+
apellido: p.apellido,
|
|
1121
|
+
razonSocial: p.razonSocial,
|
|
1122
|
+
estadoClave: p.estadoClave,
|
|
1123
|
+
domicilio: this.mapDomicilios(p.domicilio),
|
|
1124
|
+
actividad: this.mapActividades(p.actividad),
|
|
1125
|
+
impuesto: this.mapImpuestos(p.impuesto),
|
|
1126
|
+
descripcionActividadPrincipal: p.descripcionActividadPrincipal,
|
|
1127
|
+
esInscriptoIVA: this.checkImpuesto(p, 30),
|
|
1128
|
+
// 30 = IVA
|
|
1129
|
+
esMonotributista: this.checkImpuesto(p, 20),
|
|
1130
|
+
// 20 = Monotributo
|
|
1131
|
+
esExento: this.checkImpuesto(p, 32)
|
|
1132
|
+
// 32 = Exento
|
|
1133
|
+
};
|
|
1134
|
+
return { persona };
|
|
1135
|
+
}
|
|
1136
|
+
mapDomicilios(d) {
|
|
1137
|
+
if (!d) return [];
|
|
1138
|
+
const list = this.ensureArray(d);
|
|
1139
|
+
return list.map((item) => ({
|
|
1140
|
+
direccion: item.direccion,
|
|
1141
|
+
localidad: item.localidad,
|
|
1142
|
+
codPostal: item.codPostal,
|
|
1143
|
+
idProvincia: Number(item.idProvincia),
|
|
1144
|
+
descripcionProvincia: item.descripcionProvincia,
|
|
1145
|
+
tipoDomicilio: item.tipoDomicilio
|
|
1146
|
+
}));
|
|
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
|
+
}
|
|
1167
|
+
checkImpuesto(p, id) {
|
|
1168
|
+
const impuestos = p.impuesto;
|
|
1169
|
+
if (!impuestos) return false;
|
|
1170
|
+
const list = this.ensureArray(impuestos);
|
|
1171
|
+
return list.some((i) => Number(i.idImpuesto) === id);
|
|
1172
|
+
}
|
|
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];
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
984
1181
|
// Annotate the CommonJS export names for ESM import in node:
|
|
985
1182
|
0 && (module.exports = {
|
|
986
1183
|
ArcaAuthError,
|
|
987
1184
|
ArcaError,
|
|
988
1185
|
ArcaValidationError,
|
|
989
1186
|
Concepto,
|
|
1187
|
+
PadronService,
|
|
990
1188
|
TipoComprobante,
|
|
991
1189
|
TipoDocumento,
|
|
992
1190
|
WsaaService,
|