consultame-sdk 1.0.1 → 1.1.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 +901 -585
- package/dist/index.js +9777 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9777 -9
- package/dist/index.mjs.map +1 -1
- package/dist/shared/clases/detalleDocumento.d.ts +41 -0
- package/dist/shared/clases/detalleDocumento.d.ts.map +1 -0
- package/dist/shared/clases/documento.d.ts +264 -0
- package/dist/shared/clases/documento.d.ts.map +1 -0
- package/dist/shared/clases/documento.dto.d.ts +498 -0
- package/dist/shared/clases/documento.dto.d.ts.map +1 -0
- package/dist/shared/functions/functions.utils.d.ts +19 -0
- package/dist/shared/functions/functions.utils.d.ts.map +1 -1
- package/dist/shared/utils/dictionary.utils.d.ts +126 -0
- package/dist/shared/utils/dictionary.utils.d.ts.map +1 -1
- package/dist/sifen/applications/services/sifen.service.d.ts +7 -1
- package/dist/sifen/applications/services/sifen.service.d.ts.map +1 -1
- package/dist/sifen/domain/repository/sifen.repository.d.ts +3 -0
- package/dist/sifen/domain/repository/sifen.repository.d.ts.map +1 -1
- package/dist/sifen/domain/useCases/generateDocument.useCase.d.ts +8 -0
- package/dist/sifen/domain/useCases/generateDocument.useCase.d.ts.map +1 -0
- package/dist/sifen/domain/useCases/index.d.ts +2 -0
- package/dist/sifen/domain/useCases/index.d.ts.map +1 -1
- package/dist/sifen/domain/useCases/signedXml.useCase.d.ts +8 -0
- package/dist/sifen/domain/useCases/signedXml.useCase.d.ts.map +1 -0
- package/dist/sifen/infrastructure/persistence/sifen.persistence.d.ts +3 -0
- package/dist/sifen/infrastructure/persistence/sifen.persistence.d.ts.map +1 -1
- package/package.json +70 -69
package/README.md
CHANGED
|
@@ -1,585 +1,901 @@
|
|
|
1
|
-
# consultame-sdk
|
|
2
|
-
|
|
3
|
-
Librería en **TypeScript** para generar los mensajes XML (SOAP) e insumos criptográficos
|
|
4
|
-
que se necesitan para interactuar con **SIFEN** (Sistema Integrado de Facturación Electrónica
|
|
5
|
-
Nacional de Paraguay – SET).
|
|
6
|
-
|
|
7
|
-
La librería **no** hace las peticiones HTTP al web service de SIFEN: su responsabilidad es
|
|
8
|
-
**construir los sobres XML** (consulta de RUC, consulta de lote, consulta de documento),
|
|
9
|
-
**extraer el certificado y la llave privada** desde un archivo de firma digital `.p12`/`.pfx
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
|
|
21
|
-
- [`
|
|
22
|
-
- [`
|
|
23
|
-
- [`
|
|
24
|
-
- [`
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
-
|
|
569
|
-
|
|
570
|
-
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1
|
+
# consultame-sdk
|
|
2
|
+
|
|
3
|
+
Librería en **TypeScript** para generar los mensajes XML (SOAP) e insumos criptográficos
|
|
4
|
+
que se necesitan para interactuar con **SIFEN** (Sistema Integrado de Facturación Electrónica
|
|
5
|
+
Nacional de Paraguay – SET).
|
|
6
|
+
|
|
7
|
+
La librería **no** hace las peticiones HTTP al web service de SIFEN: su responsabilidad es
|
|
8
|
+
**construir los sobres XML** (consulta de RUC, consulta de lote, consulta de documento),
|
|
9
|
+
**extraer el certificado y la llave privada** desde un archivo de firma digital `.p12`/`.pfx`,
|
|
10
|
+
**firmar digitalmente (XMLDSig)** el Documento Electrónico generado y **generar el código QR**
|
|
11
|
+
que se incrusta en el documento electrónico ya firmado.
|
|
12
|
+
Con esa salida, la aplicación consumidora envía los mensajes por su cuenta.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Tabla de contenido
|
|
17
|
+
|
|
18
|
+
- [Instalación](#instalación)
|
|
19
|
+
- [Uso rápido](#uso-rápido)
|
|
20
|
+
- [API pública](#api-pública)
|
|
21
|
+
- [`consultRuc`](#consultrucconsultrucdto)
|
|
22
|
+
- [`consultDocument`](#consultdocumentconsultdocumentdto)
|
|
23
|
+
- [`consultBatch`](#consultbatchconsultbatchdto)
|
|
24
|
+
- [`obtainDigitalSign`](#obtaindigitalsigndigitalsigndto)
|
|
25
|
+
- [`generateQr`](#generateqrgenerationqrdto)
|
|
26
|
+
- [`generateDocument`](#generatedocumentgeneratedocumentoelectronicodto)
|
|
27
|
+
- [`signedXml`](#signedxmlxml-tag-responsedigitalsign)
|
|
28
|
+
- [DTOs (contratos de entrada)](#dtos-contratos-de-entrada)
|
|
29
|
+
- [Funciones utilitarias internas](#funciones-utilitarias-internas)
|
|
30
|
+
- [Arquitectura del proyecto](#arquitectura-del-proyecto)
|
|
31
|
+
- [Estructura de carpetas](#estructura-de-carpetas)
|
|
32
|
+
- [Scripts de desarrollo](#scripts-de-desarrollo)
|
|
33
|
+
- [Estado / roadmap](#estado--roadmap)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Instalación
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add consultame-sdk
|
|
41
|
+
# o
|
|
42
|
+
npm install consultame-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Dependencias en tiempo de ejecución:
|
|
46
|
+
|
|
47
|
+
| Paquete | Para qué se usa |
|
|
48
|
+
| -------------------------------------------------------- | ------------------------------------------------------------ |
|
|
49
|
+
| [`node-forge`](https://www.npmjs.com/package/node-forge) | Leer los certificados `.p12`/`.pfx` y exportarlos a PEM. |
|
|
50
|
+
| [`xml2js`](https://www.npmjs.com/package/xml2js) | Parsear el XML firmado al generar el QR. |
|
|
51
|
+
| [`xml-crypto`](https://www.npmjs.com/package/xml-crypto) | Firmar digitalmente (XMLDSig) el Documento Electrónico generado. |
|
|
52
|
+
| [`crypto-js`](https://www.npmjs.com/package/crypto-js) | Calcular el hash SHA-256 (`cHashQR`) del código QR. |
|
|
53
|
+
|
|
54
|
+
La librería se publica en formato dual **CommonJS + ESM** con tipos incluidos:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// ESM
|
|
58
|
+
import { SifenService } from "consultame-sdk";
|
|
59
|
+
|
|
60
|
+
// CommonJS
|
|
61
|
+
const { SifenService } = require("consultame-sdk");
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Uso rápido
|
|
67
|
+
|
|
68
|
+
Todo el punto de entrada público es la clase **`SifenService`**. Se instancia sin
|
|
69
|
+
argumentos y ya arma internamente sus casos de uso y dependencias.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { SifenService } from "consultame-skd";
|
|
73
|
+
|
|
74
|
+
const sifen = new SifenService();
|
|
75
|
+
|
|
76
|
+
// 1) Consultar un RUC → devuelve el XML SOAP listo, normalizado en una sola línea
|
|
77
|
+
const xmlRuc = sifen.consultRuc({
|
|
78
|
+
id: 1,
|
|
79
|
+
ruc: "80012345-6",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 2) Consultar un documento electrónico por su CDC
|
|
83
|
+
const xmlDocumento = sifen.consultDocument({
|
|
84
|
+
id: 2,
|
|
85
|
+
cdc: "01800123456001001000000012023052212345678901",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 3) Consultar el estado de un lote enviado
|
|
89
|
+
const xmlLote = sifen.consultBatch({
|
|
90
|
+
id: 3,
|
|
91
|
+
codigoLote: "1234567890",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 4) Extraer certificado + llave privada desde la firma digital (.p12)
|
|
95
|
+
const firma = sifen.obtainDigitalSign({
|
|
96
|
+
camino: "./certificados/firma.p12",
|
|
97
|
+
contraseña: "mi-clave-secreta",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
console.log(firma.certificado); // PEM del certificado
|
|
101
|
+
console.log(firma.llavePrivada); // PEM de la llave privada
|
|
102
|
+
|
|
103
|
+
// 5) Generar el XML completo de un Documento Electrónico (Factura Electrónica) sin firmar
|
|
104
|
+
const xmlFactura = sifen.generateDocument({
|
|
105
|
+
emisor: {
|
|
106
|
+
rucEmisor: "80012345",
|
|
107
|
+
digitoVerificadorEmisor: "6",
|
|
108
|
+
idTipoContribuyente: 2,
|
|
109
|
+
razonSocial: "Consultame S.A.",
|
|
110
|
+
direccion: "Avda. Mariscal López 1234",
|
|
111
|
+
idDepartamento: 1,
|
|
112
|
+
idDistrito: 1,
|
|
113
|
+
idCiudad: 1,
|
|
114
|
+
telefono: "021123456",
|
|
115
|
+
},
|
|
116
|
+
cliente: {
|
|
117
|
+
idTipoReceptor: 1,
|
|
118
|
+
idTipoOperacion: 1,
|
|
119
|
+
idPais: "PRY",
|
|
120
|
+
esContribuyente: true,
|
|
121
|
+
idTipoContribuyente: 2,
|
|
122
|
+
ruc: "80099999",
|
|
123
|
+
digitoVerificado: "1",
|
|
124
|
+
razonSocial: "Cliente Ejemplo S.R.L.",
|
|
125
|
+
direccion: "Calle Falsa 123",
|
|
126
|
+
},
|
|
127
|
+
timbrado: {
|
|
128
|
+
timbrado: "12558651",
|
|
129
|
+
establecimiento: 1,
|
|
130
|
+
puntoExpedicion: 1,
|
|
131
|
+
numeroDocumento: 1,
|
|
132
|
+
fechaTimbrado: "2024-01-01",
|
|
133
|
+
},
|
|
134
|
+
idTipoEmision: 1,
|
|
135
|
+
datosOperacion: {
|
|
136
|
+
idTipoDocumento: 1, // 1 = Factura Electrónica
|
|
137
|
+
idMoneda: "PYG",
|
|
138
|
+
idTipoTransaccion: 1,
|
|
139
|
+
idTipoImpuesto: 1,
|
|
140
|
+
},
|
|
141
|
+
fechaEmision: "2024-05-20T10:00:00",
|
|
142
|
+
datosFactura: {
|
|
143
|
+
idPresencia: 1,
|
|
144
|
+
condicion: {
|
|
145
|
+
idCondicion: 1, // Contado
|
|
146
|
+
formasPagos: [{ idFormaPago: 1, idMoneda: "PYG", monto: 110000 }],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
detalleFactura: [
|
|
150
|
+
{
|
|
151
|
+
codigo: "0001",
|
|
152
|
+
descripcion: "Servicio de consultoría",
|
|
153
|
+
idUnidadMedida: 77, // Unidad
|
|
154
|
+
cantidad: 1,
|
|
155
|
+
precioUnitario: 110000,
|
|
156
|
+
idIva: 1, // Gravado IVA
|
|
157
|
+
ivaBase: 100,
|
|
158
|
+
tasaIva: 10,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 6) Firmar digitalmente (XMLDSig) el Documento Electrónico generado (asíncrono)
|
|
164
|
+
const xmlFirmado = await sifen.signedXml(xmlFactura, "DE", firma);
|
|
165
|
+
|
|
166
|
+
// 7) Generar el QR e incrustarlo en el documento electrónico ya firmado (asíncrono)
|
|
167
|
+
const xmlConQr = await sifen.generateQr({
|
|
168
|
+
xml: xmlFirmado, // XML del rDE ya firmado digitalmente
|
|
169
|
+
csc: "ABCD0000000000000000000000000000",
|
|
170
|
+
idCsc: "0001",
|
|
171
|
+
ambiente: "test", // "test" | "produccion"
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API pública
|
|
178
|
+
|
|
179
|
+
Todos los métodos viven en la clase `SifenService`. Son **síncronos**, salvo
|
|
180
|
+
[`signedXml`](#signedxmlxml-tag-responsedigitalsign) y
|
|
181
|
+
[`generateQr`](#generateqrgenerationqrdto), que son **asíncronos** y devuelven una `Promise`.
|
|
182
|
+
|
|
183
|
+
### `consultRuc(consultRucDto)`
|
|
184
|
+
|
|
185
|
+
Genera el sobre SOAP `rEnviConsRUC` para consultar los datos de un contribuyente por RUC.
|
|
186
|
+
|
|
187
|
+
| Parámetro | Tipo | Descripción |
|
|
188
|
+
| --------- | -------- | ------------------------------------- |
|
|
189
|
+
| `id` | `number` | Identificador de la petición (`dId`). |
|
|
190
|
+
| `ruc` | `string` | RUC del contribuyente a consultar. |
|
|
191
|
+
|
|
192
|
+
**Retorna:** `string` — XML SOAP normalizado (sin saltos de línea ni tabulaciones).
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const xml = sifen.consultRuc({ id: 1, ruc: "80012345-6" });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
<details>
|
|
199
|
+
<summary>Ejemplo de salida (formateada para lectura)</summary>
|
|
200
|
+
|
|
201
|
+
```xml
|
|
202
|
+
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
|
|
203
|
+
<env:Header/>
|
|
204
|
+
<env:Body>
|
|
205
|
+
<rEnviConsRUC xmlns="http://ekuatia.set.gov.py/sifen/xsd">
|
|
206
|
+
<dId>1</dId>
|
|
207
|
+
<dRUCCons>80012345-6</dRUCCons>
|
|
208
|
+
</rEnviConsRUC>
|
|
209
|
+
</env:Body>
|
|
210
|
+
</env:Envelope>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
</details>
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### `consultDocument(consultDocumentDto)`
|
|
218
|
+
|
|
219
|
+
Genera el sobre SOAP `rEnviConsDeRequest` para consultar un documento electrónico
|
|
220
|
+
(factura, nota, etc.) mediante su **CDC** (Código de Control).
|
|
221
|
+
|
|
222
|
+
| Parámetro | Tipo | Descripción |
|
|
223
|
+
| --------- | -------- | ------------------------------------- |
|
|
224
|
+
| `id` | `number` | Identificador de la petición (`dId`). |
|
|
225
|
+
| `cdc` | `string` | CDC de 44 dígitos del documento. |
|
|
226
|
+
|
|
227
|
+
**Retorna:** `string` — XML SOAP normalizado.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const xml = sifen.consultDocument({
|
|
231
|
+
id: 2,
|
|
232
|
+
cdc: "01800123456001001000000012023052212345678901",
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
<details>
|
|
237
|
+
<summary>Ejemplo de salida (formateada para lectura)</summary>
|
|
238
|
+
|
|
239
|
+
```xml
|
|
240
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
241
|
+
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
|
|
242
|
+
<env:Header/>
|
|
243
|
+
<env:Body>
|
|
244
|
+
<rEnviConsDeRequest xmlns="http://ekuatia.set.gov.py/sifen/xsd">
|
|
245
|
+
<dId>2</dId>
|
|
246
|
+
<dCDC>01800123456001001000000012023052212345678901</dCDC>
|
|
247
|
+
</rEnviConsDeRequest>
|
|
248
|
+
</env:Body>
|
|
249
|
+
</env:Envelope>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### `consultBatch(consultBatchDto)`
|
|
257
|
+
|
|
258
|
+
Genera el sobre SOAP `rEnviConsLoteDe` para consultar el estado de un **lote**
|
|
259
|
+
previamente enviado a SIFEN.
|
|
260
|
+
|
|
261
|
+
| Parámetro | Tipo | Descripción |
|
|
262
|
+
| ------------ | -------- | ----------------------------------------------- |
|
|
263
|
+
| `id` | `number` | Identificador de la petición (`dId`). |
|
|
264
|
+
| `codigoLote` | `string` | Número de protocolo del lote (`dProtConsLote`). |
|
|
265
|
+
|
|
266
|
+
**Retorna:** `string` — XML SOAP normalizado.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const xml = sifen.consultBatch({ id: 3, codigoLote: "1234567890" });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
<details>
|
|
273
|
+
<summary>Ejemplo de salida (formateada para lectura)</summary>
|
|
274
|
+
|
|
275
|
+
```xml
|
|
276
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
277
|
+
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
|
|
278
|
+
<env:Header/>
|
|
279
|
+
<env:Body>
|
|
280
|
+
<rEnviConsLoteDe xmlns="http://ekuatia.set.gov.py/sifen/xsd">
|
|
281
|
+
<dId>3</dId>
|
|
282
|
+
<dProtConsLote>1234567890</dProtConsLote>
|
|
283
|
+
</rEnviConsLoteDe>
|
|
284
|
+
</env:Body>
|
|
285
|
+
</env:Envelope>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
</details>
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### `obtainDigitalSign(digitalSignDto)`
|
|
293
|
+
|
|
294
|
+
Lee un archivo de firma digital `.p12`/`.pfx`, valida que contenga llave privada y
|
|
295
|
+
certificado, y los devuelve en formato **PEM**.
|
|
296
|
+
|
|
297
|
+
Un `.p12`/`.pfx` puede traer más de un certificado (por ejemplo la cadena completa emisor +
|
|
298
|
+
intermedia). Cuando hay varios, se selecciona el certificado cuya llave pública corresponde
|
|
299
|
+
efectivamente a la llave privada firmante (comparando el módulo RSA), no simplemente el
|
|
300
|
+
primero encontrado — usar el certificado equivocado en el `KeyInfo` hace que SIFEN rechace el
|
|
301
|
+
documento firmado con *"Valor de la firma (SignatureValue) diferente del calculado por el
|
|
302
|
+
PKI"*.
|
|
303
|
+
|
|
304
|
+
| Parámetro | Tipo | Descripción |
|
|
305
|
+
| ------------ | -------- | ---------------------------------------- |
|
|
306
|
+
| `camino` | `string` | Ruta al archivo `.p12`/`.pfx`. |
|
|
307
|
+
| `contraseña` | `string` | Contraseña que protege la firma digital. |
|
|
308
|
+
|
|
309
|
+
**Retorna:** `ResponseDigitalSign`
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
interface ResponseDigitalSign {
|
|
313
|
+
certificado: string; // certificado en formato PEM
|
|
314
|
+
llavePrivada: string; // llave privada en formato PEM
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Errores que puede lanzar:**
|
|
319
|
+
|
|
320
|
+
- `"La firma digital no contiene una llave privada"` — el `.p12` no tiene llave.
|
|
321
|
+
- `"La firma digital no contiene un certificado"` — el `.p12` no tiene ningún certificado.
|
|
322
|
+
- `"Ningún certificado de la firma digital corresponde a la llave privada"` — el `.p12` trae
|
|
323
|
+
certificados, pero ninguno hace pareja con la llave privada encontrada.
|
|
324
|
+
- Errores de `node-forge` si la contraseña es incorrecta o el archivo está corrupto.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
try {
|
|
328
|
+
const { certificado, llavePrivada } = sifen.obtainDigitalSign({
|
|
329
|
+
camino: "./certificados/firma.p12",
|
|
330
|
+
contraseña: "clave",
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
// contraseña incorrecta, archivo inválido o sin llave/certificado
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### `generateQr(generationQrDto)`
|
|
340
|
+
|
|
341
|
+
Toma un documento electrónico (`rDE`) **ya firmado digitalmente**, calcula el enlace del
|
|
342
|
+
código QR exigido por SIFEN y devuelve el **mismo XML con el nodo `<gCamFuFD><dCarQR>`
|
|
343
|
+
incrustado**.
|
|
344
|
+
|
|
345
|
+
> ⚠️ Este es el único método **asíncrono** de la librería: devuelve `Promise<string>`.
|
|
346
|
+
|
|
347
|
+
| Parámetro | Tipo | Descripción |
|
|
348
|
+
| ---------- | -------- | ---------------------------------------------------------------------- |
|
|
349
|
+
| `xml` | `string` | XML del `rDE` **firmado** (debe contener el nodo `Signature`). |
|
|
350
|
+
| `csc` | `string` | Código de Seguridad del Contribuyente (secreto, entregado por la SET). |
|
|
351
|
+
| `idCsc` | `string` | Identificador del CSC (ej. `"0001"`). |
|
|
352
|
+
| `ambiente` | `string` | `"test"` o `"produccion"`. Define el dominio base del enlace del QR. |
|
|
353
|
+
|
|
354
|
+
**Retorna:** `Promise<string>` — el XML reconstruido con el nodo `dCarQR`.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
const xmlConQr = await sifen.generateQr({
|
|
358
|
+
xml: xmlFirmado,
|
|
359
|
+
csc: "ABCD0000000000000000000000000000",
|
|
360
|
+
idCsc: "0001",
|
|
361
|
+
ambiente: "test",
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Cómo se construye el QR
|
|
366
|
+
|
|
367
|
+
1. Se parsea el XML y se **valida que exista el nodo `Signature`**; si no está, lanza error.
|
|
368
|
+
2. Se elige el dominio base según el ambiente:
|
|
369
|
+
- `test` → `https://ekuatia.set.gov.py/consultas-test`
|
|
370
|
+
- `produccion` → `https://ekuatia.set.gov.py/consultas`
|
|
371
|
+
3. Se le concatena `/qr?` y los parámetros extraídos del documento, **en este orden**:
|
|
372
|
+
|
|
373
|
+
| Parámetro | Origen en el XML | Notas |
|
|
374
|
+
| ------------------------- | -------------------------------------------------------- | ----------------------------------------------- |
|
|
375
|
+
| `nVersion` | `rDE > dVerFor` | Versión del formato. |
|
|
376
|
+
| `Id` | `rDE > DE[@Id]` | El CDC del documento. |
|
|
377
|
+
| `dFeEmiDE` | `rDE > DE > gDatGralOpe > dFeEmiDE` | Convertido a **hexadecimal**. |
|
|
378
|
+
| `dRucRec` _o_ `dNumIDRec` | `... > gDatRec` | `dRucRec` si `iNatRec == 1`, si no `dNumIDRec`. |
|
|
379
|
+
| `dTotGralOpe` | `rDE > DE > gTotSub > dTotGralOpe` | `0` si no existe. |
|
|
380
|
+
| `dTotIVA` | `rDE > DE > gTotSub > dTotIVA` | `0` si no existe. |
|
|
381
|
+
| `cItems` | Cantidad de `gDtipDE > gCamItem` | `0` si no hay ítems. |
|
|
382
|
+
| `DigestValue` | `rDE > Signature > SignedInfo > Reference > DigestValue` | Convertido a **hexadecimal**. |
|
|
383
|
+
| `IdCSC` | Parámetro `idCsc` | Último antes del hash. |
|
|
384
|
+
| `cHashQR` | `SHA256(cadena_de_parámetros + csc)` | Se agrega al final. |
|
|
385
|
+
|
|
386
|
+
4. El enlace resultante (con `&`, `<` y `>` escapados como entidades XML) se inserta como texto
|
|
387
|
+
plano en un nodo `<gCamFuFD><dCarQR>` justo antes del cierre `</rDE>`.
|
|
388
|
+
|
|
389
|
+
> No se reconstruye el XML completo con `xml2js.Builder`: eso obligaría a reserializar el
|
|
390
|
+
> bloque `DE`/`Signature` ya firmado y, aunque el resultado sea lógicamente equivalente,
|
|
391
|
+
> puede canonizar distinto al validador de SIFEN y producir el mismo error de
|
|
392
|
+
> *"Valor de la firma (SignatureValue) diferente del calculado por el PKI"* que se evita en
|
|
393
|
+
> [`obtainDigitalSign`](#obtaindigitalsigndigitalsigndto). Por eso se hace un slice de texto
|
|
394
|
+
> sobre el XML firmado en vez de volver a parsearlo y serializarlo, dejando intactos los
|
|
395
|
+
> bytes que fueron firmados.
|
|
396
|
+
|
|
397
|
+
El XML final que devuelve `SifenService.generateQr` (vía `SifenPersistence`) pasa además por
|
|
398
|
+
`normalizeXML`, quedando en una sola línea igual que el resto de los XML que genera la
|
|
399
|
+
librería.
|
|
400
|
+
|
|
401
|
+
<details>
|
|
402
|
+
<summary>Ejemplo del enlace generado dentro de <code>dCarQR</code></summary>
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
https://ekuatia.set.gov.py/consultas-test/qr?nVersion=150&Id=01800123456001001000000012023052212345678901&dFeEmiDE=323032332d30352d32325431303a30303a3030&dRucRec=80012345&dTotGralOpe=550000&dTotIVA=50000&cItems=3&DigestValue=6a4b2c...&IdCSC=0001&cHashQR=9f2a7c1e4b8d...
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Y así queda incrustado en el XML de salida:
|
|
409
|
+
|
|
410
|
+
```xml
|
|
411
|
+
<rDE>
|
|
412
|
+
<dVerFor>150</dVerFor>
|
|
413
|
+
<DE Id="01800123456001001000000012023052212345678901">
|
|
414
|
+
...
|
|
415
|
+
</DE>
|
|
416
|
+
<Signature>...</Signature>
|
|
417
|
+
<gCamFuFD>
|
|
418
|
+
<dCarQR>https://ekuatia.set.gov.py/consultas-test/qr?nVersion=150&Id=...&cHashQR=...</dCarQR>
|
|
419
|
+
</gCamFuFD>
|
|
420
|
+
</rDE>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
</details>
|
|
424
|
+
|
|
425
|
+
**Errores que puede lanzar:**
|
|
426
|
+
|
|
427
|
+
- `"XML debe estar firmado digitalmente para generar el QR"` — el XML no tiene nodo `Signature`.
|
|
428
|
+
- Errores de parseo de `xml2js` si el XML está malformado.
|
|
429
|
+
- `TypeError` si el documento no respeta la estructura esperada del `rDE` (por ejemplo,
|
|
430
|
+
falta `gDatRec` o `gDtipDE`).
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
try {
|
|
434
|
+
const xmlConQr = await sifen.generateQr({
|
|
435
|
+
xml: xmlFirmado,
|
|
436
|
+
csc: process.env.SIFEN_CSC!,
|
|
437
|
+
idCsc: "0001",
|
|
438
|
+
ambiente: "produccion",
|
|
439
|
+
});
|
|
440
|
+
} catch (error) {
|
|
441
|
+
// XML sin firmar, malformado o con estructura inesperada
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
> **Nota sobre el `ambiente`:** internamente se compara contra el enum `SIFEN_ENVIRONMENT`
|
|
446
|
+
> (`"test"` / `"produccion"`). Cualquier valor distinto de `"test"` cae en el enlace de
|
|
447
|
+
> **producción**, así que conviene pasar el valor exacto.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### `generateDocument(generateDocumentoElectronicoDto)`
|
|
452
|
+
|
|
453
|
+
Genera el XML completo del **Documento Electrónico** (`rDE`) — Factura Electrónica, Autofactura,
|
|
454
|
+
Nota de Crédito/Débito o Remisión — según el Manual Técnico de SIFEN: arma el CDC, la cabecera,
|
|
455
|
+
los datos generales de emisor/receptor, el detalle de ítems (con sus cálculos de precio,
|
|
456
|
+
descuentos, anticipos e IVA) y los totales del documento.
|
|
457
|
+
|
|
458
|
+
> ⚠️ El XML devuelto **no está firmado**. Es el paso previo a `generateQr`: primero se genera
|
|
459
|
+
> este XML, la aplicación consumidora lo firma digitalmente (XMLDSig), y recién ese XML firmado
|
|
460
|
+
> se le pasa a `generateQr` para incrustar el código QR.
|
|
461
|
+
|
|
462
|
+
`generateDocumentoElectronicoDto` recibe todos los datos del documento directamente (ver
|
|
463
|
+
estructura abajo).
|
|
464
|
+
|
|
465
|
+
**Retorna:** `string` — XML del `rDE` normalizado (sin saltos de línea ni tabulaciones), listo
|
|
466
|
+
para firmar.
|
|
467
|
+
|
|
468
|
+
#### Estructura de `generateDocumentoElectronicoDto`
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
interface GenerateDocumentoElectronicoDto {
|
|
472
|
+
emisor: EmisorDto; // datos del emisor (RUC, razón social, dirección, ubicación...)
|
|
473
|
+
cliente: ClienteDto; // datos del receptor
|
|
474
|
+
timbrado: TimbradoDto; // timbrado, establecimiento, punto de expedición, número
|
|
475
|
+
idTipoEmision: number; // 1 = Normal, 2 = Contingencia
|
|
476
|
+
datosOperacion: DatosOperacionDto; // idTipoDocumento, idMoneda, idTipoTransaccion, idTipoImpuesto...
|
|
477
|
+
fechaEmision: string; // fecha/hora de emisión (dFeEmiDE)
|
|
478
|
+
datosFactura: DatosEspecificosFacturaDto; // campos específicos según idTipoDocumento (ver abajo)
|
|
479
|
+
detalleFactura: DetalleFacturaItemDto[]; // ítems de la operación
|
|
480
|
+
documentosAsociados?: DocumentosAsociadosDto;
|
|
481
|
+
transporte?: TransporteDto; // solo Factura (1) y Remisión (7)
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
`datosOperacion.idTipoDocumento` determina qué documento se genera y qué otros campos son
|
|
486
|
+
obligatorios:
|
|
487
|
+
|
|
488
|
+
| `idTipoDocumento` | Documento | Campo específico requerido en `datosFactura` |
|
|
489
|
+
| ------------------ | ----------------------- | ----------------------------------------------------------- |
|
|
490
|
+
| `1` | Factura Electrónica | `condicion` (y opcionalmente `comprasPublicas` si `idTipoOperacion === 3`) |
|
|
491
|
+
| `4` | Autofactura | `autoFactura` |
|
|
492
|
+
| `5` / `6` | Nota de Crédito/Débito | `notaDebitoCredito` |
|
|
493
|
+
| `7` | Remisión | `remision` |
|
|
494
|
+
|
|
495
|
+
Todos los códigos (`idDepartamento`, `idDistrito`, `idCiudad`, `idPais`, `idUnidadMedida`,
|
|
496
|
+
`idTipoContribuyente`, `idIva`, `idFormaPago`, etc.) deben existir en los catálogos de
|
|
497
|
+
[`src/shared/utils/dictionary.utils.ts`](src/shared/utils/dictionary.utils.ts); un código
|
|
498
|
+
inexistente hace que la librería lance un error al no encontrar la descripción asociada.
|
|
499
|
+
|
|
500
|
+
<details>
|
|
501
|
+
<summary>Ejemplo completo — Factura Electrónica al contado, un ítem gravado a 10% de IVA</summary>
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
const xml = sifen.generateDocument({
|
|
505
|
+
emisor: {
|
|
506
|
+
rucEmisor: "80012345",
|
|
507
|
+
digitoVerificadorEmisor: "6",
|
|
508
|
+
idTipoContribuyente: 2,
|
|
509
|
+
razonSocial: "Consultame S.A.",
|
|
510
|
+
direccion: "Avda. Mariscal López 1234",
|
|
511
|
+
idDepartamento: 1, // CAPITAL
|
|
512
|
+
idDistrito: 1, // ASUNCION (DISTRITO)
|
|
513
|
+
idCiudad: 1, // ASUNCION (DISTRITO)
|
|
514
|
+
telefono: "021123456",
|
|
515
|
+
},
|
|
516
|
+
cliente: {
|
|
517
|
+
idTipoReceptor: 1,
|
|
518
|
+
idTipoOperacion: 1,
|
|
519
|
+
idPais: "PRY",
|
|
520
|
+
esContribuyente: true,
|
|
521
|
+
idTipoContribuyente: 2,
|
|
522
|
+
ruc: "80099999",
|
|
523
|
+
digitoVerificado: "1",
|
|
524
|
+
razonSocial: "Cliente Ejemplo S.R.L.",
|
|
525
|
+
direccion: "Calle Falsa 123",
|
|
526
|
+
},
|
|
527
|
+
timbrado: {
|
|
528
|
+
timbrado: "12558651",
|
|
529
|
+
establecimiento: 1,
|
|
530
|
+
puntoExpedicion: 1,
|
|
531
|
+
numeroDocumento: 1,
|
|
532
|
+
fechaTimbrado: "2024-01-01",
|
|
533
|
+
},
|
|
534
|
+
idTipoEmision: 1,
|
|
535
|
+
datosOperacion: {
|
|
536
|
+
idTipoDocumento: 1, // Factura Electrónica
|
|
537
|
+
idMoneda: "PYG",
|
|
538
|
+
idTipoTransaccion: 1, // Venta de mercadería
|
|
539
|
+
idTipoImpuesto: 1, // IVA
|
|
540
|
+
},
|
|
541
|
+
fechaEmision: "2024-05-20T10:00:00",
|
|
542
|
+
datosFactura: {
|
|
543
|
+
idPresencia: 1, // Operación presencial
|
|
544
|
+
condicion: {
|
|
545
|
+
idCondicion: 1, // Contado
|
|
546
|
+
formasPagos: [{ idFormaPago: 1, idMoneda: "PYG", monto: 110000 }], // Efectivo
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
detalleFactura: [
|
|
550
|
+
{
|
|
551
|
+
codigo: "0001",
|
|
552
|
+
descripcion: "Servicio de consultoría",
|
|
553
|
+
idUnidadMedida: 77, // Unidad
|
|
554
|
+
cantidad: 1,
|
|
555
|
+
precioUnitario: 110000,
|
|
556
|
+
idIva: 1, // Gravado IVA
|
|
557
|
+
ivaBase: 100, // proporción gravada (100%)
|
|
558
|
+
tasaIva: 10, // tasa de IVA 10%
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
});
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
</details>
|
|
565
|
+
|
|
566
|
+
**Errores que puede lanzar:**
|
|
567
|
+
|
|
568
|
+
- `TypeError` (`Cannot read properties of undefined`) si falta el campo específico requerido
|
|
569
|
+
para el `idTipoDocumento` indicado (ej. `condicion` en una Factura).
|
|
570
|
+
- Error implícito de `.find(...)!` si algún código (`idDepartamento`, `idIva`, `idUnidadMedida`,
|
|
571
|
+
etc.) no existe en el diccionario correspondiente.
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
try {
|
|
575
|
+
const xml = sifen.generateDocument({ ... });
|
|
576
|
+
} catch (error) {
|
|
577
|
+
// datos faltantes según el tipo de documento, o código inexistente en el diccionario
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
### `signedXml(xml, tag, responseDigitalSign)`
|
|
584
|
+
|
|
585
|
+
Firma digitalmente (**XMLDSig**) un XML usando `xml-crypto`. Es el paso que conecta
|
|
586
|
+
`generateDocument` con `generateQr`: primero se genera el XML del `rDE` sin firmar, luego se
|
|
587
|
+
firma con este método, y recién ese XML firmado se le pasa a `generateQr`.
|
|
588
|
+
|
|
589
|
+
> ⚠️ Método **asíncrono**: devuelve `Promise<string>`.
|
|
590
|
+
|
|
591
|
+
| Parámetro | Tipo | Descripción |
|
|
592
|
+
| ---------------------- | ---------------------- | ----------------------------------------------------------------------------- |
|
|
593
|
+
| `xml` | `string` | XML sin firmar (ej. el `rDE` devuelto por `generateDocument`). |
|
|
594
|
+
| `tag` | `string` | Nombre local del nodo a firmar (ej. `"DE"` para firmar el nodo `DE` del `rDE`). |
|
|
595
|
+
| `responseDigitalSign` | `ResponseDigitalSign` | `{ certificado, llavePrivada }` en PEM, obtenido de `obtainDigitalSign`. |
|
|
596
|
+
|
|
597
|
+
**Retorna:** `Promise<string>` — el XML original con el nodo `<Signature>` insertado.
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
const firma = sifen.obtainDigitalSign({
|
|
601
|
+
camino: "./certificados/firma.p12",
|
|
602
|
+
contraseña: "mi-clave-secreta",
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const xmlFirmado = await sifen.signedXml(xmlFactura, "DE", firma);
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
#### Cómo firma
|
|
609
|
+
|
|
610
|
+
1. Ubica el nodo a firmar por su nombre local (`xpath: //*[local-name()='<tag>']`), sin importar
|
|
611
|
+
el namespace en el que esté declarado.
|
|
612
|
+
2. Aplica las transformaciones `enveloped-signature` y **Exclusive Canonicalization** (`exc-c14n`)
|
|
613
|
+
sobre ese nodo, y calcula el `DigestValue` con **SHA-256**.
|
|
614
|
+
3. Firma con **RSA-SHA256** (`rsa-sha256`) usando la llave privada provista.
|
|
615
|
+
4. Arma el `<KeyInfo>` incrustando el certificado como `<X509Data><X509Certificate>` (el PEM
|
|
616
|
+
se limpia de cabeceras/pies y saltos de línea antes de incrustarlo).
|
|
617
|
+
5. Inserta el `<Signature>` resultante **inmediatamente después** del nodo referenciado por
|
|
618
|
+
`tag` (`location: { reference: "//*[local-name()='<tag>']", action: "after" }`), tal como
|
|
619
|
+
exige el Manual Técnico de SIFEN para el `rDE` (la firma va como hermana del `DE`, dentro
|
|
620
|
+
de `rDE`).
|
|
621
|
+
|
|
622
|
+
**Errores que puede lanzar:**
|
|
623
|
+
|
|
624
|
+
- Cualquier error de `xml-crypto` si el `xml` no contiene un nodo que coincida con `tag`, o si
|
|
625
|
+
`certificado`/`llavePrivada` no son un par PEM válido.
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
try {
|
|
629
|
+
const xmlFirmado = await sifen.signedXml(xmlFactura, "DE", firma);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
// tag inexistente en el xml, o certificado/llave privada inválidos
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## DTOs (contratos de entrada)
|
|
638
|
+
|
|
639
|
+
Todos los DTOs de consulta extienden `GenerateIdDto`, que aporta el campo `id`.
|
|
640
|
+
|
|
641
|
+
```ts
|
|
642
|
+
interface GenerateIdDto {
|
|
643
|
+
id: number;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
interface ConsultRucDto extends GenerateIdDto {
|
|
647
|
+
ruc: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
interface ConsultDocumentDto extends GenerateIdDto {
|
|
651
|
+
cdc: string;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
interface ConsultBatchDto extends GenerateIdDto {
|
|
655
|
+
codigoLote: string;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
interface DigitalSignDto {
|
|
659
|
+
camino: string; // ruta al .p12
|
|
660
|
+
contraseña: string; // clave del .p12
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
interface GenerationQrDto {
|
|
664
|
+
xml: string; // XML del rDE ya firmado
|
|
665
|
+
csc: string; // Código de Seguridad del Contribuyente
|
|
666
|
+
idCsc: string; // Identificador del CSC
|
|
667
|
+
ambiente: string; // "test" | "produccion"
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Enums disponibles en [`src/shared/utils/dictionary.utils.ts`](src/shared/utils/dictionary.utils.ts):
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
enum SIFEN_ENVIRONMENT {
|
|
675
|
+
TEST = "test",
|
|
676
|
+
PRODUCCION = "produccion",
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
enum LINK_SIFEN {
|
|
680
|
+
TEST = "https://ekuatia.set.gov.py/consultas-test",
|
|
681
|
+
PRODUCCION = "https://ekuatia.set.gov.py/consultas",
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
DTOs ya definidos pero **aún sin implementación** en la persistencia (ver
|
|
686
|
+
[roadmap](#estado--roadmap)):
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
interface CancelInvoiceDto extends GenerateIdDto {
|
|
690
|
+
cdc: string;
|
|
691
|
+
motivo: string;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
interface InvalidationInvoiceDto extends GenerateIdDto {
|
|
695
|
+
timbrado: string;
|
|
696
|
+
establecimiento: number;
|
|
697
|
+
puntoExpedicion: number;
|
|
698
|
+
desde: number;
|
|
699
|
+
hasta: number;
|
|
700
|
+
tipoDocumento: number;
|
|
701
|
+
motivo: string;
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
### DTOs de `generateDocument`
|
|
706
|
+
|
|
707
|
+
`generateDocument` no extiende `GenerateIdDto` — usa su propia familia de DTOs, definida en
|
|
708
|
+
[`src/shared/clases/documento.dto.ts`](src/shared/clases/documento.dto.ts) en vez del barrel
|
|
709
|
+
`src/sifen/applications/dtos`. El contrato de entrada es `GenerateDocumentoElectronicoDto`
|
|
710
|
+
(ver [estructura de `generateDocumentoElectronicoDto`](#estructura-de-generatedocumentoelectronicodto)
|
|
711
|
+
más arriba); ahí también viven
|
|
712
|
+
`EmisorDto`, `ClienteDto`, `TimbradoDto`, `DetalleFacturaItemDto` y el resto de los tipos
|
|
713
|
+
específicos por sección del Manual Técnico (autofactura, nota de crédito/débito, remisión,
|
|
714
|
+
transporte, condición de pago, etc.).
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Funciones utilitarias internas
|
|
719
|
+
|
|
720
|
+
Viven en [`src/shared/functions/functions.utils.ts`](src/shared/functions/functions.utils.ts).
|
|
721
|
+
No forman parte de la API pública, pero es útil conocerlas:
|
|
722
|
+
|
|
723
|
+
| Función | Qué hace |
|
|
724
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------- |
|
|
725
|
+
| `generateConsultRucXml` | Arma el string XML de consulta de RUC. |
|
|
726
|
+
| `generateConsultDocumentXml` | Arma el string XML de consulta de documento por CDC. |
|
|
727
|
+
| `generateConsultBatchXml` | Arma el string XML de consulta de lote. |
|
|
728
|
+
| `normalizeXML` | Colapsa el XML a una sola línea: elimina saltos, tabulaciones y espacios entre etiquetas. |
|
|
729
|
+
| `readDigitalSign` | Lee el `.p12` con `node-forge` y extrae certificado + llave privada en PEM, emparejando el certificado correcto por módulo RSA cuando hay varios. |
|
|
730
|
+
| `signXml` | Firma un XML (XMLDSig) con `xml-crypto`: `enveloped-signature` + `exc-c14n`, digest SHA-256, firma RSA-SHA256. _(async)_ |
|
|
731
|
+
| `generateQr` | Arma el enlace del QR (params + hash SHA-256) y lo incrusta como texto en `gCamFuFD > dCarQR` sin reserializar el XML firmado. _(async)_ |
|
|
732
|
+
| `calculateDigitVerification` / `calcularDigitoVerificador` | Dos implementaciones (histórica y actual) del dígito verificador módulo 11, usadas por el CDC. |
|
|
733
|
+
|
|
734
|
+
Además, la generación del Documento Electrónico completo vive en dos clases dedicadas dentro de
|
|
735
|
+
[`src/shared/clases/`](src/shared/clases/), usadas por `SifenPersistence.generateDocument`:
|
|
736
|
+
|
|
737
|
+
| Clase | Archivo | Qué hace |
|
|
738
|
+
| ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
739
|
+
| `Documento` | `documento.ts` | Arma el objeto `rDE` sección por sección (CDC, cabecera, timbrado, datos generales, ítems, totales, documentos asociados, transporte) y lo serializa a XML con `xml2js.Builder`. |
|
|
740
|
+
| `DetalleDocumento` | `detalleDocumento.ts` | Cálculos por ítem (precio, tipo de cambio, descuentos, anticipos e IVA) que consume `Documento` al armar el detalle. Métodos estáticos. |
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## Arquitectura del proyecto
|
|
745
|
+
|
|
746
|
+
El proyecto sigue una **arquitectura hexagonal (puertos y adaptadores)** combinada con
|
|
747
|
+
principios de **Clean Architecture**, organizada por el módulo de negocio `sifen`.
|
|
748
|
+
|
|
749
|
+
```
|
|
750
|
+
┌──────────────────────────────────────────────┐
|
|
751
|
+
│ applications │
|
|
752
|
+
│ SifenService (punto de entrada / fachada) │
|
|
753
|
+
│ DTOs (contratos de entrada) │
|
|
754
|
+
└───────────────────────┬──────────────────────┘
|
|
755
|
+
│ orquesta
|
|
756
|
+
▼
|
|
757
|
+
┌──────────────────────────────────────────────┐
|
|
758
|
+
│ domain │
|
|
759
|
+
│ useCases (ConsultRuc, ConsultBatch, ...) │
|
|
760
|
+
│ repository (SifenRepository = PUERTO) │
|
|
761
|
+
└───────────────────────┬──────────────────────┘
|
|
762
|
+
│ implementado por
|
|
763
|
+
▼
|
|
764
|
+
┌──────────────────────────────────────────────┐
|
|
765
|
+
│ infrastructure │
|
|
766
|
+
│ SifenPersistence (ADAPTADOR del puerto) │
|
|
767
|
+
│ → funciones utilitarias (XML, forge) │
|
|
768
|
+
│ → clases Documento / DetalleDocumento │
|
|
769
|
+
└──────────────────────────────────────────────┘
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Flujo de una llamada** (ej. `consultRuc`):
|
|
773
|
+
|
|
774
|
+
1. La app llama a `SifenService.consultRuc(dto)` — capa **applications**.
|
|
775
|
+
2. El servicio delega en `ConsultRucUseCase.execute(dto)` — capa **domain**.
|
|
776
|
+
3. El caso de uso invoca la interfaz `SifenRepository` (**puerto**), sin conocer la implementación.
|
|
777
|
+
4. En runtime el puerto lo resuelve `SifenPersistence` (**adaptador**) — capa **infrastructure**.
|
|
778
|
+
5. La persistencia usa las funciones utilitarias para generar y normalizar el XML.
|
|
779
|
+
|
|
780
|
+
`generateDocument` sigue el mismo flujo (`SifenService.generateDocument` →
|
|
781
|
+
`GenerateDocumentUseCase` → `SifenRepository` → `SifenPersistence.generateDocument`), pero en el
|
|
782
|
+
paso 5 la persistencia delega en la clase `Documento` (`src/shared/clases/documento.ts`) en vez
|
|
783
|
+
de en una función utilitaria simple, ya que el armado del Documento Electrónico completo requiere
|
|
784
|
+
mantener estado intermedio (`jsonFactura`) mientras se arma cada sección del XML.
|
|
785
|
+
|
|
786
|
+
`signedXml` también sigue el mismo flujo (`SifenService.signedXml` → `SignedXmlUseCase` →
|
|
787
|
+
`SifenRepository` → `SifenPersistence.signedXml`), delegando en la función utilitaria `signXml`
|
|
788
|
+
(`src/shared/functions/functions.utils.ts`), que envuelve `xml-crypto`.
|
|
789
|
+
|
|
790
|
+
**Por qué esta separación:**
|
|
791
|
+
|
|
792
|
+
- **`domain`** no depende de nada externo: define _qué_ se hace (casos de uso) y el
|
|
793
|
+
contrato `SifenRepository`. Es el núcleo estable.
|
|
794
|
+
- **`infrastructure`** contiene los detalles concretos (armado de XML, `node-forge`, `fs`).
|
|
795
|
+
Se puede reemplazar sin tocar el dominio.
|
|
796
|
+
- **`applications`** expone la fachada y los DTOs que consume el mundo exterior.
|
|
797
|
+
- **`shared`** agrupa utilidades y DTOs de respuesta transversales a los módulos.
|
|
798
|
+
|
|
799
|
+
La inversión de dependencias es clave: `SifenPersistence implements SifenRepository`, de
|
|
800
|
+
modo que el dominio depende de la **abstracción**, no de la implementación.
|
|
801
|
+
|
|
802
|
+
---
|
|
803
|
+
|
|
804
|
+
## Estructura de carpetas
|
|
805
|
+
|
|
806
|
+
```
|
|
807
|
+
consultame-sdk/
|
|
808
|
+
├── src/
|
|
809
|
+
│ ├── index.ts # Punto de entrada público → exporta SifenService
|
|
810
|
+
│ │
|
|
811
|
+
│ ├── shared/ # Código transversal
|
|
812
|
+
│ │ ├── clases/
|
|
813
|
+
│ │ │ ├── documento.ts # Documento: arma el rDE completo (Factura, Nota, Remisión...)
|
|
814
|
+
│ │ │ ├── documento.dto.ts # DTOs de generateDocument (EmisorDto, ClienteDto, DetalleFacturaItemDto...)
|
|
815
|
+
│ │ │ └── detalleDocumento.ts # DetalleDocumento: cálculos por ítem (precio, IVA, descuentos)
|
|
816
|
+
│ │ ├── dto/
|
|
817
|
+
│ │ │ └── responseDigitalSign.dto.ts # ResponseDigitalSign { certificado, llavePrivada }
|
|
818
|
+
│ │ ├── functions/
|
|
819
|
+
│ │ │ └── functions.utils.ts # XML, normalizeXML, readDigitalSign, generateQr, DV
|
|
820
|
+
│ │ └── utils/
|
|
821
|
+
│ │ └── dictionary.utils.ts # Catálogos SIFEN (departamentos, monedas, tipos...) y enums SIFEN_ENVIRONMENT / LINK_SIFEN
|
|
822
|
+
│ │
|
|
823
|
+
│ └── sifen/ # Módulo de negocio SIFEN
|
|
824
|
+
│ ├── applications/ # Capa de aplicación (fachada + contratos)
|
|
825
|
+
│ │ ├── dtos/
|
|
826
|
+
│ │ │ ├── index.ts # Barrel de DTOs
|
|
827
|
+
│ │ │ ├── generateId.dto.ts # GenerateIdDto (base)
|
|
828
|
+
│ │ │ ├── consultRuc.dto.ts
|
|
829
|
+
│ │ │ ├── consultDocument.dto.ts
|
|
830
|
+
│ │ │ ├── consultBatch.dto.ts
|
|
831
|
+
│ │ │ ├── digitalSign.dto.ts
|
|
832
|
+
│ │ │ ├── generationQr.dto.ts
|
|
833
|
+
│ │ │ ├── cancelDocument.dto.ts
|
|
834
|
+
│ │ │ └── invalidationInvoice.dto.ts
|
|
835
|
+
│ │ └── services/
|
|
836
|
+
│ │ └── sifen.service.ts # SifenService (fachada pública)
|
|
837
|
+
│ │
|
|
838
|
+
│ ├── domain/ # Núcleo de negocio
|
|
839
|
+
│ │ ├── repository/
|
|
840
|
+
│ │ │ └── sifen.repository.ts # SifenRepository (PUERTO / interfaz)
|
|
841
|
+
│ │ └── useCases/
|
|
842
|
+
│ │ ├── index.ts # Barrel de casos de uso
|
|
843
|
+
│ │ ├── consultRuc.useCase.ts
|
|
844
|
+
│ │ ├── consultDocument.useCase.ts
|
|
845
|
+
│ │ ├── consultBatch.useCase.ts
|
|
846
|
+
│ │ ├── obtainDigitalSign.useCase.ts
|
|
847
|
+
│ │ ├── generateQr.useCase.ts
|
|
848
|
+
│ │ ├── generateDocument.useCase.ts
|
|
849
|
+
│ │ └── signedXml.useCase.ts
|
|
850
|
+
│ │
|
|
851
|
+
│ └── infrastructure/ # Detalles concretos (ADAPTADORES)
|
|
852
|
+
│ └── persistence/
|
|
853
|
+
│ └── sifen.persistence.ts # SifenPersistence implements SifenRepository
|
|
854
|
+
│
|
|
855
|
+
├── package.json
|
|
856
|
+
├── tsconfig.json
|
|
857
|
+
├── tsup.config.ts # Build dual CJS + ESM (target node22)
|
|
858
|
+
├── pnpm-workspace.yaml
|
|
859
|
+
└── README.md
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## Scripts de desarrollo
|
|
865
|
+
|
|
866
|
+
| Script | Comando | Descripción |
|
|
867
|
+
| ------------------ | --------------------------- | --------------------------------------------------- |
|
|
868
|
+
| `pnpm build` | `tsup && build:types` | Compila a `dist/` (CJS + ESM) y genera los `.d.ts`. |
|
|
869
|
+
| `pnpm build:types` | `tsc --emitDeclarationOnly` | Solo emite las declaraciones de tipos. |
|
|
870
|
+
| `pnpm dev` | `tsup --watch` | Build en modo watch. |
|
|
871
|
+
| `pnpm typecheck` | `tsc --noEmit` | Verifica tipos sin emitir archivos. |
|
|
872
|
+
| `pnpm play` | `tsx src/index.ts` | Ejecuta `src/index.ts` para pruebas manuales. |
|
|
873
|
+
| `pnpm play:watch` | `tsx watch src/index.ts` | Igual que `play` pero en modo watch. |
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## Estado / roadmap
|
|
878
|
+
|
|
879
|
+
Funcionalidades **implementadas**:
|
|
880
|
+
|
|
881
|
+
- ✅ Consulta de RUC (`consultRuc`)
|
|
882
|
+
- ✅ Consulta de documento por CDC (`consultDocument`)
|
|
883
|
+
- ✅ Consulta de lote (`consultBatch`)
|
|
884
|
+
- ✅ Lectura de firma digital `.p12` → PEM (`obtainDigitalSign`)
|
|
885
|
+
- ✅ Generación e incrustación del código QR (`generateQr`)
|
|
886
|
+
- ✅ Generación del XML completo del Documento Electrónico — Factura, Autofactura, Nota de
|
|
887
|
+
Crédito/Débito y Remisión (`generateDocument`)
|
|
888
|
+
- ✅ Firma digital XMLDSig del Documento Electrónico (`signedXml`)
|
|
889
|
+
|
|
890
|
+
Definidas pero **pendientes de implementación** (hoy lanzan `Method not implemented`
|
|
891
|
+
o aún no están expuestas en `SifenService`):
|
|
892
|
+
|
|
893
|
+
- ⏳ Cancelación de documento (`cancelationInvoice` / `CancelInvoiceDto`)
|
|
894
|
+
- ⏳ Inutilización de documentos (`invalidationInvoice` / `InvalidationInvoiceDto`)
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
> **Nota:** esta librería genera insumos (XML, credenciales, firma XMLDSig y el QR) para SIFEN,
|
|
899
|
+
> pero no realiza el envío HTTP de los documentos — eso queda a cargo de la aplicación
|
|
900
|
+
> consumidora. `generateQr` **requiere** que el XML ya venga firmado (con `signedXml` o por
|
|
901
|
+
> cualquier otro medio), porque toma el `DigestValue` de la firma para calcular el hash del QR.
|