arca-sdk 1.2.2 → 1.3.1
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/dist/index.cjs +538 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -2
- package/dist/index.d.ts +125 -2
- package/dist/index.js +537 -38
- package/dist/index.js.map +1 -1
- package/package.json +59 -59
package/dist/index.js
CHANGED
|
@@ -338,6 +338,7 @@ async function callArcaApi(url, options) {
|
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
// src/auth/wsaa.ts
|
|
341
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
341
342
|
var WsaaService = class {
|
|
342
343
|
config;
|
|
343
344
|
ticketManager;
|
|
@@ -439,13 +440,27 @@ var WsaaService = class {
|
|
|
439
440
|
body: this.buildSoapRequest(cms),
|
|
440
441
|
timeout: this.config.timeout
|
|
441
442
|
});
|
|
443
|
+
const responseText = await response.text();
|
|
442
444
|
if (!response.ok) {
|
|
445
|
+
let errorMessage = `Error HTTP al comunicarse con WSAA: ${response.status} ${response.statusText}`;
|
|
446
|
+
try {
|
|
447
|
+
const parser = new XMLParser2({
|
|
448
|
+
ignoreAttributes: false,
|
|
449
|
+
removeNSPrefix: true
|
|
450
|
+
});
|
|
451
|
+
const result = parser.parse(responseText);
|
|
452
|
+
const fault = result.Envelope?.Body?.Fault;
|
|
453
|
+
if (fault && fault.faultstring) {
|
|
454
|
+
errorMessage = `Error AFIP WSAA: ${fault.faultstring}`;
|
|
455
|
+
}
|
|
456
|
+
} catch (e) {
|
|
457
|
+
}
|
|
443
458
|
throw new ArcaAuthError(
|
|
444
|
-
|
|
445
|
-
{ status: response.status, statusText: response.statusText }
|
|
459
|
+
errorMessage,
|
|
460
|
+
{ status: response.status, statusText: response.statusText, body: responseText }
|
|
446
461
|
);
|
|
447
462
|
}
|
|
448
|
-
const responseXml =
|
|
463
|
+
const responseXml = responseText;
|
|
449
464
|
return parseWsaaResponse(responseXml);
|
|
450
465
|
}
|
|
451
466
|
/**
|
|
@@ -472,23 +487,23 @@ var WsaaService = class {
|
|
|
472
487
|
};
|
|
473
488
|
|
|
474
489
|
// src/types/wsfe.ts
|
|
475
|
-
var InvoiceType = /* @__PURE__ */ ((
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return
|
|
490
|
+
var InvoiceType = /* @__PURE__ */ ((InvoiceType3) => {
|
|
491
|
+
InvoiceType3[InvoiceType3["FACTURA_A"] = 1] = "FACTURA_A";
|
|
492
|
+
InvoiceType3[InvoiceType3["NOTA_DEBITO_A"] = 2] = "NOTA_DEBITO_A";
|
|
493
|
+
InvoiceType3[InvoiceType3["NOTA_CREDITO_A"] = 3] = "NOTA_CREDITO_A";
|
|
494
|
+
InvoiceType3[InvoiceType3["RECIBO_A"] = 4] = "RECIBO_A";
|
|
495
|
+
InvoiceType3[InvoiceType3["FACTURA_B"] = 6] = "FACTURA_B";
|
|
496
|
+
InvoiceType3[InvoiceType3["NOTA_DEBITO_B"] = 7] = "NOTA_DEBITO_B";
|
|
497
|
+
InvoiceType3[InvoiceType3["NOTA_CREDITO_B"] = 8] = "NOTA_CREDITO_B";
|
|
498
|
+
InvoiceType3[InvoiceType3["RECIBO_B"] = 9] = "RECIBO_B";
|
|
499
|
+
InvoiceType3[InvoiceType3["FACTURA_C"] = 11] = "FACTURA_C";
|
|
500
|
+
InvoiceType3[InvoiceType3["NOTA_DEBITO_C"] = 12] = "NOTA_DEBITO_C";
|
|
501
|
+
InvoiceType3[InvoiceType3["NOTA_CREDITO_C"] = 13] = "NOTA_CREDITO_C";
|
|
502
|
+
InvoiceType3[InvoiceType3["RECIBO_C"] = 15] = "RECIBO_C";
|
|
503
|
+
InvoiceType3[InvoiceType3["TICKET_A"] = 81] = "TICKET_A";
|
|
504
|
+
InvoiceType3[InvoiceType3["TICKET_B"] = 82] = "TICKET_B";
|
|
505
|
+
InvoiceType3[InvoiceType3["TICKET_C"] = 83] = "TICKET_C";
|
|
506
|
+
return InvoiceType3;
|
|
492
507
|
})(InvoiceType || {});
|
|
493
508
|
var BillingConcept = /* @__PURE__ */ ((BillingConcept2) => {
|
|
494
509
|
BillingConcept2[BillingConcept2["PRODUCTS"] = 1] = "PRODUCTS";
|
|
@@ -496,19 +511,19 @@ var BillingConcept = /* @__PURE__ */ ((BillingConcept2) => {
|
|
|
496
511
|
BillingConcept2[BillingConcept2["PRODUCTS_AND_SERVICES"] = 3] = "PRODUCTS_AND_SERVICES";
|
|
497
512
|
return BillingConcept2;
|
|
498
513
|
})(BillingConcept || {});
|
|
499
|
-
var TaxIdType = /* @__PURE__ */ ((
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return
|
|
514
|
+
var TaxIdType = /* @__PURE__ */ ((TaxIdType3) => {
|
|
515
|
+
TaxIdType3[TaxIdType3["CUIT"] = 80] = "CUIT";
|
|
516
|
+
TaxIdType3[TaxIdType3["CUIL"] = 86] = "CUIL";
|
|
517
|
+
TaxIdType3[TaxIdType3["CDI"] = 87] = "CDI";
|
|
518
|
+
TaxIdType3[TaxIdType3["LE"] = 89] = "LE";
|
|
519
|
+
TaxIdType3[TaxIdType3["LC"] = 90] = "LC";
|
|
520
|
+
TaxIdType3[TaxIdType3["FOREIGN_ID"] = 91] = "FOREIGN_ID";
|
|
521
|
+
TaxIdType3[TaxIdType3["PASSPORT"] = 94] = "PASSPORT";
|
|
522
|
+
TaxIdType3[TaxIdType3["BUENOS_AIRES_ID"] = 95] = "BUENOS_AIRES_ID";
|
|
523
|
+
TaxIdType3[TaxIdType3["NATIONAL_POLICE_ID"] = 96] = "NATIONAL_POLICE_ID";
|
|
524
|
+
TaxIdType3[TaxIdType3["DNI"] = 96] = "DNI";
|
|
525
|
+
TaxIdType3[TaxIdType3["FINAL_CONSUMER"] = 99] = "FINAL_CONSUMER";
|
|
526
|
+
return TaxIdType3;
|
|
512
527
|
})(TaxIdType || {});
|
|
513
528
|
var VatCondition = /* @__PURE__ */ ((VatCondition2) => {
|
|
514
529
|
VatCondition2[VatCondition2["IVA_RESPONSABLE_INSCRIPTO"] = 1] = "IVA_RESPONSABLE_INSCRIPTO";
|
|
@@ -1020,7 +1035,6 @@ var WsfeService = class _WsfeService {
|
|
|
1020
1035
|
* Método genérico interno para emitir cualquier tipo de comprobante.
|
|
1021
1036
|
*/
|
|
1022
1037
|
async issueDocument(request) {
|
|
1023
|
-
const invoiceNumber = await this.getNextInvoiceNumber(request.type);
|
|
1024
1038
|
let total = request.total || 0;
|
|
1025
1039
|
let net = total;
|
|
1026
1040
|
let vat = 0;
|
|
@@ -1033,6 +1047,8 @@ var WsfeService = class _WsfeService {
|
|
|
1033
1047
|
if (total <= 0) {
|
|
1034
1048
|
throw new ArcaValidationError("El monto total debe ser mayor a 0");
|
|
1035
1049
|
}
|
|
1050
|
+
this.validateBuyer(request.buyer, total);
|
|
1051
|
+
const invoiceNumber = await this.getNextInvoiceNumber(request.type);
|
|
1036
1052
|
const soapRequest = this.buildCAERequest({
|
|
1037
1053
|
type: request.type,
|
|
1038
1054
|
pointOfSale: this.config.pointOfSale,
|
|
@@ -1126,6 +1142,23 @@ var WsfeService = class _WsfeService {
|
|
|
1126
1142
|
);
|
|
1127
1143
|
}
|
|
1128
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Valida obligatoriamente al comprador cuando el monto es mayor o igual a $10.000.000 para consumidor final
|
|
1147
|
+
*/
|
|
1148
|
+
validateBuyer(buyer, total) {
|
|
1149
|
+
const isFinalConsumer = !buyer || buyer.docType === 99 /* FINAL_CONSUMER */ || buyer.docNumber === "0" || buyer.docNumber === "";
|
|
1150
|
+
if (total >= 1e7 && isFinalConsumer) {
|
|
1151
|
+
const errorMessage = "Para importes mayores o iguales a $10.000.000 es obligatorio identificar al comprador (RG 5824/2026).";
|
|
1152
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1153
|
+
console.warn(`[arca-sdk WARNING] ${errorMessage}`);
|
|
1154
|
+
}
|
|
1155
|
+
throw new ArcaValidationError(errorMessage, {
|
|
1156
|
+
total,
|
|
1157
|
+
buyer,
|
|
1158
|
+
hint: "Actualiz\xE1 el objeto buyer con un tipo de documento v\xE1lido (DNI, CUIT, CUIL) y su n\xFAmero correspondiente."
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1129
1162
|
/**
|
|
1130
1163
|
* Calcula el IVA agrupado por alícuota (requerido por ARCA para Factura A/B)
|
|
1131
1164
|
*/
|
|
@@ -1329,7 +1362,7 @@ var WsfeService = class _WsfeService {
|
|
|
1329
1362
|
};
|
|
1330
1363
|
|
|
1331
1364
|
// src/services/padron.ts
|
|
1332
|
-
import { XMLParser as
|
|
1365
|
+
import { XMLParser as XMLParser3 } from "fast-xml-parser";
|
|
1333
1366
|
var PadronService = class {
|
|
1334
1367
|
wsaa;
|
|
1335
1368
|
config;
|
|
@@ -1388,7 +1421,7 @@ var PadronService = class {
|
|
|
1388
1421
|
* Parsea la respuesta XML de getPersona
|
|
1389
1422
|
*/
|
|
1390
1423
|
parseResponse(xml) {
|
|
1391
|
-
const parser = new
|
|
1424
|
+
const parser = new XMLParser3({
|
|
1392
1425
|
ignoreAttributes: false,
|
|
1393
1426
|
removeNSPrefix: true
|
|
1394
1427
|
});
|
|
@@ -1425,8 +1458,10 @@ var PadronService = class {
|
|
|
1425
1458
|
mainActivity: p.descripcionActividadPrincipal,
|
|
1426
1459
|
isVATRegistered: this.hasTaxId(p, 30),
|
|
1427
1460
|
// 30 = IVA
|
|
1428
|
-
isMonotax: this.hasTaxId(p, 20),
|
|
1429
|
-
//
|
|
1461
|
+
isMonotax: this.hasTaxId(p, 20) || this.hasTaxId(p, 24) || this.hasTaxId(p, 21),
|
|
1462
|
+
// General o Social/Autónomo
|
|
1463
|
+
isSocialMonotax: this.hasTaxId(p, 24) || this.hasTaxId(p, 21),
|
|
1464
|
+
// 24 = Obra Social / Promovido, 21 = Autónomo
|
|
1430
1465
|
isVATExempt: this.hasTaxId(p, 32)
|
|
1431
1466
|
// 32 = IVA Exento
|
|
1432
1467
|
};
|
|
@@ -1474,12 +1509,476 @@ var PadronService = class {
|
|
|
1474
1509
|
return [data];
|
|
1475
1510
|
}
|
|
1476
1511
|
};
|
|
1512
|
+
|
|
1513
|
+
// src/services/caea.ts
|
|
1514
|
+
var CaeaService = class {
|
|
1515
|
+
config;
|
|
1516
|
+
constructor(config) {
|
|
1517
|
+
this.validateConfig(config);
|
|
1518
|
+
this.config = config;
|
|
1519
|
+
}
|
|
1520
|
+
validateConfig(config) {
|
|
1521
|
+
if (!config.ticket || !config.ticket.token) {
|
|
1522
|
+
throw new ArcaValidationError(
|
|
1523
|
+
"Ticket WSAA requerido. Ejecut\xE1 wsaa.login() primero.",
|
|
1524
|
+
{ hint: "El ticket se obtiene del servicio WsaaService" }
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (!config.pointOfSale || config.pointOfSale < 1 || config.pointOfSale > 9999) {
|
|
1528
|
+
throw new ArcaValidationError(
|
|
1529
|
+
"Punto de venta inv\xE1lido: debe ser un n\xFAmero entre 1 y 9999",
|
|
1530
|
+
{ pointOfSale: config.pointOfSale }
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1535
|
+
// Métodos del Ciclo de Vida de CAEA
|
|
1536
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1537
|
+
/**
|
|
1538
|
+
* Solicita un CAEA (Código de Autorización Electrónico Anticipado) para una quincena específica (FECAEASolicitar).
|
|
1539
|
+
*/
|
|
1540
|
+
async solicitCAEA(params) {
|
|
1541
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1542
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1543
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
1544
|
+
<soapenv:Header/>
|
|
1545
|
+
<soapenv:Body>
|
|
1546
|
+
<ar:FECAEASolicitar>
|
|
1547
|
+
<ar:Auth>
|
|
1548
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
1549
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
1550
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
1551
|
+
</ar:Auth>
|
|
1552
|
+
<ar:Periodo>${params.period}</ar:Periodo>
|
|
1553
|
+
<ar:Orden>${params.order}</ar:Orden>
|
|
1554
|
+
</ar:FECAEASolicitar>
|
|
1555
|
+
</soapenv:Body>
|
|
1556
|
+
</soapenv:Envelope>`;
|
|
1557
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
1558
|
+
const response = await callArcaApi(endpoint, {
|
|
1559
|
+
method: "POST",
|
|
1560
|
+
headers: {
|
|
1561
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1562
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECAEASolicitar"
|
|
1563
|
+
},
|
|
1564
|
+
body: soapRequest,
|
|
1565
|
+
timeout: this.config.timeout
|
|
1566
|
+
});
|
|
1567
|
+
if (!response.ok) {
|
|
1568
|
+
throw new ArcaError(`Error HTTP al solicitar CAEA: ${response.status}`, "HTTP_ERROR");
|
|
1569
|
+
}
|
|
1570
|
+
const responseXml = await response.text();
|
|
1571
|
+
const result = parseXml(responseXml);
|
|
1572
|
+
const data = result?.Envelope?.Body?.FECAEASolicitarResponse?.FECAEASolicitarResult;
|
|
1573
|
+
if (!data) {
|
|
1574
|
+
throw new ArcaError("Respuesta FECAEASolicitar inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
1575
|
+
}
|
|
1576
|
+
if (data.Errors) {
|
|
1577
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
1578
|
+
const code = error?.Code || "UNKNOWN";
|
|
1579
|
+
throw new ArcaError(
|
|
1580
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1581
|
+
"ARCA_ERROR",
|
|
1582
|
+
data.Errors,
|
|
1583
|
+
getArcaHint(code)
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
const res = data.ResultGet;
|
|
1587
|
+
return {
|
|
1588
|
+
caea: String(res.CAEA),
|
|
1589
|
+
period: Number(res.Periodo),
|
|
1590
|
+
order: Number(res.Orden),
|
|
1591
|
+
expiryDate: String(res.FchVto),
|
|
1592
|
+
actualDate: String(res.FchVigDesde),
|
|
1593
|
+
receptionDate: String(res.FchVigHasta),
|
|
1594
|
+
limitDate: String(res.FchTopeInf)
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Consulta un CAEA ya emitido (FECAEAConsultar).
|
|
1599
|
+
*/
|
|
1600
|
+
async getCAEA(caea) {
|
|
1601
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1602
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1603
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
1604
|
+
<soapenv:Header/>
|
|
1605
|
+
<soapenv:Body>
|
|
1606
|
+
<ar:FECAEAConsultar>
|
|
1607
|
+
<ar:Auth>
|
|
1608
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
1609
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
1610
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
1611
|
+
</ar:Auth>
|
|
1612
|
+
<ar:Caea>${caea}</ar:Caea>
|
|
1613
|
+
</ar:FECAEAConsultar>
|
|
1614
|
+
</soapenv:Body>
|
|
1615
|
+
</soapenv:Envelope>`;
|
|
1616
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
1617
|
+
const response = await callArcaApi(endpoint, {
|
|
1618
|
+
method: "POST",
|
|
1619
|
+
headers: {
|
|
1620
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1621
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECAEAConsultar"
|
|
1622
|
+
},
|
|
1623
|
+
body: soapRequest,
|
|
1624
|
+
timeout: this.config.timeout
|
|
1625
|
+
});
|
|
1626
|
+
if (!response.ok) {
|
|
1627
|
+
throw new ArcaError(`Error HTTP al consultar CAEA: ${response.status}`, "HTTP_ERROR");
|
|
1628
|
+
}
|
|
1629
|
+
const responseXml = await response.text();
|
|
1630
|
+
const result = parseXml(responseXml);
|
|
1631
|
+
const data = result?.Envelope?.Body?.FECAEAConsultarResponse?.FECAEAConsultarResult;
|
|
1632
|
+
if (!data) {
|
|
1633
|
+
throw new ArcaError("Respuesta FECAEAConsultar inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
1634
|
+
}
|
|
1635
|
+
if (data.Errors) {
|
|
1636
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
1637
|
+
const code = error?.Code || "UNKNOWN";
|
|
1638
|
+
throw new ArcaError(
|
|
1639
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1640
|
+
"ARCA_ERROR",
|
|
1641
|
+
data.Errors,
|
|
1642
|
+
getArcaHint(code)
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
const res = data.ResultGet;
|
|
1646
|
+
return {
|
|
1647
|
+
caea: String(res.CAEA),
|
|
1648
|
+
period: Number(res.Periodo),
|
|
1649
|
+
order: Number(res.Orden),
|
|
1650
|
+
expiryDate: String(res.FchVto),
|
|
1651
|
+
actualDate: String(res.FchVigDesde),
|
|
1652
|
+
receptionDate: String(res.FchVigHasta),
|
|
1653
|
+
limitDate: String(res.FchTopeInf)
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Rinde/registra informativamente los comprobantes emitidos en contingencia local bajo un CAEA específico (FECAEARegInformativo).
|
|
1658
|
+
* Soporta el envío en lotes de comprobantes homogéneos del mismo tipo de factura y punto de venta.
|
|
1659
|
+
*/
|
|
1660
|
+
async reportCAEAPeriod(params) {
|
|
1661
|
+
if (!params.invoices || params.invoices.length === 0) {
|
|
1662
|
+
throw new ArcaValidationError("Debe proveer al menos un comprobante para realizar la rendici\xF3n del CAEA.");
|
|
1663
|
+
}
|
|
1664
|
+
const firstInvoice = params.invoices[0];
|
|
1665
|
+
const invoiceType = firstInvoice.invoiceType;
|
|
1666
|
+
const countReg = params.invoices.length;
|
|
1667
|
+
const invalidInvoices = params.invoices.filter((inv) => inv.invoiceType !== invoiceType);
|
|
1668
|
+
if (invalidInvoices.length > 0) {
|
|
1669
|
+
throw new ArcaValidationError("Todos los comprobantes en un mismo lote de rendici\xF3n informativa deben poseer el mismo tipo de factura (invoiceType).");
|
|
1670
|
+
}
|
|
1671
|
+
let detXml = "";
|
|
1672
|
+
params.invoices.forEach((inv) => {
|
|
1673
|
+
const date = inv.date || /* @__PURE__ */ new Date();
|
|
1674
|
+
const dateStr = date.toISOString().split("T")[0].replace(/-/g, "");
|
|
1675
|
+
let total = 0;
|
|
1676
|
+
let net = 0;
|
|
1677
|
+
let vat = 0;
|
|
1678
|
+
let vatXml = "";
|
|
1679
|
+
const includesVAT = inv.includesVAT || false;
|
|
1680
|
+
if (inv.items && inv.items.length > 0) {
|
|
1681
|
+
net = round(calculateSubtotal(inv.items, includesVAT));
|
|
1682
|
+
vat = round(calculateVAT(inv.items, includesVAT));
|
|
1683
|
+
total = round(calculateTotal(inv.items, includesVAT));
|
|
1684
|
+
const byRate = /* @__PURE__ */ new Map();
|
|
1685
|
+
inv.items.forEach((item) => {
|
|
1686
|
+
const rate = item.vatRate || 0;
|
|
1687
|
+
let netPrice = item.unitPrice;
|
|
1688
|
+
if (includesVAT && rate) {
|
|
1689
|
+
netPrice = item.unitPrice / (1 + rate / 100);
|
|
1690
|
+
}
|
|
1691
|
+
const base = item.quantity * netPrice;
|
|
1692
|
+
const amount = base * rate / 100;
|
|
1693
|
+
const current = byRate.get(rate) || { base: 0, amount: 0 };
|
|
1694
|
+
byRate.set(rate, {
|
|
1695
|
+
base: current.base + base,
|
|
1696
|
+
amount: current.amount + amount
|
|
1697
|
+
});
|
|
1698
|
+
});
|
|
1699
|
+
const vatEntries = Array.from(byRate.entries()).map(([rate, values]) => ({
|
|
1700
|
+
rate,
|
|
1701
|
+
taxBase: values.base,
|
|
1702
|
+
amount: values.amount
|
|
1703
|
+
}));
|
|
1704
|
+
if (vatEntries.length > 0) {
|
|
1705
|
+
vatXml = "<ar:Iva>";
|
|
1706
|
+
vatEntries.forEach((entry) => {
|
|
1707
|
+
vatXml += `
|
|
1708
|
+
<ar:AlicIva>
|
|
1709
|
+
<ar:Id>${this.getVATCode(entry.rate)}</ar:Id>
|
|
1710
|
+
<ar:BaseImp>${entry.taxBase.toFixed(2)}</ar:BaseImp>
|
|
1711
|
+
<ar:Importe>${entry.amount.toFixed(2)}</ar:Importe>
|
|
1712
|
+
</ar:AlicIva>`;
|
|
1713
|
+
});
|
|
1714
|
+
vatXml += "\n </ar:Iva>";
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (total <= 0) {
|
|
1718
|
+
throw new ArcaValidationError(`El monto total del comprobante n\xFAmero ${inv.invoiceNumber} debe ser mayor a 0.`);
|
|
1719
|
+
}
|
|
1720
|
+
const condicionIVAReceptorXml = inv.buyer?.vatCondition !== void 0 ? `
|
|
1721
|
+
<ar:CondicionIVAReceptorId>${inv.buyer.vatCondition}</ar:CondicionIVAReceptorId>` : "";
|
|
1722
|
+
let fechasServicioXml = "";
|
|
1723
|
+
if (inv.concept === 2 /* SERVICES */ || inv.concept === 3 /* PRODUCTS_AND_SERVICES */) {
|
|
1724
|
+
const defaultDateStr = date.toISOString().split("T")[0].replace(/-/g, "");
|
|
1725
|
+
fechasServicioXml = `
|
|
1726
|
+
<ar:FchServDesde>${defaultDateStr}</ar:FchServDesde>
|
|
1727
|
+
<ar:FchServHasta>${defaultDateStr}</ar:FchServHasta>
|
|
1728
|
+
<ar:FchVtoPago>${defaultDateStr}</ar:FchVtoPago>`;
|
|
1729
|
+
}
|
|
1730
|
+
let asocXml = "";
|
|
1731
|
+
if (inv.associatedInvoices && inv.associatedInvoices.length > 0) {
|
|
1732
|
+
asocXml = "<ar:CbtesAsoc>";
|
|
1733
|
+
inv.associatedInvoices.forEach((asoc) => {
|
|
1734
|
+
asocXml += `
|
|
1735
|
+
<ar:CbteAsoc>
|
|
1736
|
+
<ar:Tipo>${asoc.type}</ar:Tipo>
|
|
1737
|
+
<ar:PtoVta>${asoc.pointOfSale}</ar:PtoVta>
|
|
1738
|
+
<ar:Nro>${asoc.invoiceNumber}</ar:Nro>
|
|
1739
|
+
${asoc.cuit ? `<ar:Cuit>${asoc.cuit}</ar:Cuit>` : ""}
|
|
1740
|
+
${asoc.date ? `<ar:CbteFch>${asoc.date.toISOString().split("T")[0].replace(/-/g, "")}</ar:CbteFch>` : ""}
|
|
1741
|
+
</ar:CbteAsoc>`;
|
|
1742
|
+
});
|
|
1743
|
+
asocXml += "\n </ar:CbtesAsoc>";
|
|
1744
|
+
}
|
|
1745
|
+
let optXml = "";
|
|
1746
|
+
if (inv.optionals && inv.optionals.length > 0) {
|
|
1747
|
+
optXml = "<ar:Opcionales>";
|
|
1748
|
+
inv.optionals.forEach((opt) => {
|
|
1749
|
+
optXml += `
|
|
1750
|
+
<ar:Opcional>
|
|
1751
|
+
<ar:Id>${opt.id}</ar:Id>
|
|
1752
|
+
<ar:Valor>${opt.value}</ar:Valor>
|
|
1753
|
+
</ar:Opcional>`;
|
|
1754
|
+
});
|
|
1755
|
+
optXml += "\n </ar:Opcionales>";
|
|
1756
|
+
}
|
|
1757
|
+
detXml += `
|
|
1758
|
+
<ar:FECAEADetRequest>
|
|
1759
|
+
<ar:Concepto>${inv.concept}</ar:Concepto>
|
|
1760
|
+
<ar:DocTipo>${inv.buyer?.docType || 99}</ar:DocTipo>
|
|
1761
|
+
<ar:DocNro>${inv.buyer?.docNumber || 0}</ar:DocNro>${condicionIVAReceptorXml}
|
|
1762
|
+
<ar:CbteDesde>${inv.invoiceNumber}</ar:CbteDesde>
|
|
1763
|
+
<ar:CbteHasta>${inv.invoiceNumber}</ar:CbteHasta>
|
|
1764
|
+
<ar:CbteFch>${dateStr}</ar:CbteFch>
|
|
1765
|
+
<ar:ImpTotal>${total.toFixed(2)}</ar:ImpTotal>
|
|
1766
|
+
<ar:ImpTotConc>0.00</ar:ImpTotConc>
|
|
1767
|
+
<ar:ImpNeto>${net.toFixed(2)}</ar:ImpNeto>
|
|
1768
|
+
<ar:ImpOpEx>0.00</ar:ImpOpEx>
|
|
1769
|
+
<ar:ImpIVA>${vat.toFixed(2)}</ar:ImpIVA>
|
|
1770
|
+
<ar:ImpTrib>0.00</ar:ImpTrib>
|
|
1771
|
+
<ar:MonId>PES</ar:MonId>
|
|
1772
|
+
<ar:MonCotiz>1</ar:MonCotiz>
|
|
1773
|
+
<ar:CAEA>${params.caea}</ar:CAEA>${fechasServicioXml}
|
|
1774
|
+
${asocXml}
|
|
1775
|
+
${vatXml}
|
|
1776
|
+
${optXml}
|
|
1777
|
+
</ar:FECAEADetRequest>`;
|
|
1778
|
+
});
|
|
1779
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1780
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1781
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
1782
|
+
<soapenv:Header/>
|
|
1783
|
+
<soapenv:Body>
|
|
1784
|
+
<ar:FECAEARegInformativo>
|
|
1785
|
+
<ar:Auth>
|
|
1786
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
1787
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
1788
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
1789
|
+
</ar:Auth>
|
|
1790
|
+
<ar:FeCAEARegInfReq>
|
|
1791
|
+
<ar:FeCabReq>
|
|
1792
|
+
<ar:CantReg>${countReg}</ar:CantReg>
|
|
1793
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
1794
|
+
<ar:CbteTipo>${invoiceType}</ar:CbteTipo>
|
|
1795
|
+
</ar:FeCabReq>
|
|
1796
|
+
<ar:FeDetReq>${detXml}
|
|
1797
|
+
</ar:FeDetReq>
|
|
1798
|
+
</ar:FeCAEARegInfReq>
|
|
1799
|
+
</ar:FECAEARegInformativo>
|
|
1800
|
+
</soapenv:Body>
|
|
1801
|
+
</soapenv:Envelope>`;
|
|
1802
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
1803
|
+
const response = await callArcaApi(endpoint, {
|
|
1804
|
+
method: "POST",
|
|
1805
|
+
headers: {
|
|
1806
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1807
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECAEARegInformativo"
|
|
1808
|
+
},
|
|
1809
|
+
body: soapRequest,
|
|
1810
|
+
timeout: this.config.timeout
|
|
1811
|
+
});
|
|
1812
|
+
if (!response.ok) {
|
|
1813
|
+
throw new ArcaError(`Error HTTP al rendir CAEA: ${response.status}`, "HTTP_ERROR");
|
|
1814
|
+
}
|
|
1815
|
+
const responseXml = await response.text();
|
|
1816
|
+
const result = parseXml(responseXml);
|
|
1817
|
+
const data = result?.Envelope?.Body?.FECAEARegInformativoResponse?.FECAEARegInformativoResult;
|
|
1818
|
+
if (!data) {
|
|
1819
|
+
throw new ArcaError("Respuesta FECAEARegInformativo inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
1820
|
+
}
|
|
1821
|
+
if (data.Errors) {
|
|
1822
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
1823
|
+
const code = error?.Code || "UNKNOWN";
|
|
1824
|
+
throw new ArcaError(
|
|
1825
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1826
|
+
"ARCA_ERROR",
|
|
1827
|
+
data.Errors,
|
|
1828
|
+
getArcaHint(code)
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
const cab = data.FeCabResp;
|
|
1832
|
+
const det = Array.isArray(data.FeDetResp.FECAEDetResponse) ? data.FeDetResp.FECAEDetResponse[0] : data.FeDetResp.FECAEDetResponse;
|
|
1833
|
+
const observations = [];
|
|
1834
|
+
if (det?.Observaciones) {
|
|
1835
|
+
const obsArray = Array.isArray(det.Observaciones.Obs) ? det.Observaciones.Obs : [det.Observaciones.Obs];
|
|
1836
|
+
obsArray.forEach((o) => observations.push(o.Msg));
|
|
1837
|
+
}
|
|
1838
|
+
return {
|
|
1839
|
+
caea: String(det.CAEA || params.caea),
|
|
1840
|
+
result: cab.Resultado,
|
|
1841
|
+
pointOfSale: Number(cab.PtoVta),
|
|
1842
|
+
invoiceType: Number(cab.CbteTipo),
|
|
1843
|
+
observations: observations.length > 0 ? observations : void 0
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Informa que un CAEA no tuvo movimientos en la quincena (FECAEASinMovimientoInformar).
|
|
1848
|
+
*/
|
|
1849
|
+
async reportCAEANoMovement(params) {
|
|
1850
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1851
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1852
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
1853
|
+
<soapenv:Header/>
|
|
1854
|
+
<soapenv:Body>
|
|
1855
|
+
<ar:FECAEASinMovimientoInformar>
|
|
1856
|
+
<ar:Auth>
|
|
1857
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
1858
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
1859
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
1860
|
+
</ar:Auth>
|
|
1861
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
1862
|
+
<ar:Caea>${params.caea}</ar:Caea>
|
|
1863
|
+
</ar:FECAEASinMovimientoInformar>
|
|
1864
|
+
</soapenv:Body>
|
|
1865
|
+
</soapenv:Envelope>`;
|
|
1866
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
1867
|
+
const response = await callArcaApi(endpoint, {
|
|
1868
|
+
method: "POST",
|
|
1869
|
+
headers: {
|
|
1870
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1871
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECAEASinMovimientoInformar"
|
|
1872
|
+
},
|
|
1873
|
+
body: soapRequest,
|
|
1874
|
+
timeout: this.config.timeout
|
|
1875
|
+
});
|
|
1876
|
+
if (!response.ok) {
|
|
1877
|
+
throw new ArcaError(`Error HTTP al informar CAEA sin movimientos: ${response.status}`, "HTTP_ERROR");
|
|
1878
|
+
}
|
|
1879
|
+
const responseXml = await response.text();
|
|
1880
|
+
const result = parseXml(responseXml);
|
|
1881
|
+
const data = result?.Envelope?.Body?.FECAEASinMovimientoInformarResponse?.FECAEASinMovimientoInformarResult;
|
|
1882
|
+
if (!data) {
|
|
1883
|
+
throw new ArcaError("Respuesta FECAEASinMovimientoInformar inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
1884
|
+
}
|
|
1885
|
+
if (data.Errors) {
|
|
1886
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
1887
|
+
const code = error?.Code || "UNKNOWN";
|
|
1888
|
+
throw new ArcaError(
|
|
1889
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1890
|
+
"ARCA_ERROR",
|
|
1891
|
+
data.Errors,
|
|
1892
|
+
getArcaHint(code)
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Consulta si se informó la falta de movimientos de un CAEA (FECAEASinMovimientoConsultar).
|
|
1898
|
+
*/
|
|
1899
|
+
async getCAEANoMovement(caea) {
|
|
1900
|
+
const soapRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1901
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
|
1902
|
+
xmlns:ar="http://ar.gov.afip.dif.FEV1/">
|
|
1903
|
+
<soapenv:Header/>
|
|
1904
|
+
<soapenv:Body>
|
|
1905
|
+
<ar:FECAEASinMovimientoConsultar>
|
|
1906
|
+
<ar:Auth>
|
|
1907
|
+
<ar:Token>${this.config.ticket.token}</ar:Token>
|
|
1908
|
+
<ar:Sign>${this.config.ticket.sign}</ar:Sign>
|
|
1909
|
+
<ar:Cuit>${this.config.cuit}</ar:Cuit>
|
|
1910
|
+
</ar:Auth>
|
|
1911
|
+
<ar:PtoVta>${this.config.pointOfSale}</ar:PtoVta>
|
|
1912
|
+
<ar:Caea>${caea}</ar:Caea>
|
|
1913
|
+
</ar:FECAEASinMovimientoConsultar>
|
|
1914
|
+
</soapenv:Body>
|
|
1915
|
+
</soapenv:Envelope>`;
|
|
1916
|
+
const endpoint = getWsfeEndpoint(this.config.environment);
|
|
1917
|
+
const response = await callArcaApi(endpoint, {
|
|
1918
|
+
method: "POST",
|
|
1919
|
+
headers: {
|
|
1920
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
1921
|
+
"SOAPAction": "http://ar.gov.afip.dif.FEV1/FECAEASinMovimientoConsultar"
|
|
1922
|
+
},
|
|
1923
|
+
body: soapRequest,
|
|
1924
|
+
timeout: this.config.timeout
|
|
1925
|
+
});
|
|
1926
|
+
if (!response.ok) {
|
|
1927
|
+
throw new ArcaError(`Error HTTP al consultar CAEA sin movimientos: ${response.status}`, "HTTP_ERROR");
|
|
1928
|
+
}
|
|
1929
|
+
const responseXml = await response.text();
|
|
1930
|
+
const result = parseXml(responseXml);
|
|
1931
|
+
const data = result?.Envelope?.Body?.FECAEASinMovimientoConsultarResponse?.FECAEASinMovimientoConsultarResult;
|
|
1932
|
+
if (!data) {
|
|
1933
|
+
throw new ArcaError("Respuesta FECAEASinMovimientoConsultar inv\xE1lida", "PARSE_ERROR", { xml: responseXml });
|
|
1934
|
+
}
|
|
1935
|
+
if (data.Errors) {
|
|
1936
|
+
const error = Array.isArray(data.Errors.Err) ? data.Errors.Err[0] : data.Errors.Err;
|
|
1937
|
+
const code = error?.Code || "UNKNOWN";
|
|
1938
|
+
throw new ArcaError(
|
|
1939
|
+
`Error ARCA: ${error?.Msg || "Error desconocido"}`,
|
|
1940
|
+
"ARCA_ERROR",
|
|
1941
|
+
data.Errors,
|
|
1942
|
+
getArcaHint(code)
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
return data.ResultGet?.FECAEASinMov || null;
|
|
1946
|
+
}
|
|
1947
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1948
|
+
// Métodos internos auxiliares
|
|
1949
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1950
|
+
/**
|
|
1951
|
+
* Mapea alícuotas numéricas a los códigos internos de ARCA
|
|
1952
|
+
*/
|
|
1953
|
+
getVATCode(rate) {
|
|
1954
|
+
switch (rate) {
|
|
1955
|
+
case 0:
|
|
1956
|
+
return 3;
|
|
1957
|
+
// 0% / Exento / No gravado (código 3 en catálogo ARCA)
|
|
1958
|
+
case 10.5:
|
|
1959
|
+
return 4;
|
|
1960
|
+
case 21:
|
|
1961
|
+
return 5;
|
|
1962
|
+
case 27:
|
|
1963
|
+
return 6;
|
|
1964
|
+
case 5:
|
|
1965
|
+
return 8;
|
|
1966
|
+
case 2.5:
|
|
1967
|
+
return 9;
|
|
1968
|
+
default:
|
|
1969
|
+
throw new ArcaValidationError(`Al\xEDcuota IVA no soportada por ARCA: ${rate}%`, {
|
|
1970
|
+
supportedRates: [0, 10.5, 21, 27, 5, 2.5]
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1477
1975
|
export {
|
|
1478
1976
|
ArcaAuthError,
|
|
1479
1977
|
ArcaError,
|
|
1480
1978
|
ArcaNetworkError,
|
|
1481
1979
|
ArcaValidationError,
|
|
1482
1980
|
BillingConcept,
|
|
1981
|
+
CaeaService,
|
|
1483
1982
|
InvoiceType,
|
|
1484
1983
|
PadronService,
|
|
1485
1984
|
TaxIdType,
|