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