arca-sdk 0.4.0 → 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/CHANGELOG.md +142 -0
- package/README.md +125 -202
- package/dist/index.cjs +532 -329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +283 -172
- package/dist/index.d.ts +283 -172
- package/dist/index.js +527 -325
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -23,10 +23,11 @@ function getPadronEndpoint(environment) {
|
|
|
23
23
|
|
|
24
24
|
// src/types/common.ts
|
|
25
25
|
var ArcaError = class extends Error {
|
|
26
|
-
constructor(message, code, details) {
|
|
26
|
+
constructor(message, code, details, hint) {
|
|
27
27
|
super(message);
|
|
28
28
|
this.code = code;
|
|
29
29
|
this.details = details;
|
|
30
|
+
this.hint = hint;
|
|
30
31
|
this.name = "ArcaError";
|
|
31
32
|
}
|
|
32
33
|
};
|
|
@@ -441,67 +442,127 @@ var WsaaService = class {
|
|
|
441
442
|
};
|
|
442
443
|
|
|
443
444
|
// src/types/wsfe.ts
|
|
444
|
-
var
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return
|
|
452
|
-
})(
|
|
453
|
-
var
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return
|
|
458
|
-
})(
|
|
459
|
-
var
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return
|
|
472
|
-
})(
|
|
445
|
+
var InvoiceType = /* @__PURE__ */ ((InvoiceType2) => {
|
|
446
|
+
InvoiceType2[InvoiceType2["FACTURA_A"] = 1] = "FACTURA_A";
|
|
447
|
+
InvoiceType2[InvoiceType2["FACTURA_B"] = 6] = "FACTURA_B";
|
|
448
|
+
InvoiceType2[InvoiceType2["FACTURA_C"] = 11] = "FACTURA_C";
|
|
449
|
+
InvoiceType2[InvoiceType2["TICKET_A"] = 81] = "TICKET_A";
|
|
450
|
+
InvoiceType2[InvoiceType2["TICKET_B"] = 82] = "TICKET_B";
|
|
451
|
+
InvoiceType2[InvoiceType2["TICKET_C"] = 83] = "TICKET_C";
|
|
452
|
+
return InvoiceType2;
|
|
453
|
+
})(InvoiceType || {});
|
|
454
|
+
var BillingConcept = /* @__PURE__ */ ((BillingConcept2) => {
|
|
455
|
+
BillingConcept2[BillingConcept2["PRODUCTS"] = 1] = "PRODUCTS";
|
|
456
|
+
BillingConcept2[BillingConcept2["SERVICES"] = 2] = "SERVICES";
|
|
457
|
+
BillingConcept2[BillingConcept2["PRODUCTS_AND_SERVICES"] = 3] = "PRODUCTS_AND_SERVICES";
|
|
458
|
+
return BillingConcept2;
|
|
459
|
+
})(BillingConcept || {});
|
|
460
|
+
var TaxIdType = /* @__PURE__ */ ((TaxIdType2) => {
|
|
461
|
+
TaxIdType2[TaxIdType2["CUIT"] = 80] = "CUIT";
|
|
462
|
+
TaxIdType2[TaxIdType2["CUIL"] = 86] = "CUIL";
|
|
463
|
+
TaxIdType2[TaxIdType2["CDI"] = 87] = "CDI";
|
|
464
|
+
TaxIdType2[TaxIdType2["LE"] = 89] = "LE";
|
|
465
|
+
TaxIdType2[TaxIdType2["LC"] = 90] = "LC";
|
|
466
|
+
TaxIdType2[TaxIdType2["FOREIGN_ID"] = 91] = "FOREIGN_ID";
|
|
467
|
+
TaxIdType2[TaxIdType2["PASSPORT"] = 94] = "PASSPORT";
|
|
468
|
+
TaxIdType2[TaxIdType2["BUENOS_AIRES_ID"] = 95] = "BUENOS_AIRES_ID";
|
|
469
|
+
TaxIdType2[TaxIdType2["NATIONAL_POLICE_ID"] = 96] = "NATIONAL_POLICE_ID";
|
|
470
|
+
TaxIdType2[TaxIdType2["DNI"] = 96] = "DNI";
|
|
471
|
+
TaxIdType2[TaxIdType2["FINAL_CONSUMER"] = 99] = "FINAL_CONSUMER";
|
|
472
|
+
return TaxIdType2;
|
|
473
|
+
})(TaxIdType || {});
|
|
473
474
|
|
|
474
475
|
// src/utils/calculations.ts
|
|
475
|
-
function
|
|
476
|
+
function calculateSubtotal(items, includesVAT = false) {
|
|
476
477
|
return items.reduce((sum, item) => {
|
|
477
|
-
let
|
|
478
|
-
if (
|
|
479
|
-
|
|
478
|
+
let netPrice = item.unitPrice;
|
|
479
|
+
if (includesVAT && item.vatRate) {
|
|
480
|
+
netPrice = item.unitPrice / (1 + item.vatRate / 100);
|
|
480
481
|
}
|
|
481
|
-
return sum + item.
|
|
482
|
+
return sum + item.quantity * netPrice;
|
|
482
483
|
}, 0);
|
|
483
484
|
}
|
|
484
|
-
function
|
|
485
|
+
function calculateVAT(items, includesVAT = false) {
|
|
485
486
|
return items.reduce((sum, item) => {
|
|
486
|
-
const
|
|
487
|
-
let
|
|
488
|
-
if (
|
|
489
|
-
|
|
487
|
+
const rate = item.vatRate || 0;
|
|
488
|
+
let netPrice = item.unitPrice;
|
|
489
|
+
if (includesVAT && rate) {
|
|
490
|
+
netPrice = item.unitPrice / (1 + rate / 100);
|
|
490
491
|
}
|
|
491
|
-
const
|
|
492
|
-
return sum +
|
|
492
|
+
const netSubtotal = item.quantity * netPrice;
|
|
493
|
+
return sum + netSubtotal * rate / 100;
|
|
493
494
|
}, 0);
|
|
494
495
|
}
|
|
495
|
-
function
|
|
496
|
-
if (
|
|
497
|
-
return items.reduce((sum, item) => sum + item.
|
|
496
|
+
function calculateTotal(items, includesVAT = false) {
|
|
497
|
+
if (includesVAT) {
|
|
498
|
+
return items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
|
|
498
499
|
}
|
|
499
|
-
const subtotal =
|
|
500
|
-
const
|
|
501
|
-
return subtotal +
|
|
500
|
+
const subtotal = calculateSubtotal(items, false);
|
|
501
|
+
const vat = calculateVAT(items, false);
|
|
502
|
+
return subtotal + vat;
|
|
502
503
|
}
|
|
503
|
-
function
|
|
504
|
-
return Math.round(
|
|
504
|
+
function round(value) {
|
|
505
|
+
return Math.round(value * 100) / 100;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/utils/qr.ts
|
|
509
|
+
function generateQRUrl(caeResponse, issuerCUIT, total, buyer) {
|
|
510
|
+
const cleanCUIT = issuerCUIT.replace(/\D/g, "");
|
|
511
|
+
const cleanCAE = caeResponse.cae.replace(/\D/g, "");
|
|
512
|
+
const rawDate = caeResponse.date;
|
|
513
|
+
const formattedDate = rawDate.length === 8 ? `${rawDate.substring(0, 4)}-${rawDate.substring(4, 6)}-${rawDate.substring(6, 8)}` : rawDate;
|
|
514
|
+
const docType = buyer?.docType || 99 /* FINAL_CONSUMER */;
|
|
515
|
+
const docNumber = buyer?.docNumber ? buyer.docNumber.replace(/\D/g, "") : "0";
|
|
516
|
+
const qrData = {
|
|
517
|
+
ver: 1,
|
|
518
|
+
fecha: formattedDate,
|
|
519
|
+
cuit: Number(cleanCUIT),
|
|
520
|
+
ptoVta: Number(caeResponse.pointOfSale),
|
|
521
|
+
tipoCmp: Number(caeResponse.invoiceType),
|
|
522
|
+
nroCmp: Number(caeResponse.invoiceNumber),
|
|
523
|
+
importe: Number(parseFloat(total.toFixed(2))),
|
|
524
|
+
moneda: "PES",
|
|
525
|
+
ctz: 1
|
|
526
|
+
};
|
|
527
|
+
if (docType !== 99 /* FINAL_CONSUMER */ || Number(docNumber) > 0) {
|
|
528
|
+
qrData.tipoDocRec = Number(docType);
|
|
529
|
+
qrData.nroDocRec = Number(docNumber);
|
|
530
|
+
}
|
|
531
|
+
qrData.tipoCodAut = "E";
|
|
532
|
+
qrData.codAut = Number(cleanCAE);
|
|
533
|
+
const jsonString = JSON.stringify(qrData);
|
|
534
|
+
const base64 = typeof Buffer !== "undefined" ? Buffer.from(jsonString).toString("base64") : btoa(jsonString);
|
|
535
|
+
return `https://www.afip.gob.ar/fe/qr/?p=${encodeURIComponent(base64)}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/constants/errors.ts
|
|
539
|
+
var ARCA_ERROR_HINTS = {
|
|
540
|
+
// === Autenticación WSAA ===
|
|
541
|
+
501: "El certificado puede haber expirado o la relaci\xF3n CUIT/Servicio no est\xE1 habilitada en el portal de ARCA.",
|
|
542
|
+
502: "El ticket de acceso (TA) es inv\xE1lido o ya expir\xF3. Hac\xE9 wsaa.login() nuevamente.",
|
|
543
|
+
503: "Error interno del servidor de autenticaci\xF3n de ARCA. Reintent\xE1 en unos minutos.",
|
|
544
|
+
1e3: "El CUIT informado no es v\xE1lido o no corresponde al certificado usado.",
|
|
545
|
+
1001: "El servicio solicitado no existe o el certificado no tiene autorizaci\xF3n para usarlo.",
|
|
546
|
+
1003: "El TRA (Ticket de Requerimiento de Acceso) tiene un formato inv\xE1lido.",
|
|
547
|
+
1005: "El TRA ya expir\xF3 antes de ser presentado. Verific\xE1 la hora del sistema.",
|
|
548
|
+
// === WSFE — Puntos de venta y configuración ===
|
|
549
|
+
10048: "El punto de venta no est\xE1 dado de alta en ARCA. Dalo de alta como Webservice en el portal.",
|
|
550
|
+
10049: "El punto de venta no est\xE1 activo o est\xE1 bloqueado.",
|
|
551
|
+
// === WSFE — Comprobantes y montos ===
|
|
552
|
+
10015: "Factura B: El importe supera el l\xEDmite para consumidores finales an\xF3nimos. Identific\xE1 al comprador con CUIT/DNI.",
|
|
553
|
+
10016: "El CUIT informado como receptor no es v\xE1lido o no existe en el Padr\xF3n.",
|
|
554
|
+
600: "No se pudo autorizar el comprobante. Revis\xE1 el campo `observations` en la respuesta para m\xE1s detalle.",
|
|
555
|
+
601: "El comprobante ya fue autorizado anteriormente. No emitas dos veces el mismo n\xFAmero.",
|
|
556
|
+
602: "El n\xFAmero de comprobante es inv\xE1lido o no es el correcto seg\xFAn el \xFAltimo autorizado.",
|
|
557
|
+
// === WSFE — IVA ===
|
|
558
|
+
10043: "La al\xEDcuota de IVA informada no existe o es incorrecta. Us\xE1 3 (0%), 4 (10.5%), 5 (21%) o 6 (27%).",
|
|
559
|
+
10044: "El importe de IVA no cuadra con la base imponible \xD7 al\xEDcuota.",
|
|
560
|
+
// === Padrón ===
|
|
561
|
+
PADRON_ERROR: "El servicio de Padr\xF3n suele ser inestable en homologaci\xF3n. Reintent\xE1 en unos minutos.",
|
|
562
|
+
CUIT_NOT_FOUND: "El CUIT consultado no existe en el Padr\xF3n de ARCA."
|
|
563
|
+
};
|
|
564
|
+
function getArcaHint(code) {
|
|
565
|
+
return ARCA_ERROR_HINTS[code];
|
|
505
566
|
}
|
|
506
567
|
|
|
507
568
|
// src/services/wsfe.ts
|
|
@@ -515,35 +576,24 @@ var WsfeService = class _WsfeService {
|
|
|
515
576
|
if (!config.ticket || !config.ticket.token) {
|
|
516
577
|
throw new ArcaValidationError(
|
|
517
578
|
"Ticket WSAA requerido. Ejecut\xE1 wsaa.login() primero.",
|
|
518
|
-
{ hint: "El ticket se obtiene del servicio
|
|
579
|
+
{ hint: "El ticket se obtiene del servicio WsaaService" }
|
|
519
580
|
);
|
|
520
581
|
}
|
|
521
|
-
if (!config.
|
|
582
|
+
if (!config.pointOfSale || config.pointOfSale < 1 || config.pointOfSale > 9999) {
|
|
522
583
|
throw new ArcaValidationError(
|
|
523
584
|
"Punto de venta inv\xE1lido: debe ser un n\xFAmero entre 1 y 9999",
|
|
524
|
-
{
|
|
585
|
+
{ pointOfSale: config.pointOfSale }
|
|
525
586
|
);
|
|
526
587
|
}
|
|
527
588
|
}
|
|
589
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
590
|
+
// Estado de los servidores
|
|
591
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
528
592
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return this.emitirComprobante({
|
|
534
|
-
tipo: 83 /* TICKET_C */,
|
|
535
|
-
concepto: params.concepto || 1 /* PRODUCTOS */,
|
|
536
|
-
total: params.total,
|
|
537
|
-
fecha: params.fecha,
|
|
538
|
-
comprador: {
|
|
539
|
-
tipoDocumento: 99 /* CONSUMIDOR_FINAL */,
|
|
540
|
-
nroDocumento: "0"
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Verifica el estado de los servidores de ARCA (FEDummy)
|
|
546
|
-
* No requiere autenticación ni instancia configurada.
|
|
593
|
+
* Verifica el estado de los servidores de ARCA (FEDummy).
|
|
594
|
+
* No requiere autenticación. Útil para health checks.
|
|
595
|
+
*
|
|
596
|
+
* @param environment Ambiente a consultar (default: 'homologacion')
|
|
547
597
|
*/
|
|
548
598
|
static async checkStatus(environment = "homologacion") {
|
|
549
599
|
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -574,164 +624,265 @@ var WsfeService = class _WsfeService {
|
|
|
574
624
|
throw new ArcaError("Respuesta FEDummy inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
575
625
|
}
|
|
576
626
|
return {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
627
|
+
appServer: data.AppServer,
|
|
628
|
+
dbServer: data.DbServer,
|
|
629
|
+
authServer: data.AuthServer
|
|
580
630
|
};
|
|
581
631
|
}
|
|
582
632
|
/**
|
|
583
|
-
* Verifica el estado de los servidores de ARCA
|
|
584
|
-
*
|
|
633
|
+
* Verifica el estado de los servidores de ARCA.
|
|
634
|
+
* Versión de instancia — usa el ambiente configurado.
|
|
585
635
|
*/
|
|
586
636
|
async checkStatus() {
|
|
587
637
|
return _WsfeService.checkStatus(this.config.environment);
|
|
588
638
|
}
|
|
639
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
640
|
+
// Emisión de comprobantes
|
|
641
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
589
642
|
/**
|
|
590
|
-
* Emite un Ticket C
|
|
591
|
-
*
|
|
643
|
+
* Emite un Ticket C simple (solo monto total, sin detalle de items).
|
|
644
|
+
* Ideal para registros mínimos, como una app móvil de punto de venta.
|
|
592
645
|
*/
|
|
593
|
-
async
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
646
|
+
async issueSimpleReceipt(params) {
|
|
647
|
+
return this.issueDocument({
|
|
648
|
+
type: 83 /* TICKET_C */,
|
|
649
|
+
concept: params.concept || 1 /* PRODUCTS */,
|
|
650
|
+
total: params.total,
|
|
651
|
+
date: params.date,
|
|
652
|
+
buyer: {
|
|
653
|
+
docType: 99 /* FINAL_CONSUMER */,
|
|
654
|
+
docNumber: "0"
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Emite un Ticket C con detalle de items.
|
|
660
|
+
* Los items se guardan en la respuesta pero no se envían a ARCA.
|
|
661
|
+
*/
|
|
662
|
+
async issueReceipt(params) {
|
|
663
|
+
const total = round(calculateTotal(params.items));
|
|
664
|
+
const cae = await this.issueDocument({
|
|
665
|
+
type: 83 /* TICKET_C */,
|
|
666
|
+
concept: params.concept || 1 /* PRODUCTS */,
|
|
598
667
|
total,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
668
|
+
date: params.date,
|
|
669
|
+
buyer: {
|
|
670
|
+
docType: 99 /* FINAL_CONSUMER */,
|
|
671
|
+
docNumber: "0"
|
|
603
672
|
}
|
|
604
673
|
});
|
|
605
|
-
return {
|
|
606
|
-
...cae,
|
|
607
|
-
items: params.items
|
|
608
|
-
};
|
|
674
|
+
return { ...cae, items: params.items };
|
|
609
675
|
}
|
|
610
676
|
/**
|
|
611
|
-
* Emite una Factura
|
|
612
|
-
* REQUIERE detalle de items con IVA discriminado
|
|
677
|
+
* Emite una Factura C (consumidor final, sin discriminación de IVA).
|
|
613
678
|
*/
|
|
614
|
-
async
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
679
|
+
async issueInvoiceC(params) {
|
|
680
|
+
const total = round(calculateTotal(params.items));
|
|
681
|
+
return this.issueDocument({
|
|
682
|
+
type: 11 /* FACTURA_C */,
|
|
683
|
+
concept: params.concept || 1 /* PRODUCTS */,
|
|
684
|
+
total,
|
|
685
|
+
date: params.date,
|
|
686
|
+
buyer: {
|
|
687
|
+
docType: 99 /* FINAL_CONSUMER */,
|
|
688
|
+
docNumber: "0"
|
|
689
|
+
},
|
|
690
|
+
items: params.items
|
|
626
691
|
});
|
|
627
692
|
}
|
|
628
693
|
/**
|
|
629
|
-
* Emite una Factura
|
|
630
|
-
* REQUIERE
|
|
694
|
+
* Emite una Factura B (con IVA discriminado).
|
|
695
|
+
* REQUIERE `vatRate` en todos los items.
|
|
631
696
|
*/
|
|
632
|
-
async
|
|
633
|
-
this.
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
return this.
|
|
637
|
-
|
|
638
|
-
|
|
697
|
+
async issueInvoiceB(params) {
|
|
698
|
+
this.validateItemsWithVAT(params.items);
|
|
699
|
+
const includesVAT = params.includesVAT || false;
|
|
700
|
+
const vatData = this.calculateVATByRate(params.items, includesVAT);
|
|
701
|
+
return this.issueDocument({
|
|
702
|
+
type: 6 /* FACTURA_B */,
|
|
703
|
+
concept: params.concept || 1 /* PRODUCTS */,
|
|
639
704
|
items: params.items,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
705
|
+
buyer: params.buyer,
|
|
706
|
+
date: params.date,
|
|
707
|
+
vatData,
|
|
708
|
+
includesVAT
|
|
644
709
|
});
|
|
645
710
|
}
|
|
646
711
|
/**
|
|
647
|
-
*
|
|
712
|
+
* Emite una Factura A (Responsable Inscripto a Responsable Inscripto, con IVA discriminado).
|
|
713
|
+
* REQUIERE `vatRate` en todos los items.
|
|
648
714
|
*/
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
715
|
+
async issueInvoiceA(params) {
|
|
716
|
+
this.validateItemsWithVAT(params.items);
|
|
717
|
+
const includesVAT = params.includesVAT || false;
|
|
718
|
+
const vatData = this.calculateVATByRate(params.items, includesVAT);
|
|
719
|
+
return this.issueDocument({
|
|
720
|
+
type: 1 /* FACTURA_A */,
|
|
721
|
+
concept: params.concept || 1 /* PRODUCTS */,
|
|
722
|
+
items: params.items,
|
|
723
|
+
buyer: params.buyer,
|
|
724
|
+
date: params.date,
|
|
725
|
+
vatData,
|
|
726
|
+
includesVAT
|
|
727
|
+
});
|
|
662
728
|
}
|
|
729
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
730
|
+
// Consultas
|
|
731
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
663
732
|
/**
|
|
664
|
-
*
|
|
665
|
-
*
|
|
733
|
+
* Consulta un comprobante ya emitido (FECompConsultar).
|
|
734
|
+
*
|
|
735
|
+
* @param type Tipo de comprobante
|
|
736
|
+
* @param invoiceNumber Número de comprobante
|
|
666
737
|
*/
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
738
|
+
async getInvoice(type, invoiceNumber) {
|
|
739
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
740
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
741
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
742
|
+
<soapenv:Header/>
|
|
743
|
+
<soapenv:Body>
|
|
744
|
+
<ar:FECompConsultar>
|
|
745
|
+
<ar:Auth>
|
|
746
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
747
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
748
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
749
|
+
</ar:Auth>
|
|
750
|
+
<ar:FeCompConsReq>
|
|
751
|
+
<ar:CbteTipo>${type}</ar:CbteTipo>
|
|
752
|
+
<ar:CbteNro>${invoiceNumber}</ar:CbteNro>
|
|
753
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
754
|
+
</ar:FeCompConsReq>
|
|
755
|
+
</ar:FECompConsultar>
|
|
756
|
+
</soapenv:Body>
|
|
757
|
+
</soapenv:Envelope>`;
|
|
758
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
759
|
+
const response = await callArcaApi(endpoint, {
|
|
760
|
+
method: "POST",
|
|
761
|
+
headers: {
|
|
762
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
763
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECompConsultar"
|
|
764
|
+
},
|
|
765
|
+
body: soapRequest,
|
|
766
|
+
timeout: this.config.timeout
|
|
682
767
|
});
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
768
|
+
if (!response.ok) {
|
|
769
|
+
throw new ArcaError(`Error HTTP al consultar comprobante: ${response.status}`, "HTTP_ERROR");
|
|
770
|
+
}
|
|
771
|
+
const responseXml = await response.text();
|
|
772
|
+
const result = parseXml(responseXml);
|
|
773
|
+
const data = result?.Envelope?.Body?.FECompConsultarResponse?.FECompConsultarResult;
|
|
774
|
+
if (!data) {
|
|
775
|
+
throw new ArcaError("Respuesta FECompConsultar inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
776
|
+
}
|
|
777
|
+
if (data.Errors) {
|
|
778
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
779
|
+
const code = error?.Code || "UNKNOWN";
|
|
780
|
+
throw new ArcaError(
|
|
781
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
782
|
+
"ARCA_ERROR",
|
|
783
|
+
data.Errors,
|
|
784
|
+
getArcaHint(code)
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
const det = data.ResultGet;
|
|
788
|
+
return {
|
|
789
|
+
invoiceType: Number(det.CbteTipo),
|
|
790
|
+
pointOfSale: Number(det.PtoVta),
|
|
791
|
+
invoiceNumber: Number(det.CbteDesde),
|
|
792
|
+
date: String(det.CbteFch),
|
|
793
|
+
concept: Number(det.Concepto),
|
|
794
|
+
docType: Number(det.DocTipo),
|
|
795
|
+
docNumber: Number(det.DocNro),
|
|
796
|
+
total: Number(det.ImpTotal),
|
|
797
|
+
net: Number(det.ImpNeto),
|
|
798
|
+
vat: Number(det.ImpIVA),
|
|
799
|
+
cae: String(det.CodAutorizacion),
|
|
800
|
+
caeExpiry: String(det.FchVto),
|
|
801
|
+
result: det.Resultado
|
|
802
|
+
};
|
|
688
803
|
}
|
|
689
804
|
/**
|
|
690
|
-
*
|
|
691
|
-
* Forma simplificada sin especificar comprador
|
|
805
|
+
* Lista los puntos de venta habilitados para el CUIT autenticado (FEParamGetPtosVenta).
|
|
692
806
|
*/
|
|
693
|
-
async
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
807
|
+
async getPointsOfSale() {
|
|
808
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
809
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
810
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
811
|
+
<soapenv:Header/>
|
|
812
|
+
<soapenv:Body>
|
|
813
|
+
<ar:FEParamGetPtosVenta>
|
|
814
|
+
<ar:Auth>
|
|
815
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
816
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
817
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
818
|
+
</ar:Auth>
|
|
819
|
+
</ar:FEParamGetPtosVenta>
|
|
820
|
+
</soapenv:Body>
|
|
821
|
+
</soapenv:Envelope>`;
|
|
822
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
823
|
+
const response = await callArcaApi(endpoint, {
|
|
824
|
+
method: "POST",
|
|
825
|
+
headers: {
|
|
826
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
827
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FEParamGetPtosVenta"
|
|
703
828
|
},
|
|
704
|
-
|
|
829
|
+
body: soapRequest,
|
|
830
|
+
timeout: this.config.timeout
|
|
705
831
|
});
|
|
832
|
+
if (!response.ok) {
|
|
833
|
+
throw new ArcaError(`Error HTTP al consultar puntos de venta: ${response.status}`, "HTTP_ERROR");
|
|
834
|
+
}
|
|
835
|
+
const responseXml = await response.text();
|
|
836
|
+
const result = parseXml(responseXml);
|
|
837
|
+
const data = result?.Envelope?.Body?.FEParamGetPtosVentaResponse?.FEParamGetPtosVentaResult;
|
|
838
|
+
if (!data) {
|
|
839
|
+
throw new ArcaError("Respuesta FEParamGetPtosVenta inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
840
|
+
}
|
|
841
|
+
if (data.Errors) {
|
|
842
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
843
|
+
throw new ArcaError(`Error ARCA: ${error?.Msg || "Error desconocido"}`, "ARCA_ERROR", data.Errors);
|
|
844
|
+
}
|
|
845
|
+
const raw = data.ResultGet?.PtoVenta;
|
|
846
|
+
if (!raw) return [];
|
|
847
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
848
|
+
return list.map((pv) => ({
|
|
849
|
+
number: Number(pv.Nro),
|
|
850
|
+
type: String(pv.EmisionTipo),
|
|
851
|
+
isBlocked: pv.Bloqueado === "S",
|
|
852
|
+
blockedSince: pv.FchBaja ? String(pv.FchBaja) : void 0
|
|
853
|
+
}));
|
|
706
854
|
}
|
|
855
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
856
|
+
// Métodos internos
|
|
857
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
707
858
|
/**
|
|
708
|
-
*
|
|
859
|
+
* Método genérico interno para emitir cualquier tipo de comprobante.
|
|
709
860
|
*/
|
|
710
|
-
async
|
|
711
|
-
const
|
|
861
|
+
async issueDocument(request) {
|
|
862
|
+
const invoiceNumber = await this.getNextInvoiceNumber(request.type);
|
|
712
863
|
let total = request.total || 0;
|
|
713
|
-
let
|
|
714
|
-
let
|
|
864
|
+
let net = total;
|
|
865
|
+
let vat = 0;
|
|
715
866
|
if (request.items && request.items.length > 0) {
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
total =
|
|
867
|
+
const includesVAT = request.includesVAT || false;
|
|
868
|
+
net = round(calculateSubtotal(request.items, includesVAT));
|
|
869
|
+
vat = round(calculateVAT(request.items, includesVAT));
|
|
870
|
+
total = round(calculateTotal(request.items, includesVAT));
|
|
720
871
|
}
|
|
721
872
|
if (total <= 0) {
|
|
722
873
|
throw new ArcaValidationError("El monto total debe ser mayor a 0");
|
|
723
874
|
}
|
|
724
|
-
const soapRequest = this.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
875
|
+
const soapRequest = this.buildCAERequest({
|
|
876
|
+
type: request.type,
|
|
877
|
+
pointOfSale: this.config.pointOfSale,
|
|
878
|
+
invoiceNumber,
|
|
879
|
+
concept: request.concept,
|
|
880
|
+
date: request.date || /* @__PURE__ */ new Date(),
|
|
881
|
+
buyer: request.buyer,
|
|
882
|
+
net,
|
|
883
|
+
vat,
|
|
733
884
|
total,
|
|
734
|
-
|
|
885
|
+
vatData: request.vatData
|
|
735
886
|
});
|
|
736
887
|
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
737
888
|
const response = await callArcaApi(endpoint, {
|
|
@@ -752,17 +903,19 @@ var WsfeService = class _WsfeService {
|
|
|
752
903
|
}
|
|
753
904
|
const responseXml = await response.text();
|
|
754
905
|
const result = await this.parseCAEResponse(responseXml);
|
|
906
|
+
const qrUrl = generateQRUrl(result, this.config.cuit, total, request.buyer);
|
|
755
907
|
return {
|
|
756
908
|
...result,
|
|
757
909
|
items: request.items,
|
|
758
|
-
|
|
910
|
+
vat: request.vatData,
|
|
911
|
+
qrUrl
|
|
759
912
|
};
|
|
760
913
|
}
|
|
761
914
|
/**
|
|
762
|
-
* Obtiene el próximo número de comprobante disponible
|
|
915
|
+
* Obtiene el próximo número de comprobante disponible (FECompUltimoAutorizado + 1)
|
|
763
916
|
*/
|
|
764
|
-
async
|
|
765
|
-
const soapRequest = this.
|
|
917
|
+
async getNextInvoiceNumber(type) {
|
|
918
|
+
const soapRequest = this.buildLastInvoiceRequest(type);
|
|
766
919
|
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
767
920
|
const response = await callArcaApi(endpoint, {
|
|
768
921
|
method: "POST",
|
|
@@ -774,32 +927,102 @@ var WsfeService = class _WsfeService {
|
|
|
774
927
|
timeout: this.config.timeout
|
|
775
928
|
});
|
|
776
929
|
if (!response.ok) {
|
|
777
|
-
throw new ArcaError(`Error HTTP al consultar
|
|
930
|
+
throw new ArcaError(`Error HTTP al consultar \xFAltimo comprobante: ${response.status}`, "HTTP_ERROR");
|
|
778
931
|
}
|
|
779
932
|
const responseXml = await response.text();
|
|
780
933
|
const result = parseXml(responseXml);
|
|
781
934
|
const data = result?.Envelope?.Body?.FECompUltimoAutorizadoResponse?.FECompUltimoAutorizadoResult;
|
|
782
935
|
if (data?.Errors) {
|
|
783
936
|
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
784
|
-
|
|
937
|
+
const code = error?.Code || "UNKNOWN";
|
|
938
|
+
throw new ArcaError(
|
|
939
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
940
|
+
"ARCA_ERROR",
|
|
941
|
+
data.Errors,
|
|
942
|
+
getArcaHint(code)
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
const lastNumber = data?.CbteNro;
|
|
946
|
+
return typeof lastNumber === "number" ? lastNumber + 1 : 1;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Valida que todos los items tengan alícuota IVA definida
|
|
950
|
+
*/
|
|
951
|
+
validateItemsWithVAT(items) {
|
|
952
|
+
const missingVAT = items.filter(
|
|
953
|
+
(item) => item.vatRate === void 0 || item.vatRate === null
|
|
954
|
+
);
|
|
955
|
+
if (missingVAT.length > 0) {
|
|
956
|
+
throw new ArcaValidationError(
|
|
957
|
+
"Esta operaci\xF3n requiere `vatRate` en todos los items",
|
|
958
|
+
{
|
|
959
|
+
itemsMissingVAT: missingVAT.map((i) => i.description),
|
|
960
|
+
hint: "Agreg\xE1 vatRate a cada item (21, 10.5, 27, o 0)"
|
|
961
|
+
}
|
|
962
|
+
);
|
|
785
963
|
}
|
|
786
|
-
const nro = data?.CbteNro;
|
|
787
|
-
return typeof nro === "number" ? nro + 1 : 1;
|
|
788
964
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
965
|
+
/**
|
|
966
|
+
* Calcula el IVA agrupado por alícuota (requerido por ARCA para Factura A/B)
|
|
967
|
+
*/
|
|
968
|
+
calculateVATByRate(items, includesVAT = false) {
|
|
969
|
+
const byRate = /* @__PURE__ */ new Map();
|
|
970
|
+
items.forEach((item) => {
|
|
971
|
+
const rate = item.vatRate || 0;
|
|
972
|
+
let netPrice = item.unitPrice;
|
|
973
|
+
if (includesVAT && rate) {
|
|
974
|
+
netPrice = item.unitPrice / (1 + rate / 100);
|
|
975
|
+
}
|
|
976
|
+
const base = item.quantity * netPrice;
|
|
977
|
+
const amount = base * rate / 100;
|
|
978
|
+
const current = byRate.get(rate) || { base: 0, amount: 0 };
|
|
979
|
+
byRate.set(rate, {
|
|
980
|
+
base: current.base + base,
|
|
981
|
+
amount: current.amount + amount
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
return Array.from(byRate.entries()).map(([rate, values]) => ({
|
|
985
|
+
rate,
|
|
986
|
+
taxBase: round(values.base),
|
|
987
|
+
amount: round(values.amount)
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Mapea alícuota % al código interno de ARCA
|
|
992
|
+
*/
|
|
993
|
+
getVATCode(percentage) {
|
|
994
|
+
const map = {
|
|
995
|
+
0: 3,
|
|
996
|
+
10.5: 4,
|
|
997
|
+
21: 5,
|
|
998
|
+
27: 6
|
|
999
|
+
};
|
|
1000
|
+
const code = map[percentage];
|
|
1001
|
+
if (code === void 0) {
|
|
1002
|
+
throw new ArcaValidationError(
|
|
1003
|
+
`Al\xEDcuota IVA inv\xE1lida: ${percentage}%`,
|
|
1004
|
+
{
|
|
1005
|
+
validRates: [0, 10.5, 21, 27],
|
|
1006
|
+
hint: "Us\xE1 una de las al\xEDcuotas oficiales de Argentina"
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
return code;
|
|
1011
|
+
}
|
|
1012
|
+
buildCAERequest(params) {
|
|
1013
|
+
const dateStr = params.date.toISOString().split("T")[0].replace(/-/g, "");
|
|
1014
|
+
let vatXml = "";
|
|
1015
|
+
if (params.vatData && params.vatData.length > 0) {
|
|
1016
|
+
vatXml = "<ar:Iva>";
|
|
1017
|
+
params.vatData.forEach((entry) => {
|
|
1018
|
+
vatXml += `
|
|
796
1019
|
<ar:AlicIva>
|
|
797
|
-
<ar:Id>${this.
|
|
798
|
-
<ar:BaseImp>${
|
|
799
|
-
<ar:Importe>${
|
|
1020
|
+
<ar:Id>${this.getVATCode(entry.rate)}</ar:Id>
|
|
1021
|
+
<ar:BaseImp>${entry.taxBase.toFixed(2)}</ar:BaseImp>
|
|
1022
|
+
<ar:Importe>${entry.amount.toFixed(2)}</ar:Importe>
|
|
800
1023
|
</ar:AlicIva>`;
|
|
801
1024
|
});
|
|
802
|
-
|
|
1025
|
+
vatXml += "\n </ar:Iva>";
|
|
803
1026
|
}
|
|
804
1027
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
805
1028
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
@@ -815,26 +1038,26 @@ var WsfeService = class _WsfeService {
|
|
|
815
1038
|
<ar:FeCAEReq>
|
|
816
1039
|
<ar:FeCabReq>
|
|
817
1040
|
<ar:CantReg>1</ar:CantReg>
|
|
818
|
-
<ar:PtoVta>${params.
|
|
819
|
-
<ar:CbteTipo>${params.
|
|
1041
|
+
<ar:PtoVta>${params.pointOfSale}</ar:PtoVta>
|
|
1042
|
+
<ar:CbteTipo>${params.type}</ar:CbteTipo>
|
|
820
1043
|
</ar:FeCabReq>
|
|
821
1044
|
<ar:FeDetReq>
|
|
822
1045
|
<ar:FECAEDetRequest>
|
|
823
|
-
<ar:Concepto>${params.
|
|
824
|
-
<ar:DocTipo>${params.
|
|
825
|
-
<ar:DocNro>${params.
|
|
826
|
-
<ar:CbteDesde>${params.
|
|
827
|
-
<ar:CbteHasta>${params.
|
|
828
|
-
<ar:CbteFch>${
|
|
1046
|
+
<ar:Concepto>${params.concept}</ar:Concepto>
|
|
1047
|
+
<ar:DocTipo>${params.buyer?.docType || 99}</ar:DocTipo>
|
|
1048
|
+
<ar:DocNro>${params.buyer?.docNumber || 0}</ar:DocNro>
|
|
1049
|
+
<ar:CbteDesde>${params.invoiceNumber}</ar:CbteDesde>
|
|
1050
|
+
<ar:CbteHasta>${params.invoiceNumber}</ar:CbteHasta>
|
|
1051
|
+
<ar:CbteFch>${dateStr}</ar:CbteFch>
|
|
829
1052
|
<ar:ImpTotal>${params.total.toFixed(2)}</ar:ImpTotal>
|
|
830
1053
|
<ar:ImpTotConc>0.00</ar:ImpTotConc>
|
|
831
|
-
<ar:ImpNeto>${params.
|
|
1054
|
+
<ar:ImpNeto>${params.net.toFixed(2)}</ar:ImpNeto>
|
|
832
1055
|
<ar:ImpOpEx>0.00</ar:ImpOpEx>
|
|
833
|
-
<ar:ImpIVA>${params.
|
|
1056
|
+
<ar:ImpIVA>${params.vat.toFixed(2)}</ar:ImpIVA>
|
|
834
1057
|
<ar:ImpTrib>0.00</ar:ImpTrib>
|
|
835
1058
|
<ar:MonId>PES</ar:MonId>
|
|
836
1059
|
<ar:MonCotiz>1</ar:MonCotiz>
|
|
837
|
-
${
|
|
1060
|
+
${vatXml}
|
|
838
1061
|
</ar:FECAEDetRequest>
|
|
839
1062
|
</ar:FeDetReq>
|
|
840
1063
|
</ar:FeCAEReq>
|
|
@@ -842,29 +1065,7 @@ var WsfeService = class _WsfeService {
|
|
|
842
1065
|
</soapenv:Body>
|
|
843
1066
|
</soapenv:Envelope>`;
|
|
844
1067
|
}
|
|
845
|
-
|
|
846
|
-
* Mapea alícuota % a código ARCA
|
|
847
|
-
*/
|
|
848
|
-
getCodigoAlicuota(porcentaje) {
|
|
849
|
-
const mapa = {
|
|
850
|
-
0: 3,
|
|
851
|
-
10.5: 4,
|
|
852
|
-
21: 5,
|
|
853
|
-
27: 6
|
|
854
|
-
};
|
|
855
|
-
const codigo = mapa[porcentaje];
|
|
856
|
-
if (!codigo) {
|
|
857
|
-
throw new ArcaValidationError(
|
|
858
|
-
`Al\xEDcuota IVA inv\xE1lida: ${porcentaje}%`,
|
|
859
|
-
{
|
|
860
|
-
alicuotasValidas: [0, 10.5, 21, 27],
|
|
861
|
-
hint: "Usar una de las al\xEDcuotas oficiales de Argentina"
|
|
862
|
-
}
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
return codigo;
|
|
866
|
-
}
|
|
867
|
-
buildProximoNumeroRequest(tipo) {
|
|
1068
|
+
buildLastInvoiceRequest(type) {
|
|
868
1069
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
869
1070
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
870
1071
|
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
@@ -876,8 +1077,8 @@ var WsfeService = class _WsfeService {
|
|
|
876
1077
|
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
877
1078
|
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
878
1079
|
</ar:Auth>
|
|
879
|
-
<ar:PtoVta>${this.config.
|
|
880
|
-
<ar:CbteTipo>${
|
|
1080
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
1081
|
+
<ar:CbteTipo>${type}</ar:CbteTipo>
|
|
881
1082
|
</ar:FECompUltimoAutorizado>
|
|
882
1083
|
</soapenv:Body>
|
|
883
1084
|
</soapenv:Envelope>`;
|
|
@@ -890,27 +1091,33 @@ var WsfeService = class _WsfeService {
|
|
|
890
1091
|
}
|
|
891
1092
|
if (data.Errors) {
|
|
892
1093
|
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
893
|
-
|
|
1094
|
+
const code = error?.Code || "UNKNOWN";
|
|
1095
|
+
throw new ArcaError(
|
|
1096
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1097
|
+
"ARCA_ERROR",
|
|
1098
|
+
data.Errors,
|
|
1099
|
+
getArcaHint(code)
|
|
1100
|
+
);
|
|
894
1101
|
}
|
|
895
1102
|
const cab = data.FeCabResp;
|
|
896
1103
|
const det = Array.isArray(data.FeDetResp.FECAEDetResponse) ? data.FeDetResp.FECAEDetResponse[0] : data.FeDetResp.FECAEDetResponse;
|
|
897
1104
|
if (!det) {
|
|
898
1105
|
throw new ArcaError("Respuesta WSFE incompleta: falta detalle del comprobante", "PARSE_ERROR");
|
|
899
1106
|
}
|
|
900
|
-
const
|
|
1107
|
+
const observations = [];
|
|
901
1108
|
if (det.Observaciones) {
|
|
902
1109
|
const obsArray = Array.isArray(det.Observaciones.Obs) ? det.Observaciones.Obs : [det.Observaciones.Obs];
|
|
903
|
-
obsArray.forEach((o) =>
|
|
1110
|
+
obsArray.forEach((o) => observations.push(o.Msg));
|
|
904
1111
|
}
|
|
905
1112
|
return {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1113
|
+
invoiceType: cab.CbteTipo,
|
|
1114
|
+
pointOfSale: cab.PtoVta,
|
|
1115
|
+
invoiceNumber: Number(det.CbteDesde),
|
|
1116
|
+
date: det.CbteFch,
|
|
910
1117
|
cae: String(det.CAE),
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1118
|
+
caeExpiry: String(det.CAEFchVto),
|
|
1119
|
+
result: det.Resultado,
|
|
1120
|
+
observations: observations.length > 0 ? observations : void 0
|
|
914
1121
|
};
|
|
915
1122
|
}
|
|
916
1123
|
};
|
|
@@ -932,12 +1139,12 @@ var PadronService = class {
|
|
|
932
1139
|
});
|
|
933
1140
|
}
|
|
934
1141
|
/**
|
|
935
|
-
* Consulta los datos de
|
|
936
|
-
*
|
|
937
|
-
* @param
|
|
938
|
-
* @returns Datos
|
|
1142
|
+
* Consulta los datos de un contribuyente por CUIT
|
|
1143
|
+
*
|
|
1144
|
+
* @param taxId CUIT a consultar (11 dígitos sin guiones)
|
|
1145
|
+
* @returns Datos del contribuyente o mensaje de error
|
|
939
1146
|
*/
|
|
940
|
-
async
|
|
1147
|
+
async getTaxpayer(taxId) {
|
|
941
1148
|
const ticket = await this.wsaa.login();
|
|
942
1149
|
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
943
1150
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
@@ -948,7 +1155,7 @@ var PadronService = class {
|
|
|
948
1155
|
<token>${ticket.token}</token>
|
|
949
1156
|
<sign>${ticket.sign}</sign>
|
|
950
1157
|
<cuitRepresentada>${this.config.cuit}</cuitRepresentada>
|
|
951
|
-
<idPersona>${
|
|
1158
|
+
<idPersona>${taxId}</idPersona>
|
|
952
1159
|
</a13:getPersona>
|
|
953
1160
|
</soapenv:Body>
|
|
954
1161
|
</soapenv:Envelope>`;
|
|
@@ -958,14 +1165,13 @@ var PadronService = class {
|
|
|
958
1165
|
headers: {
|
|
959
1166
|
"Content-Type": "text/xml; charset=utf-8",
|
|
960
1167
|
"SOAPAction": ""
|
|
961
|
-
// A13 no suele requerir SOAPAction específica en el header
|
|
962
1168
|
},
|
|
963
1169
|
body: soapRequest,
|
|
964
1170
|
timeout: this.config.timeout || 15e3
|
|
965
1171
|
});
|
|
966
1172
|
if (!response.ok) {
|
|
967
1173
|
throw new ArcaNetworkError(
|
|
968
|
-
`Error HTTP al comunicarse con
|
|
1174
|
+
`Error HTTP al comunicarse con Padr\xF3n A13: ${response.status}`,
|
|
969
1175
|
{ status: response.status }
|
|
970
1176
|
);
|
|
971
1177
|
}
|
|
@@ -973,7 +1179,7 @@ var PadronService = class {
|
|
|
973
1179
|
return this.parseResponse(xml);
|
|
974
1180
|
}
|
|
975
1181
|
/**
|
|
976
|
-
*
|
|
1182
|
+
* Parsea la respuesta XML de getPersona
|
|
977
1183
|
*/
|
|
978
1184
|
parseResponse(xml) {
|
|
979
1185
|
const parser = new XMLParser2({
|
|
@@ -983,13 +1189,13 @@ var PadronService = class {
|
|
|
983
1189
|
const result = parser.parse(xml);
|
|
984
1190
|
const body = result.Envelope?.Body;
|
|
985
1191
|
if (!body) {
|
|
986
|
-
throw new ArcaError("Respuesta
|
|
1192
|
+
throw new ArcaError("Respuesta del Padr\xF3n inv\xE1lida: Body no encontrado", "PADRON_ERROR");
|
|
987
1193
|
}
|
|
988
1194
|
const response = body.getPersonaResponse?.personaReturn;
|
|
989
1195
|
if (!response) {
|
|
990
1196
|
const fault = body.Fault;
|
|
991
1197
|
if (fault) {
|
|
992
|
-
return { error: fault.faultstring || "Error desconocido en
|
|
1198
|
+
return { error: fault.faultstring || "Error desconocido en ARCA" };
|
|
993
1199
|
}
|
|
994
1200
|
return { error: "No se encontraron datos para el CUIT informado" };
|
|
995
1201
|
}
|
|
@@ -1000,83 +1206,79 @@ var PadronService = class {
|
|
|
1000
1206
|
if (!p) {
|
|
1001
1207
|
return { error: "CUIT no encontrado" };
|
|
1002
1208
|
}
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1209
|
+
const taxpayer = {
|
|
1210
|
+
taxId: Number(p.idPersona),
|
|
1211
|
+
personType: p.tipoPersona,
|
|
1212
|
+
firstName: p.nombre,
|
|
1213
|
+
lastName: p.apellido,
|
|
1214
|
+
companyName: p.razonSocial,
|
|
1215
|
+
status: p.estadoClave,
|
|
1216
|
+
addresses: this.mapAddresses(p.domicilio),
|
|
1217
|
+
activities: this.mapActivities(p.actividad),
|
|
1218
|
+
taxes: this.mapTaxRecords(p.impuesto),
|
|
1219
|
+
mainActivity: p.descripcionActividadPrincipal,
|
|
1220
|
+
isVATRegistered: this.hasTaxId(p, 30),
|
|
1013
1221
|
// 30 = IVA
|
|
1014
|
-
|
|
1222
|
+
isMonotax: this.hasTaxId(p, 20),
|
|
1015
1223
|
// 20 = Monotributo
|
|
1016
|
-
|
|
1017
|
-
// 32 = Exento
|
|
1224
|
+
isVATExempt: this.hasTaxId(p, 32)
|
|
1225
|
+
// 32 = IVA Exento
|
|
1018
1226
|
};
|
|
1019
|
-
return {
|
|
1227
|
+
return { taxpayer };
|
|
1020
1228
|
}
|
|
1021
|
-
|
|
1022
|
-
if (!
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
tipoDomicilio: item.tipoDomicilio
|
|
1229
|
+
mapAddresses(raw) {
|
|
1230
|
+
if (!raw) return [];
|
|
1231
|
+
return this.toArray(raw).map((item) => ({
|
|
1232
|
+
street: item.direccion,
|
|
1233
|
+
city: item.localidad,
|
|
1234
|
+
postalCode: item.codPostal,
|
|
1235
|
+
provinceId: Number(item.idProvincia),
|
|
1236
|
+
province: item.descripcionProvincia,
|
|
1237
|
+
type: item.tipoDomicilio
|
|
1031
1238
|
}));
|
|
1032
1239
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1240
|
+
mapActivities(raw) {
|
|
1241
|
+
if (!raw) return [];
|
|
1242
|
+
return this.toArray(raw).map((item) => ({
|
|
1243
|
+
id: Number(item.idActividad),
|
|
1244
|
+
description: item.descripcion,
|
|
1245
|
+
order: Number(item.orden),
|
|
1246
|
+
period: Number(item.periodo)
|
|
1247
|
+
}));
|
|
1038
1248
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
const fechaFormat = fDate.length === 8 ? `${fDate.substring(0, 4)}-${fDate.substring(4, 6)}-${fDate.substring(6, 8)}` : fDate;
|
|
1047
|
-
const docTipo = comprador?.tipoDocumento || 99 /* CONSUMIDOR_FINAL */;
|
|
1048
|
-
const docNro = comprador?.nroDocumento ? comprador.nroDocumento.replace(/\D/g, "") : "0";
|
|
1049
|
-
const qrObj = {
|
|
1050
|
-
ver: 1,
|
|
1051
|
-
fecha: fechaFormat,
|
|
1052
|
-
cuit: Number(cleanCuit),
|
|
1053
|
-
ptoVta: Number(caeResponse.puntoVenta),
|
|
1054
|
-
tipoCmp: Number(caeResponse.tipoComprobante),
|
|
1055
|
-
nroCmp: Number(caeResponse.nroComprobante),
|
|
1056
|
-
importe: Number(parseFloat(total.toFixed(2))),
|
|
1057
|
-
moneda: "PES",
|
|
1058
|
-
ctz: 1
|
|
1059
|
-
};
|
|
1060
|
-
if (docTipo !== 99 /* CONSUMIDOR_FINAL */ || Number(docNro) > 0) {
|
|
1061
|
-
qrObj.tipoDocRec = Number(docTipo);
|
|
1062
|
-
qrObj.nroDocRec = Number(docNro);
|
|
1249
|
+
mapTaxRecords(raw) {
|
|
1250
|
+
if (!raw) return [];
|
|
1251
|
+
return this.toArray(raw).map((item) => ({
|
|
1252
|
+
id: Number(item.idImpuesto),
|
|
1253
|
+
description: item.descripcion,
|
|
1254
|
+
period: Number(item.periodo)
|
|
1255
|
+
}));
|
|
1063
1256
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1257
|
+
hasTaxId(p, id) {
|
|
1258
|
+
const taxes = p.impuesto;
|
|
1259
|
+
if (!taxes) return false;
|
|
1260
|
+
return this.toArray(taxes).some((i) => Number(i.idImpuesto) === id);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Normaliza un valor que puede ser un objeto único o un array (comportamiento de fast-xml-parser)
|
|
1264
|
+
*/
|
|
1265
|
+
toArray(data) {
|
|
1266
|
+
if (data === void 0 || data === null) return [];
|
|
1267
|
+
if (Array.isArray(data)) return data;
|
|
1268
|
+
return [data];
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1070
1271
|
export {
|
|
1071
1272
|
ArcaAuthError,
|
|
1072
1273
|
ArcaError,
|
|
1274
|
+
ArcaNetworkError,
|
|
1073
1275
|
ArcaValidationError,
|
|
1074
|
-
|
|
1276
|
+
BillingConcept,
|
|
1277
|
+
InvoiceType,
|
|
1075
1278
|
PadronService,
|
|
1076
|
-
|
|
1077
|
-
TipoDocumento,
|
|
1279
|
+
TaxIdType,
|
|
1078
1280
|
WsaaService,
|
|
1079
1281
|
WsfeService,
|
|
1080
|
-
|
|
1282
|
+
generateQRUrl
|
|
1081
1283
|
};
|
|
1082
1284
|
//# sourceMappingURL=index.js.map
|