arca-sdk 0.5.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 -48
- package/dist/index.cjs +489 -352
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +271 -179
- package/dist/index.d.ts +271 -179
- package/dist/index.js +484 -348
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -442,112 +442,124 @@ var WsaaService = class {
|
|
|
442
442
|
};
|
|
443
443
|
|
|
444
444
|
// src/types/wsfe.ts
|
|
445
|
-
var
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return
|
|
453
|
-
})(
|
|
454
|
-
var
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
return
|
|
459
|
-
})(
|
|
460
|
-
var
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return
|
|
473
|
-
})(
|
|
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 || {});
|
|
474
474
|
|
|
475
475
|
// src/utils/calculations.ts
|
|
476
|
-
function
|
|
476
|
+
function calculateSubtotal(items, includesVAT = false) {
|
|
477
477
|
return items.reduce((sum, item) => {
|
|
478
|
-
let
|
|
479
|
-
if (
|
|
480
|
-
|
|
478
|
+
let netPrice = item.unitPrice;
|
|
479
|
+
if (includesVAT && item.vatRate) {
|
|
480
|
+
netPrice = item.unitPrice / (1 + item.vatRate / 100);
|
|
481
481
|
}
|
|
482
|
-
return sum + item.
|
|
482
|
+
return sum + item.quantity * netPrice;
|
|
483
483
|
}, 0);
|
|
484
484
|
}
|
|
485
|
-
function
|
|
485
|
+
function calculateVAT(items, includesVAT = false) {
|
|
486
486
|
return items.reduce((sum, item) => {
|
|
487
|
-
const
|
|
488
|
-
let
|
|
489
|
-
if (
|
|
490
|
-
|
|
487
|
+
const rate = item.vatRate || 0;
|
|
488
|
+
let netPrice = item.unitPrice;
|
|
489
|
+
if (includesVAT && rate) {
|
|
490
|
+
netPrice = item.unitPrice / (1 + rate / 100);
|
|
491
491
|
}
|
|
492
|
-
const
|
|
493
|
-
return sum +
|
|
492
|
+
const netSubtotal = item.quantity * netPrice;
|
|
493
|
+
return sum + netSubtotal * rate / 100;
|
|
494
494
|
}, 0);
|
|
495
495
|
}
|
|
496
|
-
function
|
|
497
|
-
if (
|
|
498
|
-
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);
|
|
499
499
|
}
|
|
500
|
-
const subtotal =
|
|
501
|
-
const
|
|
502
|
-
return subtotal +
|
|
500
|
+
const subtotal = calculateSubtotal(items, false);
|
|
501
|
+
const vat = calculateVAT(items, false);
|
|
502
|
+
return subtotal + vat;
|
|
503
503
|
}
|
|
504
|
-
function
|
|
505
|
-
return Math.round(
|
|
504
|
+
function round(value) {
|
|
505
|
+
return Math.round(value * 100) / 100;
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
// src/utils/qr.ts
|
|
509
|
-
function
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
const
|
|
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
517
|
ver: 1,
|
|
518
|
-
fecha:
|
|
519
|
-
cuit: Number(
|
|
520
|
-
ptoVta: Number(caeResponse.
|
|
521
|
-
tipoCmp: Number(caeResponse.
|
|
522
|
-
nroCmp: Number(caeResponse.
|
|
518
|
+
fecha: formattedDate,
|
|
519
|
+
cuit: Number(cleanCUIT),
|
|
520
|
+
ptoVta: Number(caeResponse.pointOfSale),
|
|
521
|
+
tipoCmp: Number(caeResponse.invoiceType),
|
|
522
|
+
nroCmp: Number(caeResponse.invoiceNumber),
|
|
523
523
|
importe: Number(parseFloat(total.toFixed(2))),
|
|
524
524
|
moneda: "PES",
|
|
525
525
|
ctz: 1
|
|
526
526
|
};
|
|
527
|
-
if (
|
|
528
|
-
|
|
529
|
-
|
|
527
|
+
if (docType !== 99 /* FINAL_CONSUMER */ || Number(docNumber) > 0) {
|
|
528
|
+
qrData.tipoDocRec = Number(docType);
|
|
529
|
+
qrData.nroDocRec = Number(docNumber);
|
|
530
530
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const jsonString = JSON.stringify(
|
|
534
|
-
|
|
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
535
|
return `https://www.afip.gob.ar/fe/qr/?p=${encodeURIComponent(base64)}`;
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
// src/constants/errors.ts
|
|
539
539
|
var ARCA_ERROR_HINTS = {
|
|
540
|
-
//
|
|
541
|
-
501: "
|
|
542
|
-
502: "
|
|
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.",
|
|
543
544
|
1e3: "El CUIT informado no es v\xE1lido o no corresponde al certificado usado.",
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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."
|
|
551
563
|
};
|
|
552
564
|
function getArcaHint(code) {
|
|
553
565
|
return ARCA_ERROR_HINTS[code];
|
|
@@ -564,35 +576,24 @@ var WsfeService = class _WsfeService {
|
|
|
564
576
|
if (!config.ticket || !config.ticket.token) {
|
|
565
577
|
throw new ArcaValidationError(
|
|
566
578
|
"Ticket WSAA requerido. Ejecut\xE1 wsaa.login() primero.",
|
|
567
|
-
{ hint: "El ticket se obtiene del servicio
|
|
579
|
+
{ hint: "El ticket se obtiene del servicio WsaaService" }
|
|
568
580
|
);
|
|
569
581
|
}
|
|
570
|
-
if (!config.
|
|
582
|
+
if (!config.pointOfSale || config.pointOfSale < 1 || config.pointOfSale > 9999) {
|
|
571
583
|
throw new ArcaValidationError(
|
|
572
584
|
"Punto de venta inv\xE1lido: debe ser un n\xFAmero entre 1 y 9999",
|
|
573
|
-
{
|
|
585
|
+
{ pointOfSale: config.pointOfSale }
|
|
574
586
|
);
|
|
575
587
|
}
|
|
576
588
|
}
|
|
589
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
590
|
+
// Estado de los servidores
|
|
591
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
577
592
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
return this.emitirComprobante({
|
|
583
|
-
tipo: 83 /* TICKET_C */,
|
|
584
|
-
concepto: params.concepto || 1 /* PRODUCTOS */,
|
|
585
|
-
total: params.total,
|
|
586
|
-
fecha: params.fecha,
|
|
587
|
-
comprador: {
|
|
588
|
-
tipoDocumento: 99 /* CONSUMIDOR_FINAL */,
|
|
589
|
-
nroDocumento: "0"
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Verifica el estado de los servidores de ARCA (FEDummy)
|
|
595
|
-
* 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')
|
|
596
597
|
*/
|
|
597
598
|
static async checkStatus(environment = "homologacion") {
|
|
598
599
|
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -623,164 +624,265 @@ var WsfeService = class _WsfeService {
|
|
|
623
624
|
throw new ArcaError("Respuesta FEDummy inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
624
625
|
}
|
|
625
626
|
return {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
627
|
+
appServer: data.AppServer,
|
|
628
|
+
dbServer: data.DbServer,
|
|
629
|
+
authServer: data.AuthServer
|
|
629
630
|
};
|
|
630
631
|
}
|
|
631
632
|
/**
|
|
632
|
-
* Verifica el estado de los servidores de ARCA
|
|
633
|
-
*
|
|
633
|
+
* Verifica el estado de los servidores de ARCA.
|
|
634
|
+
* Versión de instancia — usa el ambiente configurado.
|
|
634
635
|
*/
|
|
635
636
|
async checkStatus() {
|
|
636
637
|
return _WsfeService.checkStatus(this.config.environment);
|
|
637
638
|
}
|
|
639
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
640
|
+
// Emisión de comprobantes
|
|
641
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
638
642
|
/**
|
|
639
|
-
* Emite un Ticket C
|
|
640
|
-
*
|
|
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.
|
|
641
645
|
*/
|
|
642
|
-
async
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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 */,
|
|
647
667
|
total,
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
668
|
+
date: params.date,
|
|
669
|
+
buyer: {
|
|
670
|
+
docType: 99 /* FINAL_CONSUMER */,
|
|
671
|
+
docNumber: "0"
|
|
652
672
|
}
|
|
653
673
|
});
|
|
654
|
-
return {
|
|
655
|
-
...cae,
|
|
656
|
-
items: params.items
|
|
657
|
-
};
|
|
674
|
+
return { ...cae, items: params.items };
|
|
658
675
|
}
|
|
659
676
|
/**
|
|
660
|
-
* Emite una Factura
|
|
661
|
-
* REQUIERE detalle de items con IVA discriminado
|
|
677
|
+
* Emite una Factura C (consumidor final, sin discriminación de IVA).
|
|
662
678
|
*/
|
|
663
|
-
async
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
|
675
691
|
});
|
|
676
692
|
}
|
|
677
693
|
/**
|
|
678
|
-
* Emite una Factura
|
|
679
|
-
* REQUIERE
|
|
694
|
+
* Emite una Factura B (con IVA discriminado).
|
|
695
|
+
* REQUIERE `vatRate` en todos los items.
|
|
680
696
|
*/
|
|
681
|
-
async
|
|
682
|
-
this.
|
|
683
|
-
const
|
|
684
|
-
const
|
|
685
|
-
return this.
|
|
686
|
-
|
|
687
|
-
|
|
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 */,
|
|
688
704
|
items: params.items,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
705
|
+
buyer: params.buyer,
|
|
706
|
+
date: params.date,
|
|
707
|
+
vatData,
|
|
708
|
+
includesVAT
|
|
693
709
|
});
|
|
694
710
|
}
|
|
695
711
|
/**
|
|
696
|
-
*
|
|
712
|
+
* Emite una Factura A (Responsable Inscripto a Responsable Inscripto, con IVA discriminado).
|
|
713
|
+
* REQUIERE `vatRate` en todos los items.
|
|
697
714
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
}
|
|
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
|
+
});
|
|
711
728
|
}
|
|
729
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
730
|
+
// Consultas
|
|
731
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
712
732
|
/**
|
|
713
|
-
*
|
|
714
|
-
*
|
|
733
|
+
* Consulta un comprobante ya emitido (FECompConsultar).
|
|
734
|
+
*
|
|
735
|
+
* @param type Tipo de comprobante
|
|
736
|
+
* @param invoiceNumber Número de comprobante
|
|
715
737
|
*/
|
|
716
|
-
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
|
731
767
|
});
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
+
};
|
|
737
803
|
}
|
|
738
804
|
/**
|
|
739
|
-
*
|
|
740
|
-
* Forma simplificada sin especificar comprador
|
|
805
|
+
* Lista los puntos de venta habilitados para el CUIT autenticado (FEParamGetPtosVenta).
|
|
741
806
|
*/
|
|
742
|
-
async
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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"
|
|
752
828
|
},
|
|
753
|
-
|
|
829
|
+
body: soapRequest,
|
|
830
|
+
timeout: this.config.timeout
|
|
754
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
|
+
}));
|
|
755
854
|
}
|
|
855
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
856
|
+
// Métodos internos
|
|
857
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
756
858
|
/**
|
|
757
|
-
*
|
|
859
|
+
* Método genérico interno para emitir cualquier tipo de comprobante.
|
|
758
860
|
*/
|
|
759
|
-
async
|
|
760
|
-
const
|
|
861
|
+
async issueDocument(request) {
|
|
862
|
+
const invoiceNumber = await this.getNextInvoiceNumber(request.type);
|
|
761
863
|
let total = request.total || 0;
|
|
762
|
-
let
|
|
763
|
-
let
|
|
864
|
+
let net = total;
|
|
865
|
+
let vat = 0;
|
|
764
866
|
if (request.items && request.items.length > 0) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
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));
|
|
769
871
|
}
|
|
770
872
|
if (total <= 0) {
|
|
771
873
|
throw new ArcaValidationError("El monto total debe ser mayor a 0");
|
|
772
874
|
}
|
|
773
|
-
const soapRequest = this.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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,
|
|
782
884
|
total,
|
|
783
|
-
|
|
885
|
+
vatData: request.vatData
|
|
784
886
|
});
|
|
785
887
|
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
786
888
|
const response = await callArcaApi(endpoint, {
|
|
@@ -801,24 +903,19 @@ var WsfeService = class _WsfeService {
|
|
|
801
903
|
}
|
|
802
904
|
const responseXml = await response.text();
|
|
803
905
|
const result = await this.parseCAEResponse(responseXml);
|
|
804
|
-
const
|
|
805
|
-
result,
|
|
806
|
-
this.config.cuit,
|
|
807
|
-
total,
|
|
808
|
-
request.comprador
|
|
809
|
-
);
|
|
906
|
+
const qrUrl = generateQRUrl(result, this.config.cuit, total, request.buyer);
|
|
810
907
|
return {
|
|
811
908
|
...result,
|
|
812
909
|
items: request.items,
|
|
813
|
-
|
|
814
|
-
|
|
910
|
+
vat: request.vatData,
|
|
911
|
+
qrUrl
|
|
815
912
|
};
|
|
816
913
|
}
|
|
817
914
|
/**
|
|
818
|
-
* Obtiene el próximo número de comprobante disponible
|
|
915
|
+
* Obtiene el próximo número de comprobante disponible (FECompUltimoAutorizado + 1)
|
|
819
916
|
*/
|
|
820
|
-
async
|
|
821
|
-
const soapRequest = this.
|
|
917
|
+
async getNextInvoiceNumber(type) {
|
|
918
|
+
const soapRequest = this.buildLastInvoiceRequest(type);
|
|
822
919
|
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
823
920
|
const response = await callArcaApi(endpoint, {
|
|
824
921
|
method: "POST",
|
|
@@ -830,7 +927,7 @@ var WsfeService = class _WsfeService {
|
|
|
830
927
|
timeout: this.config.timeout
|
|
831
928
|
});
|
|
832
929
|
if (!response.ok) {
|
|
833
|
-
throw new ArcaError(`Error HTTP al consultar
|
|
930
|
+
throw new ArcaError(`Error HTTP al consultar \xFAltimo comprobante: ${response.status}`, "HTTP_ERROR");
|
|
834
931
|
}
|
|
835
932
|
const responseXml = await response.text();
|
|
836
933
|
const result = parseXml(responseXml);
|
|
@@ -845,23 +942,87 @@ var WsfeService = class _WsfeService {
|
|
|
845
942
|
getArcaHint(code)
|
|
846
943
|
);
|
|
847
944
|
}
|
|
848
|
-
const
|
|
849
|
-
return typeof
|
|
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
|
+
);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
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
|
+
}));
|
|
850
989
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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 += `
|
|
858
1019
|
<ar:AlicIva>
|
|
859
|
-
<ar:Id>${this.
|
|
860
|
-
<ar:BaseImp>${
|
|
861
|
-
<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>
|
|
862
1023
|
</ar:AlicIva>`;
|
|
863
1024
|
});
|
|
864
|
-
|
|
1025
|
+
vatXml += "\n </ar:Iva>";
|
|
865
1026
|
}
|
|
866
1027
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
867
1028
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
@@ -877,26 +1038,26 @@ var WsfeService = class _WsfeService {
|
|
|
877
1038
|
<ar:FeCAEReq>
|
|
878
1039
|
<ar:FeCabReq>
|
|
879
1040
|
<ar:CantReg>1</ar:CantReg>
|
|
880
|
-
<ar:PtoVta>${params.
|
|
881
|
-
<ar:CbteTipo>${params.
|
|
1041
|
+
<ar:PtoVta>${params.pointOfSale}</ar:PtoVta>
|
|
1042
|
+
<ar:CbteTipo>${params.type}</ar:CbteTipo>
|
|
882
1043
|
</ar:FeCabReq>
|
|
883
1044
|
<ar:FeDetReq>
|
|
884
1045
|
<ar:FECAEDetRequest>
|
|
885
|
-
<ar:Concepto>${params.
|
|
886
|
-
<ar:DocTipo>${params.
|
|
887
|
-
<ar:DocNro>${params.
|
|
888
|
-
<ar:CbteDesde>${params.
|
|
889
|
-
<ar:CbteHasta>${params.
|
|
890
|
-
<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>
|
|
891
1052
|
<ar:ImpTotal>${params.total.toFixed(2)}</ar:ImpTotal>
|
|
892
1053
|
<ar:ImpTotConc>0.00</ar:ImpTotConc>
|
|
893
|
-
<ar:ImpNeto>${params.
|
|
1054
|
+
<ar:ImpNeto>${params.net.toFixed(2)}</ar:ImpNeto>
|
|
894
1055
|
<ar:ImpOpEx>0.00</ar:ImpOpEx>
|
|
895
|
-
<ar:ImpIVA>${params.
|
|
1056
|
+
<ar:ImpIVA>${params.vat.toFixed(2)}</ar:ImpIVA>
|
|
896
1057
|
<ar:ImpTrib>0.00</ar:ImpTrib>
|
|
897
1058
|
<ar:MonId>PES</ar:MonId>
|
|
898
1059
|
<ar:MonCotiz>1</ar:MonCotiz>
|
|
899
|
-
${
|
|
1060
|
+
${vatXml}
|
|
900
1061
|
</ar:FECAEDetRequest>
|
|
901
1062
|
</ar:FeDetReq>
|
|
902
1063
|
</ar:FeCAEReq>
|
|
@@ -904,29 +1065,7 @@ var WsfeService = class _WsfeService {
|
|
|
904
1065
|
</soapenv:Body>
|
|
905
1066
|
</soapenv:Envelope>`;
|
|
906
1067
|
}
|
|
907
|
-
|
|
908
|
-
* Mapea alícuota % a código ARCA
|
|
909
|
-
*/
|
|
910
|
-
getCodigoAlicuota(porcentaje) {
|
|
911
|
-
const mapa = {
|
|
912
|
-
0: 3,
|
|
913
|
-
10.5: 4,
|
|
914
|
-
21: 5,
|
|
915
|
-
27: 6
|
|
916
|
-
};
|
|
917
|
-
const codigo = mapa[porcentaje];
|
|
918
|
-
if (!codigo) {
|
|
919
|
-
throw new ArcaValidationError(
|
|
920
|
-
`Al\xEDcuota IVA inv\xE1lida: ${porcentaje}%`,
|
|
921
|
-
{
|
|
922
|
-
alicuotasValidas: [0, 10.5, 21, 27],
|
|
923
|
-
hint: "Usar una de las al\xEDcuotas oficiales de Argentina"
|
|
924
|
-
}
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
return codigo;
|
|
928
|
-
}
|
|
929
|
-
buildProximoNumeroRequest(tipo) {
|
|
1068
|
+
buildLastInvoiceRequest(type) {
|
|
930
1069
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
931
1070
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
932
1071
|
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
@@ -938,8 +1077,8 @@ var WsfeService = class _WsfeService {
|
|
|
938
1077
|
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
939
1078
|
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
940
1079
|
</ar:Auth>
|
|
941
|
-
<ar:PtoVta>${this.config.
|
|
942
|
-
<ar:CbteTipo>${
|
|
1080
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
1081
|
+
<ar:CbteTipo>${type}</ar:CbteTipo>
|
|
943
1082
|
</ar:FECompUltimoAutorizado>
|
|
944
1083
|
</soapenv:Body>
|
|
945
1084
|
</soapenv:Envelope>`;
|
|
@@ -965,20 +1104,20 @@ var WsfeService = class _WsfeService {
|
|
|
965
1104
|
if (!det) {
|
|
966
1105
|
throw new ArcaError("Respuesta WSFE incompleta: falta detalle del comprobante", "PARSE_ERROR");
|
|
967
1106
|
}
|
|
968
|
-
const
|
|
1107
|
+
const observations = [];
|
|
969
1108
|
if (det.Observaciones) {
|
|
970
1109
|
const obsArray = Array.isArray(det.Observaciones.Obs) ? det.Observaciones.Obs : [det.Observaciones.Obs];
|
|
971
|
-
obsArray.forEach((o) =>
|
|
1110
|
+
obsArray.forEach((o) => observations.push(o.Msg));
|
|
972
1111
|
}
|
|
973
1112
|
return {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1113
|
+
invoiceType: cab.CbteTipo,
|
|
1114
|
+
pointOfSale: cab.PtoVta,
|
|
1115
|
+
invoiceNumber: Number(det.CbteDesde),
|
|
1116
|
+
date: det.CbteFch,
|
|
978
1117
|
cae: String(det.CAE),
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1118
|
+
caeExpiry: String(det.CAEFchVto),
|
|
1119
|
+
result: det.Resultado,
|
|
1120
|
+
observations: observations.length > 0 ? observations : void 0
|
|
982
1121
|
};
|
|
983
1122
|
}
|
|
984
1123
|
};
|
|
@@ -1000,12 +1139,12 @@ var PadronService = class {
|
|
|
1000
1139
|
});
|
|
1001
1140
|
}
|
|
1002
1141
|
/**
|
|
1003
|
-
* Consulta los datos de
|
|
1004
|
-
*
|
|
1005
|
-
* @param
|
|
1006
|
-
* @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
|
|
1007
1146
|
*/
|
|
1008
|
-
async
|
|
1147
|
+
async getTaxpayer(taxId) {
|
|
1009
1148
|
const ticket = await this.wsaa.login();
|
|
1010
1149
|
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1011
1150
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
@@ -1016,7 +1155,7 @@ var PadronService = class {
|
|
|
1016
1155
|
<token>${ticket.token}</token>
|
|
1017
1156
|
<sign>${ticket.sign}</sign>
|
|
1018
1157
|
<cuitRepresentada>${this.config.cuit}</cuitRepresentada>
|
|
1019
|
-
<idPersona>${
|
|
1158
|
+
<idPersona>${taxId}</idPersona>
|
|
1020
1159
|
</a13:getPersona>
|
|
1021
1160
|
</soapenv:Body>
|
|
1022
1161
|
</soapenv:Envelope>`;
|
|
@@ -1026,14 +1165,13 @@ var PadronService = class {
|
|
|
1026
1165
|
headers: {
|
|
1027
1166
|
"Content-Type": "text/xml; charset=utf-8",
|
|
1028
1167
|
"SOAPAction": ""
|
|
1029
|
-
// A13 no suele requerir SOAPAction específica en el header
|
|
1030
1168
|
},
|
|
1031
1169
|
body: soapRequest,
|
|
1032
1170
|
timeout: this.config.timeout || 15e3
|
|
1033
1171
|
});
|
|
1034
1172
|
if (!response.ok) {
|
|
1035
1173
|
throw new ArcaNetworkError(
|
|
1036
|
-
`Error HTTP al comunicarse con
|
|
1174
|
+
`Error HTTP al comunicarse con Padr\xF3n A13: ${response.status}`,
|
|
1037
1175
|
{ status: response.status }
|
|
1038
1176
|
);
|
|
1039
1177
|
}
|
|
@@ -1041,7 +1179,7 @@ var PadronService = class {
|
|
|
1041
1179
|
return this.parseResponse(xml);
|
|
1042
1180
|
}
|
|
1043
1181
|
/**
|
|
1044
|
-
*
|
|
1182
|
+
* Parsea la respuesta XML de getPersona
|
|
1045
1183
|
*/
|
|
1046
1184
|
parseResponse(xml) {
|
|
1047
1185
|
const parser = new XMLParser2({
|
|
@@ -1051,7 +1189,7 @@ var PadronService = class {
|
|
|
1051
1189
|
const result = parser.parse(xml);
|
|
1052
1190
|
const body = result.Envelope?.Body;
|
|
1053
1191
|
if (!body) {
|
|
1054
|
-
throw new ArcaError("Respuesta
|
|
1192
|
+
throw new ArcaError("Respuesta del Padr\xF3n inv\xE1lida: Body no encontrado", "PADRON_ERROR");
|
|
1055
1193
|
}
|
|
1056
1194
|
const response = body.getPersonaResponse?.personaReturn;
|
|
1057
1195
|
if (!response) {
|
|
@@ -1068,81 +1206,79 @@ var PadronService = class {
|
|
|
1068
1206
|
if (!p) {
|
|
1069
1207
|
return { error: "CUIT no encontrado" };
|
|
1070
1208
|
}
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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),
|
|
1083
1221
|
// 30 = IVA
|
|
1084
|
-
|
|
1222
|
+
isMonotax: this.hasTaxId(p, 20),
|
|
1085
1223
|
// 20 = Monotributo
|
|
1086
|
-
|
|
1087
|
-
// 32 = Exento
|
|
1224
|
+
isVATExempt: this.hasTaxId(p, 32)
|
|
1225
|
+
// 32 = IVA Exento
|
|
1088
1226
|
};
|
|
1089
|
-
return {
|
|
1227
|
+
return { taxpayer };
|
|
1090
1228
|
}
|
|
1091
|
-
|
|
1092
|
-
if (!
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
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
|
|
1101
1238
|
}));
|
|
1102
1239
|
}
|
|
1103
|
-
|
|
1104
|
-
if (!
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
periodo: Number(item.periodo)
|
|
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)
|
|
1111
1247
|
}));
|
|
1112
1248
|
}
|
|
1113
|
-
|
|
1114
|
-
if (!
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
periodo: Number(item.periodo)
|
|
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)
|
|
1120
1255
|
}));
|
|
1121
1256
|
}
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
if (!
|
|
1125
|
-
|
|
1126
|
-
return list.some((i) => Number(i.idImpuesto) === id);
|
|
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);
|
|
1127
1261
|
}
|
|
1128
1262
|
/**
|
|
1129
|
-
*
|
|
1263
|
+
* Normaliza un valor que puede ser un objeto único o un array (comportamiento de fast-xml-parser)
|
|
1130
1264
|
*/
|
|
1131
|
-
|
|
1265
|
+
toArray(data) {
|
|
1132
1266
|
if (data === void 0 || data === null) return [];
|
|
1133
|
-
|
|
1267
|
+
if (Array.isArray(data)) return data;
|
|
1268
|
+
return [data];
|
|
1134
1269
|
}
|
|
1135
1270
|
};
|
|
1136
1271
|
export {
|
|
1137
1272
|
ArcaAuthError,
|
|
1138
1273
|
ArcaError,
|
|
1274
|
+
ArcaNetworkError,
|
|
1139
1275
|
ArcaValidationError,
|
|
1140
|
-
|
|
1276
|
+
BillingConcept,
|
|
1277
|
+
InvoiceType,
|
|
1141
1278
|
PadronService,
|
|
1142
|
-
|
|
1143
|
-
TipoDocumento,
|
|
1279
|
+
TaxIdType,
|
|
1144
1280
|
WsaaService,
|
|
1145
1281
|
WsfeService,
|
|
1146
|
-
|
|
1282
|
+
generateQRUrl
|
|
1147
1283
|
};
|
|
1148
1284
|
//# sourceMappingURL=index.js.map
|