facturas 0.21.0 → 0.22.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.
Files changed (37) hide show
  1. package/README.md +4 -2
  2. package/dist/{chunk-C2KX652X.mjs → chunk-5CGIRDZY.mjs} +5 -2
  3. package/dist/chunk-5CGIRDZY.mjs.map +1 -0
  4. package/dist/{chunk-FQD2PZXE.mjs → chunk-67ISWKGE.mjs} +3 -2
  5. package/dist/{chunk-4PSLYA2B.mjs → chunk-7MNVZSW6.mjs} +12 -3
  6. package/dist/chunk-7MNVZSW6.mjs.map +1 -0
  7. package/dist/{chunk-QHNI4ZIA.mjs → chunk-FSJHE4VS.mjs} +2 -2
  8. package/dist/{chunk-HSGTCGUX.mjs → chunk-GQGYO2Z2.mjs} +2 -2
  9. package/dist/{chunk-WEEV76IC.mjs → chunk-R4MOVPYT.mjs} +2815 -299
  10. package/dist/chunk-R4MOVPYT.mjs.map +1 -0
  11. package/dist/cli.d.ts +4 -4
  12. package/dist/cli.mjs +9 -9
  13. package/dist/cli.mjs.map +1 -1
  14. package/dist/{client-CP1PQD_j.d.ts → client-ghHzZdu8.d.ts} +613 -73
  15. package/dist/constants.d.ts +3 -0
  16. package/dist/constants.mjs +1 -1
  17. package/dist/errors.d.ts +2 -2
  18. package/dist/{fiscal-evidence-Cp-ntg5p.d.ts → fiscal-evidence-DjG8zB3o.d.ts} +2 -2
  19. package/dist/{index-abxokwjJ.d.ts → index-BSTxy3nN.d.ts} +1 -1
  20. package/dist/index.d.ts +5 -5
  21. package/dist/index.mjs +10 -10
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/padron.d.ts +2 -2
  24. package/dist/padron.mjs +2 -2
  25. package/dist/{types-DhrPnj3r.d.ts → types-CbvsjIhf.d.ts} +2 -2
  26. package/dist/types.d.ts +1 -1
  27. package/dist/wsfe.d.ts +3 -3
  28. package/dist/wsfe.mjs +2 -2
  29. package/dist/wsmtxca.d.ts +3 -3
  30. package/dist/wsmtxca.mjs +2 -2
  31. package/package.json +1 -1
  32. package/dist/chunk-4PSLYA2B.mjs.map +0 -1
  33. package/dist/chunk-C2KX652X.mjs.map +0 -1
  34. package/dist/chunk-WEEV76IC.mjs.map +0 -1
  35. /package/dist/{chunk-FQD2PZXE.mjs.map → chunk-67ISWKGE.mjs.map} +0 -0
  36. /package/dist/{chunk-QHNI4ZIA.mjs.map → chunk-FSJHE4VS.mjs.map} +0 -0
  37. /package/dist/{chunk-HSGTCGUX.mjs.map → chunk-GQGYO2Z2.mjs.map} +0 -0
@@ -7,10 +7,10 @@ import {
7
7
  ARCA_RECEIVER_CONDITION_IDS,
8
8
  ARCA_VAT_RATES,
9
9
  ARCA_VOUCHER_TYPES
10
- } from "./chunk-C2KX652X.mjs";
10
+ } from "./chunk-5CGIRDZY.mjs";
11
11
  import {
12
12
  createPadronService
13
- } from "./chunk-QHNI4ZIA.mjs";
13
+ } from "./chunk-FSJHE4VS.mjs";
14
14
  import {
15
15
  arcaMinorUnitsToNumber,
16
16
  assertArcaMinorUnits,
@@ -20,12 +20,19 @@ import {
20
20
  normalizeWsfeDateInput,
21
21
  normalizeWsfeVoucherInput,
22
22
  roundHalfEvenRatio,
23
+ serializeArcaAmount,
23
24
  serializeArcaExchangeRate,
24
25
  toIsoDate
25
- } from "./chunk-FQD2PZXE.mjs";
26
+ } from "./chunk-67ISWKGE.mjs";
26
27
  import {
27
28
  createWsmtxcaService
28
- } from "./chunk-HSGTCGUX.mjs";
29
+ } from "./chunk-GQGYO2Z2.mjs";
30
+ import {
31
+ classifyArcaAuthenticationError,
32
+ classifyArcaAuthenticationIssues,
33
+ createArcaAuthenticationEvidence,
34
+ executeWithAuthenticationRecovery
35
+ } from "./chunk-7MNVZSW6.mjs";
29
36
  import {
30
37
  ArcaConfigurationError,
31
38
  ArcaError,
@@ -155,6 +162,16 @@ var ARCA_SERVICE_CONFIG = {
155
162
  soapActionBase: "http://ar.gov.afip.dif.FEV1/",
156
163
  useLegacyTlsSecurityLevel0: true
157
164
  },
165
+ wsfex: {
166
+ namespace: "http://ar.gov.afip.dif.fexv1/",
167
+ endpoint: {
168
+ production: "https://servicios1.afip.gov.ar/wsfexv1/service.asmx",
169
+ test: "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx"
170
+ },
171
+ soapVersion: "1.1",
172
+ soapActionBase: "http://ar.gov.afip.dif.fexv1/",
173
+ useLegacyTlsSecurityLevel0: true
174
+ },
158
175
  wsmtxca: {
159
176
  namespace: "http://impl.service.wsmtxca.afip.gov.ar/service/",
160
177
  endpoint: {
@@ -303,7 +320,16 @@ function voucherFamily(type) {
303
320
  });
304
321
  }
305
322
  var KINDS = ["invoice", "debit_note", "credit_note"];
323
+ var EXPORT_TYPES = [19, 20, 21];
306
324
  function describeVoucherType(voucherType) {
325
+ const exportIndex = EXPORT_TYPES.indexOf(voucherType);
326
+ if (exportIndex !== -1) {
327
+ return {
328
+ family: "export",
329
+ voucherClass: "E",
330
+ kind: KINDS[exportIndex]
331
+ };
332
+ }
307
333
  const { family, voucherClass, types } = voucherFamily(voucherType);
308
334
  return {
309
335
  family,
@@ -699,20 +725,17 @@ function receiverDocument(document) {
699
725
  invalid("document.type", "an ARCA document type");
700
726
  }
701
727
  const number = digits(document.number, "document.number", 0, 20);
702
- if (document.type === 99 || number === 0) {
703
- return {};
704
- }
705
728
  return { tipoDocRec: document.type, nroDocRec: number };
706
729
  }
707
730
  function digits(value, field, min, max) {
708
- const text = String(value).trim();
709
- if (!/^\d*$/.test(text) || text.length < min || text.length > max) {
731
+ const text2 = String(value).trim();
732
+ if (!/^\d*$/.test(text2) || text2.length < min || text2.length > max) {
710
733
  invalid(field, min === max ? `${min} digits` : `${min} to ${max} digits`);
711
734
  }
712
- if (text === "") {
735
+ if (text2 === "") {
713
736
  return 0;
714
737
  }
715
- const parsed = Number(text);
738
+ const parsed = Number(text2);
716
739
  if (!Number.isSafeInteger(parsed)) {
717
740
  invalid(field, "a safe integer");
718
741
  }
@@ -1175,10 +1198,10 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
1175
1198
  const headerTotal = Number(
1176
1199
  normalizeArcaAmountToMinorUnits(data.netAmount, "net") + normalizeArcaAmountToMinorUnits(data.vatAmount, "vat") + normalizeArcaAmountToMinorUnits(data.exemptAmount, "exempt") + normalizeArcaAmountToMinorUnits(data.nonTaxableAmount, "untaxed") + normalizeArcaAmountToMinorUnits(data.taxAmount, "tax")
1177
1200
  );
1178
- const reviewedTotal = input.amounts ? input.total : void 0;
1179
- data.totalAmount = reviewedTotal === void 0 ? headerTotal / 100 : minor(reviewedTotal, "total");
1201
+ const reviewedTotal2 = input.amounts ? input.total : void 0;
1202
+ data.totalAmount = reviewedTotal2 === void 0 ? headerTotal / 100 : minor(reviewedTotal2, "total");
1180
1203
  amounts.computedTotal += headerTotal - amounts.sentTotal;
1181
- amounts.sentTotal = reviewedTotal ?? headerTotal;
1204
+ amounts.sentTotal = reviewedTotal2 ?? headerTotal;
1182
1205
  if (receiver.receiverVatConditionId === 5 && receiver.documentType === ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL) {
1183
1206
  const [whole, fraction = ""] = currency.exchangeRate.split(".");
1184
1207
  const rate = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
@@ -1290,11 +1313,11 @@ function issueDocumentNumber(value, field, min, max) {
1290
1313
  if (typeof value !== "number" && typeof value !== "string") {
1291
1314
  invalid2(field, `a positive document number with ${min} to ${max} digits`);
1292
1315
  }
1293
- const text = String(value);
1294
- if (!/^\d+$/.test(text) || text.length < min || text.length > max || !Number.isSafeInteger(Number(text)) || Number(text) <= 0) {
1316
+ const text2 = String(value);
1317
+ if (!/^\d+$/.test(text2) || text2.length < min || text2.length > max || !Number.isSafeInteger(Number(text2)) || Number(text2) <= 0) {
1295
1318
  invalid2(field, `a positive document number with ${min} to ${max} digits`);
1296
1319
  }
1297
- return Number(text);
1320
+ return Number(text2);
1298
1321
  }
1299
1322
  function deriveCurrency(input) {
1300
1323
  const currency = input.currency === void 0 ? "ARS" : input.currency;
@@ -1407,6 +1430,16 @@ function voucherDateWindow(input) {
1407
1430
  if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
1408
1431
  invalid2("now", "a valid Date");
1409
1432
  }
1433
+ if (EXPORT_TYPES.includes(input.voucherType)) {
1434
+ if (input.service !== void 0) {
1435
+ invalid2("service", "absent: export vouchers always use WSFEX");
1436
+ }
1437
+ const { from: from2, to: to2 } = exportDateWindow(
1438
+ concept === "services",
1439
+ buenosAiresDate(now)
1440
+ );
1441
+ return { from: isoDay(from2), to: isoDay(to2) };
1442
+ }
1410
1443
  voucherFamily(input.voucherType);
1411
1444
  const { from, to } = dateWindow(
1412
1445
  { concept: CONCEPT_IDS[concept], voucherType: input.voucherType },
@@ -1434,18 +1467,32 @@ function dateWindow(data, service, today) {
1434
1467
  function assertVoucherDateWindow(data, service, today) {
1435
1468
  const { from, to } = dateWindow(data, service, today);
1436
1469
  if (data.voucherDate < from || data.voucherDate > to) {
1437
- const window = { from: isoDay(from), to: isoDay(to) };
1438
- throw new ArcaInputError(
1439
- `date must be from ${window.from} through ${window.to}, the window ARCA accepts on ${isoDay(today)}.`,
1440
- {
1441
- code: "ARCA_INPUT_DATE_OUTSIDE_WINDOW",
1442
- field: "date",
1443
- expected: `a date from ${window.from} through ${window.to}`,
1444
- window
1445
- }
1446
- );
1470
+ throwOutsideWindow(from, to, today);
1471
+ }
1472
+ }
1473
+ function assertExportDateWindow(voucherDate, services, today) {
1474
+ const { from, to } = exportDateWindow(services, today);
1475
+ if (voucherDate < from || voucherDate > to) {
1476
+ throwOutsideWindow(from, to, today);
1447
1477
  }
1448
1478
  }
1479
+ function exportDateWindow(services, today) {
1480
+ const from = addDays(today, -5);
1481
+ const to = addDays(today, 5);
1482
+ return { from, to: services ? minDate(to, endOfMonth(today)) : to };
1483
+ }
1484
+ function throwOutsideWindow(from, to, today) {
1485
+ const window = { from: isoDay(from), to: isoDay(to) };
1486
+ throw new ArcaInputError(
1487
+ `date must be from ${window.from} through ${window.to}, the window ARCA accepts on ${isoDay(today)}.`,
1488
+ {
1489
+ code: "ARCA_INPUT_DATE_OUTSIDE_WINDOW",
1490
+ field: "date",
1491
+ expected: `a date from ${window.from} through ${window.to}`,
1492
+ window
1493
+ }
1494
+ );
1495
+ }
1449
1496
  function isoDay(date) {
1450
1497
  return toIsoDate(date) ?? date;
1451
1498
  }
@@ -1546,74 +1593,1138 @@ function deriveNumericReceiver(to) {
1546
1593
  };
1547
1594
  }
1548
1595
 
1549
- // src/store/types.ts
1550
- import { createHash } from "crypto";
1551
- function attemptKey(environment, taxId, key) {
1552
- return `arca:v1:attempt:${environment}:${taxId}:${key}`;
1596
+ // src/services/wsfex.ts
1597
+ function createWsfexService(options) {
1598
+ async function executeWsfexRawOperation(operation, input, body = {}, authFields = {}) {
1599
+ const auth2 = await options.auth.login("wsfex", {
1600
+ representedTaxId: input.representedTaxId,
1601
+ forceRefresh: input.forceRefresh,
1602
+ abortSignal: input.abortSignal
1603
+ });
1604
+ const response = await options.soap.execute({
1605
+ service: "wsfex",
1606
+ operation,
1607
+ signal: input.abortSignal,
1608
+ body: {
1609
+ Auth: {
1610
+ ...createWsfexAuth(
1611
+ input.representedTaxId ?? options.config.taxId,
1612
+ auth2.token,
1613
+ auth2.sign
1614
+ ),
1615
+ ...authFields
1616
+ },
1617
+ ...body
1618
+ }
1619
+ });
1620
+ return unwrapWsfexOperationEnvelope(operation, response.result);
1621
+ }
1622
+ function executeWsfexOperation(operation, input, body = {}, authFields = {}) {
1623
+ return executeWithAuthenticationRecovery({
1624
+ service: "wsfex",
1625
+ operation,
1626
+ forceRefresh: input.forceRefresh,
1627
+ async execute(forceRefresh) {
1628
+ const result = await executeWsfexRawOperation(
1629
+ operation,
1630
+ {
1631
+ representedTaxId: input.representedTaxId,
1632
+ forceRefresh,
1633
+ abortSignal: input.abortSignal
1634
+ },
1635
+ body,
1636
+ authFields
1637
+ );
1638
+ throwForWsfexOperationError(operation, result);
1639
+ return result;
1640
+ }
1641
+ });
1642
+ }
1643
+ async function getWsfexResultRows(operation, rowKey, input) {
1644
+ const result = await executeWsfexOperation(operation, {
1645
+ representedTaxId: input.representedTaxId,
1646
+ forceRefresh: input.forceRefresh
1647
+ });
1648
+ return getWsfexRows(result.FEXResultGet, rowKey);
1649
+ }
1650
+ async function getWsfexCatalog(operation, rowKey, fields, input) {
1651
+ const rows2 = await getWsfexResultRows(operation, rowKey, input);
1652
+ return rows2.flatMap((row) => {
1653
+ const id = readWsfexText(row[fields.id]);
1654
+ return id === void 0 ? [] : [{ id, description: readWsfexText(row[fields.description]) ?? "" }];
1655
+ });
1656
+ }
1657
+ function issue(input) {
1658
+ const request = createWsfexRequest(input);
1659
+ return executeWsfexAuthorization(input, request);
1660
+ }
1661
+ async function executeWsfexAuthorization(input, request) {
1662
+ try {
1663
+ const result = await executeWsfexRawOperation(
1664
+ "FEXAuthorize",
1665
+ {
1666
+ representedTaxId: input.representedTaxId,
1667
+ forceRefresh: input.forceRefresh,
1668
+ abortSignal: input.abortSignal
1669
+ },
1670
+ { Cmp: request }
1671
+ );
1672
+ return classifyWsfexAuthorization(result);
1673
+ } catch (error) {
1674
+ return createWsfexIndeterminateOutcome(error);
1675
+ }
1676
+ }
1677
+ function lookupVoucher2(input) {
1678
+ return executeWithAuthenticationRecovery({
1679
+ service: "wsfex",
1680
+ operation: "FEXGetCMP",
1681
+ forceRefresh: input.forceRefresh,
1682
+ execute: (forceRefresh) => lookupVoucherOnce({ ...input, forceRefresh })
1683
+ });
1684
+ }
1685
+ async function lookupVoucherOnce(input) {
1686
+ const operation = "FEXGetCMP";
1687
+ const result = await executeWsfexRawOperation(operation, input, {
1688
+ // ClsFEXGetCMP spells it `Cbte_tipo`; the manual's `Cbte_Tipo` would
1689
+ // be dropped by the server and consult type 0.
1690
+ Cmp: {
1691
+ Cbte_tipo: input.voucherType,
1692
+ Punto_vta: input.salesPoint,
1693
+ Cbte_nro: input.number
1694
+ }
1695
+ });
1696
+ const errors = extractWsfexErrors(result, operation);
1697
+ if (errors.length > 0 && errors.every((issue2) => issue2.code === "1020")) {
1698
+ return {
1699
+ kind: "not_found",
1700
+ service: "wsfex",
1701
+ operation,
1702
+ errors,
1703
+ observations: [],
1704
+ raw: result
1705
+ };
1706
+ }
1707
+ if (errors.length > 0) {
1708
+ throw createWsfexServiceError(operation, errors);
1709
+ }
1710
+ const raw = toWsfexRecord(result.FEXResultGet);
1711
+ if (!raw) {
1712
+ throw new ArcaServiceError("WSFEX did not return the consulted voucher", {
1713
+ service: "wsfex",
1714
+ operation
1715
+ });
1716
+ }
1717
+ return {
1718
+ kind: "found",
1719
+ service: "wsfex",
1720
+ operation,
1721
+ voucher: mapWsfexVoucherInfo(raw),
1722
+ observations: extractWsfexReasons(
1723
+ raw.Motivos_Obs,
1724
+ operation,
1725
+ "observation"
1726
+ ),
1727
+ raw: result
1728
+ };
1729
+ }
1730
+ return {
1731
+ issue,
1732
+ async getLastRequestId(input = {}) {
1733
+ const operation = "FEXGetLast_ID";
1734
+ const result = await executeWsfexOperation(operation, input);
1735
+ return parseWsfexInteger(toWsfexRecord(result.FEXResultGet)?.Id, {
1736
+ operation,
1737
+ field: "Id",
1738
+ min: 0,
1739
+ max: WSFEX_MAX_REQUEST_ID
1740
+ });
1741
+ },
1742
+ async getLastVoucherNumber({ salesPoint, voucherType, ...input }) {
1743
+ const operation = "FEXGetLast_CMP";
1744
+ const result = await executeWsfexOperation(
1745
+ operation,
1746
+ input,
1747
+ {},
1748
+ { Pto_venta: salesPoint, Cbte_Tipo: voucherType }
1749
+ );
1750
+ return parseWsfexInteger(
1751
+ toWsfexRecord(result.FEXResult_LastCMP)?.Cbte_nro,
1752
+ { operation, field: "Cbte_nro", min: 0, max: 99999999 }
1753
+ );
1754
+ },
1755
+ lookupVoucher: lookupVoucher2,
1756
+ async getSalesPoints(input = {}) {
1757
+ const rows2 = await getWsfexResultRows(
1758
+ "FEXGetPARAM_PtoVenta",
1759
+ "ClsFEXResponse_PtoVenta",
1760
+ input
1761
+ );
1762
+ return rows2.map(mapWsfexSalesPoint);
1763
+ },
1764
+ getCountries(input = {}) {
1765
+ return getWsfexCatalog(
1766
+ "FEXGetPARAM_DST_pais",
1767
+ "ClsFEXResponse_DST_pais",
1768
+ { id: "DST_Codigo", description: "DST_Ds" },
1769
+ input
1770
+ );
1771
+ },
1772
+ async getCountryTaxIds(input = {}) {
1773
+ const rows2 = await getWsfexResultRows(
1774
+ "FEXGetPARAM_DST_CUIT",
1775
+ "ClsFEXResponse_DST_cuit",
1776
+ input
1777
+ );
1778
+ return rows2.flatMap((row) => {
1779
+ const taxId = readWsfexText(row.DST_CUIT);
1780
+ return taxId === void 0 ? [] : [{ taxId, description: readWsfexText(row.DST_Ds) ?? "" }];
1781
+ });
1782
+ },
1783
+ getCurrencies(input = {}) {
1784
+ return getWsfexCatalog(
1785
+ "FEXGetPARAM_MON",
1786
+ "ClsFEXResponse_Mon",
1787
+ { id: "Mon_Id", description: "Mon_Ds" },
1788
+ input
1789
+ );
1790
+ },
1791
+ getIncoterms(input = {}) {
1792
+ return getWsfexCatalog(
1793
+ "FEXGetPARAM_Incoterms",
1794
+ "ClsFEXResponse_Inc",
1795
+ { id: "Inc_Id", description: "Inc_Ds" },
1796
+ input
1797
+ );
1798
+ },
1799
+ getUnits(input = {}) {
1800
+ return getWsfexCatalog(
1801
+ "FEXGetPARAM_UMed",
1802
+ "ClsFEXResponse_UMed",
1803
+ { id: "Umed_Id", description: "Umed_Ds" },
1804
+ input
1805
+ );
1806
+ },
1807
+ getLanguages(input = {}) {
1808
+ return getWsfexCatalog(
1809
+ "FEXGetPARAM_Idiomas",
1810
+ "ClsFEXResponse_Idi",
1811
+ { id: "Idi_Id", description: "Idi_Ds" },
1812
+ input
1813
+ );
1814
+ },
1815
+ getVoucherTypes(input = {}) {
1816
+ return getWsfexCatalog(
1817
+ "FEXGetPARAM_Cbte_Tipo",
1818
+ "ClsFEXResponse_Cbte_Tipo",
1819
+ { id: "Cbte_Id", description: "Cbte_Ds" },
1820
+ input
1821
+ );
1822
+ },
1823
+ getExportTypes(input = {}) {
1824
+ return getWsfexCatalog(
1825
+ "FEXGetPARAM_Tipo_Expo",
1826
+ "ClsFEXResponse_Tex",
1827
+ { id: "Tex_Id", description: "Tex_Ds" },
1828
+ input
1829
+ );
1830
+ },
1831
+ async getActivities(input = {}) {
1832
+ const rows2 = await getWsfexResultRows(
1833
+ "FEXGetPARAM_Actividades",
1834
+ "ClsFEXResponse_ActividadTipo",
1835
+ input
1836
+ );
1837
+ return rows2.flatMap((row) => {
1838
+ const id = toWsfexActivityId(row.Id);
1839
+ return id === void 0 ? [] : [{ id, description: readWsfexText(row.Desc) ?? "" }];
1840
+ });
1841
+ },
1842
+ async getExchangeRate({ currencyId, date, ...input }) {
1843
+ const operation = "FEXGetPARAM_Ctz";
1844
+ const quotationDate = date === void 0 ? void 0 : toIsoDate(normalizeWsfexDate(date, "date"));
1845
+ const result = await executeWsfexOperation(operation, input, {
1846
+ Mon_id: currencyId,
1847
+ ...optionalWsfexField("FchCotiz", quotationDate)
1848
+ });
1849
+ const raw = toWsfexRecord(result.FEXResultGet);
1850
+ const rate = canonicalizeWsfexDecimal(raw?.Mon_ctz);
1851
+ const rateDate = toIsoDate(readWsfexText(raw?.Mon_fecha));
1852
+ if (rate === void 0 || rateDate === void 0) {
1853
+ throw new ArcaInvalidSoapResponseError("Invalid WSFEX exchange rate", {
1854
+ service: "wsfex",
1855
+ operation
1856
+ });
1857
+ }
1858
+ return { currencyId, rate, date: rateDate };
1859
+ },
1860
+ async checkPermit({ id, destination, ...input }) {
1861
+ const operation = "FEXCheck_Permiso";
1862
+ const result = await executeWsfexOperation(operation, input, {
1863
+ ID_Permiso: id,
1864
+ Dst_merc: destination
1865
+ });
1866
+ const status = readWsfexText(
1867
+ toWsfexRecord(result.FEXResultGet)?.Status
1868
+ )?.toUpperCase();
1869
+ if (status === "OK" || status === "NO") {
1870
+ return status === "OK";
1871
+ }
1872
+ throw new ArcaInvalidSoapResponseError("Invalid WSFEX permit status", {
1873
+ service: "wsfex",
1874
+ operation
1875
+ });
1876
+ }
1877
+ };
1553
1878
  }
1554
- function sequenceKey(environment, taxId, salesPoint, voucherType) {
1555
- return `arca:v1:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
1879
+ var WSFEX_MAX_REQUEST_ID = 999999999999999;
1880
+ var WSFEX_VOUCHER_TYPES = [19, 20, 21];
1881
+ var WSFEX_EXPORT_TYPES = [1, 2, 4];
1882
+ var WSFEX_LANGUAGES = [1, 2, 3];
1883
+ var WSFEX_ITEM_DECIMAL_PATTERN = /^(\d{1,12})(?:\.(\d{1,6}))?$/;
1884
+ var WSFEX_TAX_ID_PATTERN = /^\d{11}$/;
1885
+ function assertWsfexVoucherInput(voucher) {
1886
+ createWsfexRequest({ ...voucher, id: 0, number: 1 });
1556
1887
  }
1557
- function sequenceLockKey(environment, taxId, salesPoint, voucherType) {
1558
- return `arca:v1:lock:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
1888
+ function createWsfexRequest(input) {
1889
+ return {
1890
+ Id: assertWsfexInteger(input.id, "id", 0, WSFEX_MAX_REQUEST_ID),
1891
+ ...optionalWsfexField(
1892
+ "Fecha_cbte",
1893
+ normalizeOptionalWsfexDate(input.voucherDate, "voucherDate")
1894
+ ),
1895
+ Cbte_Tipo: assertWsfexCode(
1896
+ input.voucherType,
1897
+ "voucherType",
1898
+ WSFEX_VOUCHER_TYPES
1899
+ ),
1900
+ Punto_vta: assertWsfexInteger(input.salesPoint, "salesPoint", 1, 99999),
1901
+ Cbte_nro: assertWsfexInteger(input.number, "number", 1, 99999999),
1902
+ Tipo_expo: assertWsfexCode(
1903
+ input.exportType,
1904
+ "exportType",
1905
+ WSFEX_EXPORT_TYPES
1906
+ ),
1907
+ // ARCA expects the element present and empty for notes and services.
1908
+ Permiso_existente: assertOptionalWsfexFlag(input.permitExists, "permitExists") ?? "",
1909
+ ...optionalWsfexField(
1910
+ "Permisos",
1911
+ createWsfexList(input.permits, "Permiso", createWsfexPermit)
1912
+ ),
1913
+ Dst_cmp: assertWsfexInteger(input.destination, "destination", 1, 999),
1914
+ Cliente: requireWsfexText(input.receiverName, "receiverName"),
1915
+ ...optionalWsfexField(
1916
+ "Cuit_pais_cliente",
1917
+ assertOptionalWsfexTaxId(
1918
+ input.receiverCountryTaxId,
1919
+ "receiverCountryTaxId"
1920
+ )
1921
+ ),
1922
+ Domicilio_cliente: requireWsfexText(
1923
+ input.receiverAddress,
1924
+ "receiverAddress"
1925
+ ),
1926
+ ...optionalWsfexField(
1927
+ "Id_impositivo",
1928
+ optionalWsfexText(input.receiverTaxId, "receiverTaxId")
1929
+ ),
1930
+ Moneda_Id: requireWsfexText(input.currencyId, "currencyId"),
1931
+ ...optionalWsfexField(
1932
+ "Moneda_ctz",
1933
+ input.exchangeRate === void 0 ? void 0 : serializeArcaExchangeRate(input.exchangeRate, "exchangeRate")
1934
+ ),
1935
+ ...optionalWsfexField(
1936
+ "CanMisMonExt",
1937
+ assertOptionalWsfexFlag(
1938
+ input.sameCurrencyForeignCancellation,
1939
+ "sameCurrencyForeignCancellation"
1940
+ )
1941
+ ),
1942
+ ...optionalWsfexField(
1943
+ "Obs_comerciales",
1944
+ optionalWsfexText(input.commercialObservations, "commercialObservations")
1945
+ ),
1946
+ Imp_total: serializeArcaAmount(input.totalAmount, "totalAmount"),
1947
+ ...optionalWsfexField(
1948
+ "Obs",
1949
+ optionalWsfexText(input.observations, "observations")
1950
+ ),
1951
+ ...optionalWsfexField(
1952
+ "Cmps_asoc",
1953
+ createWsfexList(
1954
+ input.associatedVouchers,
1955
+ "Cmp_asoc",
1956
+ createWsfexAssociatedVoucher
1957
+ )
1958
+ ),
1959
+ ...optionalWsfexField(
1960
+ "Forma_pago",
1961
+ optionalWsfexText(input.paymentTerms, "paymentTerms")
1962
+ ),
1963
+ ...optionalWsfexField(
1964
+ "Incoterms",
1965
+ optionalWsfexText(input.incoterms, "incoterms")
1966
+ ),
1967
+ ...optionalWsfexField(
1968
+ "Incoterms_Ds",
1969
+ optionalWsfexText(input.incotermsDetail, "incotermsDetail")
1970
+ ),
1971
+ Idioma_cbte: assertWsfexCode(input.language, "language", WSFEX_LANGUAGES),
1972
+ Items: createWsfexItems(input.items),
1973
+ ...optionalWsfexField(
1974
+ "Fecha_pago",
1975
+ normalizeOptionalWsfexDate(input.paymentDate, "paymentDate")
1976
+ ),
1977
+ ...optionalWsfexField(
1978
+ "Actividades",
1979
+ createWsfexList(input.activities, "Actividad", createWsfexActivity)
1980
+ )
1981
+ };
1559
1982
  }
1560
- function settledKey(environment, taxId, key) {
1561
- return `arca:v1:settled:${environment}:${taxId}:${key}`;
1983
+ function createWsfexList(rows2, key, map) {
1984
+ if (rows2 === void 0 || rows2.length === 0) {
1985
+ return void 0;
1986
+ }
1987
+ return { [key]: rows2.map(map) };
1562
1988
  }
1563
- function canonicalHash(input) {
1564
- return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
1989
+ function createWsfexPermit(permit, index) {
1990
+ return {
1991
+ Id_permiso: requireWsfexText(permit.id, `permits[${index}].id`),
1992
+ Dst_merc: assertWsfexInteger(
1993
+ permit.destination,
1994
+ `permits[${index}].destination`,
1995
+ 1,
1996
+ 999
1997
+ )
1998
+ };
1565
1999
  }
1566
- function canonical(value) {
1567
- if (Array.isArray(value)) {
1568
- return value.map(canonical);
2000
+ function createWsfexAssociatedVoucher(voucher, index) {
2001
+ const field = `associatedVouchers[${index}]`;
2002
+ return {
2003
+ Cbte_tipo: assertWsfexInteger(
2004
+ voucher.voucherType,
2005
+ `${field}.voucherType`,
2006
+ 1,
2007
+ 999
2008
+ ),
2009
+ Cbte_punto_vta: assertWsfexInteger(
2010
+ voucher.salesPoint,
2011
+ `${field}.salesPoint`,
2012
+ 1,
2013
+ 99999
2014
+ ),
2015
+ Cbte_nro: assertWsfexInteger(
2016
+ voucher.number,
2017
+ `${field}.number`,
2018
+ 1,
2019
+ 999999999
2020
+ ),
2021
+ ...optionalWsfexField(
2022
+ "Cbte_cuit",
2023
+ assertOptionalWsfexTaxId(voucher.issuerTaxId, `${field}.issuerTaxId`)
2024
+ )
2025
+ };
2026
+ }
2027
+ function createWsfexItems(items) {
2028
+ if (!Array.isArray(items) || items.length === 0) {
2029
+ throw new ArcaInputError("items must contain at least one item.", {
2030
+ code: "ARCA_INPUT_MISSING_FIELD",
2031
+ field: "items",
2032
+ expected: "from 1 to 9999 items"
2033
+ });
1569
2034
  }
1570
- if (value instanceof Date) {
1571
- return value.toJSON();
2035
+ return { Item: items.map(createWsfexItem) };
2036
+ }
2037
+ function createWsfexItem(item, index) {
2038
+ const field = `items[${index}]`;
2039
+ return {
2040
+ ...optionalWsfexField(
2041
+ "Pro_codigo",
2042
+ optionalWsfexText(item.code, `${field}.code`)
2043
+ ),
2044
+ Pro_ds: requireWsfexText(item.description, `${field}.description`),
2045
+ ...optionalWsfexField(
2046
+ "Pro_qty",
2047
+ serializeWsfexItemDecimal(item.quantity, `${field}.quantity`)
2048
+ ),
2049
+ Pro_umed: assertWsfexInteger(item.unit, `${field}.unit`, 0, 99),
2050
+ ...optionalWsfexField(
2051
+ "Pro_precio_uni",
2052
+ serializeWsfexItemDecimal(item.unitPrice, `${field}.unitPrice`)
2053
+ ),
2054
+ ...optionalWsfexField(
2055
+ "Pro_bonificacion",
2056
+ serializeWsfexItemDecimal(item.discount, `${field}.discount`)
2057
+ ),
2058
+ Pro_total_item: serializeWsfexSignedAmount(item.amount, `${field}.amount`)
2059
+ };
2060
+ }
2061
+ function createWsfexActivity(activity, index) {
2062
+ const field = `activities[${index}].id`;
2063
+ if (typeof activity.id !== "string" || !/^\d{1,6}$/.test(activity.id)) {
2064
+ throw new ArcaInputError(`${field} must be an activity code.`, {
2065
+ code: "ARCA_INPUT_INVALID_VALUE",
2066
+ field,
2067
+ expected: "a code of up to 6 digits from getActivities()"
2068
+ });
1572
2069
  }
1573
- if (value !== null && typeof value === "object") {
1574
- return Object.fromEntries(
1575
- Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
2070
+ return { Id: Number.parseInt(activity.id, 10) };
2071
+ }
2072
+ function serializeWsfexSignedAmount(value, field) {
2073
+ return typeof value === "number" && value < 0 ? `-${serializeArcaAmount(-value, field)}` : serializeArcaAmount(value, field);
2074
+ }
2075
+ function serializeWsfexItemDecimal(value, field) {
2076
+ if (value === void 0) {
2077
+ return void 0;
2078
+ }
2079
+ const text2 = typeof value === "number" && Number.isFinite(value) ? String(value) : value;
2080
+ const match = typeof text2 === "string" ? WSFEX_ITEM_DECIMAL_PATTERN.exec(text2.trim()) : null;
2081
+ if (!match) {
2082
+ throw new ArcaInputError(
2083
+ `${field} must be a non-negative decimal with at most 12 integer and 6 fractional digits.`,
2084
+ {
2085
+ code: "ARCA_INPUT_INVALID_AMOUNT",
2086
+ field,
2087
+ expected: "a non-negative decimal with up to 12 integer and 6 fractional digits"
2088
+ }
1576
2089
  );
1577
2090
  }
1578
- return value;
2091
+ return formatWsfexDecimal(match[1] ?? "0", match[2]);
1579
2092
  }
1580
- async function storeCall(fn) {
1581
- try {
1582
- return await fn();
1583
- } catch (cause) {
1584
- throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
2093
+ function formatWsfexDecimal(integerPart, fractionPart = "") {
2094
+ const integer = integerPart.replace(/^0+(?=\d)/, "");
2095
+ const fraction = fractionPart.replace(/0+$/, "");
2096
+ return fraction ? `${integer}.${fraction}` : integer;
2097
+ }
2098
+ function canonicalizeWsfexDecimal(value) {
2099
+ const match = /^(\d+)(?:\.(\d+))?$/.exec(readWsfexText(value) ?? "");
2100
+ return match ? formatWsfexDecimal(match[1] ?? "0", match[2]) : void 0;
2101
+ }
2102
+ function optionalWsfexField(key, value) {
2103
+ return value === void 0 ? {} : { [key]: value };
2104
+ }
2105
+ function assertWsfexInteger(value, field, min, max) {
2106
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max) {
2107
+ return value;
1585
2108
  }
2109
+ return throwWsfexInvalidInput(
2110
+ value,
2111
+ field,
2112
+ `an integer from ${min} to ${max}`
2113
+ );
1586
2114
  }
1587
-
1588
- // src/services/wsfe-identity.ts
1589
- function matchWsfeVoucherIdentity(sent, number, found) {
1590
- let missing;
1591
- const compare = (field, expected, actual, normalize) => {
1592
- if (actual === void 0 || actual === null || expected === void 0) {
1593
- missing ??= field;
1594
- return void 0;
2115
+ function assertWsfexCode(value, field, allowed) {
2116
+ if (allowed.includes(value)) {
2117
+ return value;
2118
+ }
2119
+ return throwWsfexInvalidInput(value, field, `one of ${allowed.join(", ")}`);
2120
+ }
2121
+ function assertOptionalWsfexFlag(value, field) {
2122
+ if (value === void 0 || value === "S" || value === "N") {
2123
+ return value;
2124
+ }
2125
+ return throwWsfexInvalidInput(value, field, "S or N");
2126
+ }
2127
+ function assertOptionalWsfexTaxId(value, field) {
2128
+ if (value === void 0 || typeof value === "string" && WSFEX_TAX_ID_PATTERN.test(value)) {
2129
+ return value;
2130
+ }
2131
+ return throwWsfexInvalidInput(value, field, "an 11-digit CUIT string");
2132
+ }
2133
+ function requireWsfexText(value, field) {
2134
+ if (typeof value === "string" && value.trim() !== "") {
2135
+ return value;
2136
+ }
2137
+ return throwWsfexInvalidInput(value, field, "a non-empty string");
2138
+ }
2139
+ function optionalWsfexText(value, field) {
2140
+ if (value === void 0 || typeof value === "string") {
2141
+ return value;
2142
+ }
2143
+ return throwWsfexInvalidInput(value, field, "a string");
2144
+ }
2145
+ function throwWsfexInvalidInput(value, field, expected) {
2146
+ throw new ArcaInputError(
2147
+ value === void 0 ? `${field} is required.` : `${field} must be ${expected}.`,
2148
+ {
2149
+ code: value === void 0 ? "ARCA_INPUT_MISSING_FIELD" : "ARCA_INPUT_INVALID_VALUE",
2150
+ field,
2151
+ expected
1595
2152
  }
1596
- try {
1597
- const left = normalize ? normalize(expected) : expected;
1598
- const right = normalize ? normalize(actual) : actual;
1599
- if (left !== right) {
1600
- return {
1601
- matches: false,
1602
- evidence: "conflict",
1603
- reason: `${field} differs from the sent input`
1604
- };
1605
- }
1606
- } catch {
1607
- missing ??= field;
2153
+ );
2154
+ }
2155
+ function normalizeOptionalWsfexDate(value, field) {
2156
+ return value === void 0 ? void 0 : normalizeWsfexDate(value, field);
2157
+ }
2158
+ function normalizeWsfexDate(value, field) {
2159
+ const text2 = typeof value === "string" ? value.trim() : "";
2160
+ const [, year = "", month = "", day = ""] = /^(\d{4})(\d{2})(\d{2})$/.exec(text2) ?? /^(\d{4})-(\d{2})-(\d{2})$/.exec(text2) ?? [];
2161
+ const candidate = new Date(
2162
+ Date.UTC(Number(year), Number(month) - 1, Number(day))
2163
+ );
2164
+ if (year !== "" && candidate.getUTCFullYear() === Number(year) && candidate.getUTCMonth() === Number(month) - 1 && candidate.getUTCDate() === Number(day)) {
2165
+ return `${year}${month}${day}`;
2166
+ }
2167
+ throw new ArcaInputError(
2168
+ `Invalid WSFEX ${field}: expected an existing YYYYMMDD or YYYY-MM-DD date`,
2169
+ {
2170
+ code: "ARCA_INPUT_INVALID_DATE",
2171
+ field,
2172
+ expected: "an existing YYYYMMDD or YYYY-MM-DD calendar date"
1608
2173
  }
1609
- return void 0;
2174
+ );
2175
+ }
2176
+ function createWsfexAuth(representedTaxId, token, sign) {
2177
+ return {
2178
+ Token: token,
2179
+ Sign: sign,
2180
+ Cuit: Number.parseInt(String(representedTaxId), 10)
1610
2181
  };
1611
- const checks = [
1612
- ["voucherType", sent.voucherType, found.voucherType],
1613
- ["salesPoint", sent.salesPoint, found.salesPoint],
1614
- ["number", number, found.voucherNumber, normalizeVoucherNumber],
1615
- [
1616
- "date",
2182
+ }
2183
+ function unwrapWsfexOperationEnvelope(operation, response) {
2184
+ const operationResponse = response[`${operation}Response`];
2185
+ const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
2186
+ return result;
2187
+ }
2188
+ function throwForWsfexOperationError(operation, result) {
2189
+ const errors = extractWsfexErrors(result, operation);
2190
+ if (errors.length > 0) {
2191
+ throw createWsfexServiceError(operation, errors);
2192
+ }
2193
+ }
2194
+ function createWsfexServiceError(operation, issues) {
2195
+ const authenticationError = classifyArcaAuthenticationIssues(issues, {
2196
+ service: "wsfex",
2197
+ operation
2198
+ });
2199
+ if (authenticationError) {
2200
+ return authenticationError;
2201
+ }
2202
+ const firstIssue = issues[0];
2203
+ return new ArcaServiceError(
2204
+ firstIssue ? formatWsfexIssue(firstIssue) : "WSFEX returned a service error",
2205
+ {
2206
+ service: "wsfex",
2207
+ operation,
2208
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
2209
+ issues
2210
+ }
2211
+ );
2212
+ }
2213
+ function formatWsfexIssue(issue) {
2214
+ return issue.code ? `(${issue.code}) ${issue.message}` : issue.message;
2215
+ }
2216
+ function extractWsfexErrors(result, operation, resultLevel) {
2217
+ const error = toWsfexRecord(result.FEXErr);
2218
+ const code = readWsfexText(error?.ErrCode);
2219
+ if (code === void 0 || /^0+$/.test(code)) {
2220
+ return [];
2221
+ }
2222
+ return [
2223
+ {
2224
+ service: "wsfex",
2225
+ operation,
2226
+ source: "error",
2227
+ category: getWsfexErrorCategory(operation, code),
2228
+ code,
2229
+ message: readWsfexText(error?.ErrMsg) ?? readWsfexText(error?.Errmsg) ?? "Unknown WSFEX error",
2230
+ ...resultLevel === void 0 ? {} : { resultLevel }
2231
+ }
2232
+ ];
2233
+ }
2234
+ function getWsfexErrorCategory(operation, code) {
2235
+ if (operation !== "FEXAuthorize") {
2236
+ return "unknown";
2237
+ }
2238
+ return WSFEX_AUTHORIZATION_INFRASTRUCTURE_CODES.has(code) ? "infrastructure" : "business";
2239
+ }
2240
+ var WSFEX_AUTHORIZATION_INFRASTRUCTURE_CODES = /* @__PURE__ */ new Set([
2241
+ "500",
2242
+ "501",
2243
+ "502",
2244
+ "505",
2245
+ "1000",
2246
+ "1001"
2247
+ ]);
2248
+ function extractWsfexReasons(value, operation, category) {
2249
+ const text2 = readWsfexText(value);
2250
+ if (text2 === void 0) {
2251
+ return [];
2252
+ }
2253
+ const base = {
2254
+ service: "wsfex",
2255
+ operation,
2256
+ source: "observation",
2257
+ category,
2258
+ resultLevel: "header"
2259
+ };
2260
+ if (!/^\d+(?:\s*[,;|\s]\s*\d+)*$/.test(text2)) {
2261
+ return [{ ...base, message: text2 }];
2262
+ }
2263
+ return text2.split(/\D+/).filter(Boolean).map((token) => {
2264
+ const code = String(Number.parseInt(token, 10));
2265
+ return { ...base, code, message: code };
2266
+ });
2267
+ }
2268
+ function classifyWsfexAuthorization(result) {
2269
+ const operation = "FEXAuthorize";
2270
+ const authorization = toWsfexRecord(result.FEXResultAuth);
2271
+ const resultCode = normalizeWsfexResult(authorization?.Resultado);
2272
+ const echo = readWsfexEcho(authorization);
2273
+ const reprocessed = readWsfexReprocessed(authorization?.Reproceso);
2274
+ const base = {
2275
+ service: "wsfex",
2276
+ operation,
2277
+ results: resultCode === void 0 ? {} : { header: resultCode },
2278
+ errors: extractWsfexErrors(result, operation, "header"),
2279
+ observations: extractWsfexReasons(
2280
+ authorization?.Motivos_Obs,
2281
+ operation,
2282
+ resultCode === "R" ? "business" : "observation"
2283
+ ),
2284
+ raw: result
2285
+ };
2286
+ assignWsfexValue(base, "reprocessed", reprocessed);
2287
+ assignWsfexValue(base, "echo", echo);
2288
+ const context = {
2289
+ base,
2290
+ resultCode,
2291
+ cae: readWsfexText(authorization?.Cae),
2292
+ caeExpiry: readWsfexText(authorization?.Fch_venc_Cae),
2293
+ voucherNumber: echo?.number
2294
+ };
2295
+ return base.errors.length > 0 ? classifyWsfexErrorAnswer(context) : classifyWsfexResultAnswer(context);
2296
+ }
2297
+ function classifyWsfexErrorAnswer(context) {
2298
+ if (context.cae || context.resultCode === "A") {
2299
+ return createWsfexStructuredIndeterminate(
2300
+ context,
2301
+ "contradictory_response"
2302
+ );
2303
+ }
2304
+ const authenticationError = classifyArcaAuthenticationIssues(
2305
+ context.base.errors,
2306
+ { service: "wsfex", operation: context.base.operation }
2307
+ );
2308
+ if (authenticationError) {
2309
+ return {
2310
+ ...createWsfexStructuredIndeterminate(context, "authentication_rejected"),
2311
+ authentication: createArcaAuthenticationEvidence(authenticationError)
2312
+ };
2313
+ }
2314
+ if (context.base.errors.some((issue) => issue.category === "infrastructure")) {
2315
+ return createWsfexStructuredIndeterminate(context, "incomplete_response");
2316
+ }
2317
+ return {
2318
+ ...context.base,
2319
+ kind: "rejected",
2320
+ result: "R",
2321
+ resultLevel: "header"
2322
+ };
2323
+ }
2324
+ function classifyWsfexResultAnswer(context) {
2325
+ const { cae, caeExpiry, voucherNumber } = context;
2326
+ if (context.resultCode === "A") {
2327
+ return cae && caeExpiry && voucherNumber !== void 0 ? {
2328
+ ...context.base,
2329
+ kind: "authorized",
2330
+ result: "A",
2331
+ resultLevel: "header",
2332
+ cae,
2333
+ caeExpiry,
2334
+ voucherNumber
2335
+ } : createWsfexStructuredIndeterminate(context, "incomplete_response");
2336
+ }
2337
+ if (context.resultCode === "R") {
2338
+ return cae ? createWsfexStructuredIndeterminate(context, "contradictory_response") : {
2339
+ ...context.base,
2340
+ kind: "rejected",
2341
+ result: "R",
2342
+ resultLevel: "header"
2343
+ };
2344
+ }
2345
+ return createWsfexStructuredIndeterminate(context, "incomplete_response");
2346
+ }
2347
+ function createWsfexStructuredIndeterminate(context, reason) {
2348
+ const outcome = {
2349
+ ...context.base,
2350
+ kind: "indeterminate",
2351
+ reason
2352
+ };
2353
+ assignWsfexValue(outcome, "result", context.resultCode);
2354
+ if (context.resultCode !== void 0) {
2355
+ outcome.resultLevel = "header";
2356
+ }
2357
+ assignWsfexValue(outcome, "cae", context.cae);
2358
+ assignWsfexValue(outcome, "caeExpiry", context.caeExpiry);
2359
+ return outcome;
2360
+ }
2361
+ function createWsfexIndeterminateOutcome(error) {
2362
+ const operation = "FEXAuthorize";
2363
+ const authenticationError = classifyArcaAuthenticationError(error, {
2364
+ service: "wsfex",
2365
+ operation
2366
+ });
2367
+ return {
2368
+ kind: "indeterminate",
2369
+ service: "wsfex",
2370
+ operation,
2371
+ results: {},
2372
+ reason: authenticationError ? "authentication_rejected" : getWsfexIndeterminateReason(error),
2373
+ ...authenticationError ? {
2374
+ authentication: createArcaAuthenticationEvidence(authenticationError)
2375
+ } : {},
2376
+ errors: [],
2377
+ observations: []
2378
+ };
2379
+ }
2380
+ function getWsfexIndeterminateReason(error) {
2381
+ if (error instanceof ArcaTransportError) {
2382
+ return "transport_error";
2383
+ }
2384
+ if (error instanceof ArcaSoapFaultError) {
2385
+ return "soap_fault";
2386
+ }
2387
+ if (error instanceof ArcaInvalidSoapResponseError) {
2388
+ return "invalid_response";
2389
+ }
2390
+ return "unexpected_error";
2391
+ }
2392
+ function readWsfexEcho(authorization) {
2393
+ if (!authorization) {
2394
+ return void 0;
2395
+ }
2396
+ const echo = {};
2397
+ assignWsfexValue(
2398
+ echo,
2399
+ "id",
2400
+ readWsfexInteger(authorization.Id, 0, WSFEX_MAX_REQUEST_ID)
2401
+ );
2402
+ assignWsfexValue(
2403
+ echo,
2404
+ "salesPoint",
2405
+ readWsfexInteger(authorization.Punto_vta, 1, 99999)
2406
+ );
2407
+ assignWsfexValue(
2408
+ echo,
2409
+ "voucherType",
2410
+ readWsfexInteger(authorization.Cbte_tipo ?? authorization.Cbte_Tipo, 1, 999)
2411
+ );
2412
+ assignWsfexValue(
2413
+ echo,
2414
+ "number",
2415
+ readWsfexInteger(authorization.Cbte_nro, 1, 99999999)
2416
+ );
2417
+ assignWsfexValue(echo, "date", readWsfexText(authorization.Fch_cbte));
2418
+ return Object.keys(echo).length > 0 ? echo : void 0;
2419
+ }
2420
+ function readWsfexReprocessed(value) {
2421
+ const flag = normalizeWsfexResult(value);
2422
+ return flag === "S" || flag === "N" ? flag === "S" : void 0;
2423
+ }
2424
+ function mapWsfexVoucherInfo(raw) {
2425
+ const operation = "FEXGetCMP";
2426
+ const voucher = {
2427
+ voucherType: parseWsfexInteger(raw.Cbte_tipo ?? raw.Cbte_Tipo, {
2428
+ operation,
2429
+ field: "Cbte_tipo",
2430
+ min: 1,
2431
+ max: 999
2432
+ }),
2433
+ salesPoint: parseWsfexInteger(raw.Punto_vta, {
2434
+ operation,
2435
+ field: "Punto_vta",
2436
+ min: 1,
2437
+ max: 99999
2438
+ }),
2439
+ number: parseWsfexInteger(raw.Cbte_nro, {
2440
+ operation,
2441
+ field: "Cbte_nro",
2442
+ min: 1,
2443
+ max: 99999999
2444
+ }),
2445
+ destination: parseWsfexNumber(raw.Dst_cmp, operation, "Dst_cmp"),
2446
+ receiverName: readWsfexText(raw.Cliente) ?? "",
2447
+ receiverAddress: readWsfexText(raw.Domicilio_cliente) ?? "",
2448
+ currencyId: readWsfexText(raw.Moneda_Id) ?? "",
2449
+ totalAmount: parseWsfexNumber(raw.Imp_total, operation, "Imp_total"),
2450
+ items: getWsfexRows(raw.Items, "Item").map(mapWsfexLookupItem)
2451
+ };
2452
+ assignWsfexValue(
2453
+ voucher,
2454
+ "id",
2455
+ readWsfexInteger(raw.Id, 0, WSFEX_MAX_REQUEST_ID)
2456
+ );
2457
+ assignWsfexValue(voucher, "voucherDate", readWsfexText(raw.Fecha_cbte));
2458
+ assignWsfexValue(voucher, "exportType", readWsfexNumber(raw.Tipo_expo));
2459
+ assignWsfexValue(
2460
+ voucher,
2461
+ "permitExists",
2462
+ readWsfexFlag(raw.Permiso_existente)
2463
+ );
2464
+ assignWsfexValue(
2465
+ voucher,
2466
+ "permits",
2467
+ mapWsfexLookupList(raw.Permisos, "Permiso", mapWsfexLookupPermit)
2468
+ );
2469
+ assignWsfexValue(
2470
+ voucher,
2471
+ "receiverCountryTaxId",
2472
+ readWsfexTaxId(raw.Cuit_pais_cliente)
2473
+ );
2474
+ assignWsfexValue(voucher, "receiverTaxId", readWsfexText(raw.Id_impositivo));
2475
+ assignWsfexValue(
2476
+ voucher,
2477
+ "exchangeRate",
2478
+ canonicalizeWsfexDecimal(raw.Moneda_ctz)
2479
+ );
2480
+ assignWsfexValue(
2481
+ voucher,
2482
+ "sameCurrencyForeignCancellation",
2483
+ readWsfexFlag(raw.CanMisMonExt)
2484
+ );
2485
+ assignWsfexValue(
2486
+ voucher,
2487
+ "commercialObservations",
2488
+ readWsfexText(raw.Obs_comerciales)
2489
+ );
2490
+ assignWsfexValue(voucher, "observations", readWsfexText(raw.Obs));
2491
+ assignWsfexValue(
2492
+ voucher,
2493
+ "associatedVouchers",
2494
+ mapWsfexLookupList(
2495
+ raw.Cmps_asoc,
2496
+ "Cmp_asoc",
2497
+ mapWsfexLookupAssociatedVoucher
2498
+ )
2499
+ );
2500
+ assignWsfexValue(voucher, "paymentTerms", readWsfexText(raw.Forma_pago));
2501
+ assignWsfexValue(voucher, "incoterms", readWsfexText(raw.Incoterms));
2502
+ assignWsfexValue(voucher, "incotermsDetail", readWsfexText(raw.Incoterms_Ds));
2503
+ assignWsfexValue(voucher, "language", readWsfexNumber(raw.Idioma_cbte));
2504
+ assignWsfexValue(voucher, "paymentDate", readWsfexText(raw.Fecha_pago));
2505
+ assignWsfexValue(
2506
+ voucher,
2507
+ "activities",
2508
+ mapWsfexLookupList(raw.Actividades, "Actividad", (row) => {
2509
+ const id = toWsfexActivityId(row.Id);
2510
+ return id === void 0 ? void 0 : { id };
2511
+ })
2512
+ );
2513
+ assignWsfexValue(voucher, "result", normalizeWsfexResult(raw.Resultado));
2514
+ assignWsfexValue(voucher, "cae", readWsfexText(raw.Cae));
2515
+ assignWsfexValue(voucher, "caeExpiry", readWsfexText(raw.Fch_venc_Cae));
2516
+ assignWsfexValue(voucher, "authorizedAt", readWsfexText(raw.Fecha_cbte_cae));
2517
+ return voucher;
2518
+ }
2519
+ function mapWsfexLookupItem(row) {
2520
+ const operation = "FEXGetCMP";
2521
+ const code = readWsfexText(row.Pro_codigo);
2522
+ const quantity = canonicalizeWsfexDecimal(row.Pro_qty);
2523
+ const unitPrice = canonicalizeWsfexDecimal(row.Pro_precio_uni);
2524
+ const discount = canonicalizeWsfexDecimal(row.Pro_bonificacion);
2525
+ return {
2526
+ ...code === void 0 ? {} : { code },
2527
+ description: readWsfexText(row.Pro_ds) ?? "",
2528
+ ...quantity === void 0 ? {} : { quantity },
2529
+ unit: parseWsfexNumber(row.Pro_umed, operation, "Pro_umed"),
2530
+ ...unitPrice === void 0 ? {} : { unitPrice },
2531
+ ...discount === void 0 ? {} : { discount },
2532
+ amount: parseWsfexNumber(row.Pro_total_item, operation, "Pro_total_item")
2533
+ };
2534
+ }
2535
+ function mapWsfexLookupPermit(row) {
2536
+ const id = readWsfexText(row.Id_permiso);
2537
+ const destination = readWsfexNumber(row.Dst_merc);
2538
+ return id === void 0 || destination === void 0 ? void 0 : { id, destination };
2539
+ }
2540
+ function mapWsfexLookupAssociatedVoucher(row) {
2541
+ const voucherType = readWsfexNumber(row.Cbte_tipo ?? row.Cbte_Tipo);
2542
+ const salesPoint = readWsfexNumber(row.Cbte_punto_vta);
2543
+ const number = readWsfexNumber(row.Cbte_nro);
2544
+ if (voucherType === void 0 || salesPoint === void 0 || number === void 0) {
2545
+ return void 0;
2546
+ }
2547
+ const issuerTaxId2 = readWsfexTaxId(row.Cbte_cuit);
2548
+ return {
2549
+ voucherType,
2550
+ salesPoint,
2551
+ number,
2552
+ ...issuerTaxId2 === void 0 ? {} : { issuerTaxId: issuerTaxId2 }
2553
+ };
2554
+ }
2555
+ function mapWsfexLookupList(container, key, map) {
2556
+ const rows2 = getWsfexRows(container, key);
2557
+ if (rows2.length === 0) {
2558
+ return void 0;
2559
+ }
2560
+ const result = [];
2561
+ for (const row of rows2) {
2562
+ const mapped = map(row);
2563
+ if (mapped === void 0) {
2564
+ return void 0;
2565
+ }
2566
+ result.push(mapped);
2567
+ }
2568
+ return result;
2569
+ }
2570
+ function mapWsfexSalesPoint(row) {
2571
+ const deactivatedAt = toIsoDate(readWsfexText(row.Pve_FchBaja));
2572
+ return {
2573
+ number: parseWsfexInteger(row.Pve_Nro, {
2574
+ operation: "FEXGetPARAM_PtoVenta",
2575
+ field: "Pve_Nro",
2576
+ min: 1,
2577
+ max: 99999
2578
+ }),
2579
+ blocked: normalizeWsfexResult(row.Pve_Bloqueado) === "S",
2580
+ ...deactivatedAt === void 0 ? {} : { deactivatedAt }
2581
+ };
2582
+ }
2583
+ function toWsfexActivityId(value) {
2584
+ const text2 = readWsfexText(value);
2585
+ return text2 !== void 0 && /^\d+$/.test(text2) && Number(text2) !== 0 ? text2.padStart(6, "0") : void 0;
2586
+ }
2587
+ function readWsfexTaxId(value) {
2588
+ const text2 = readWsfexText(value);
2589
+ return text2 !== void 0 && /^\d+$/.test(text2) && Number(text2) !== 0 ? text2 : void 0;
2590
+ }
2591
+ function readWsfexFlag(value) {
2592
+ const flag = normalizeWsfexResult(value);
2593
+ return flag === "S" || flag === "N" ? flag : void 0;
2594
+ }
2595
+ function getWsfexRows(container, key) {
2596
+ const value = toWsfexRecord(container)?.[key];
2597
+ if (value === void 0) {
2598
+ return [];
2599
+ }
2600
+ return (Array.isArray(value) ? value : [value]).flatMap((row) => {
2601
+ const record = toWsfexRecord(row);
2602
+ return record ? [record] : [];
2603
+ });
2604
+ }
2605
+ function toWsfexRecord(value) {
2606
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2607
+ return void 0;
2608
+ }
2609
+ const record = value;
2610
+ return Object.keys(record).some((key) => !key.startsWith("@_")) ? record : void 0;
2611
+ }
2612
+ function readWsfexText(value) {
2613
+ if (typeof value !== "string" && typeof value !== "number") {
2614
+ return void 0;
2615
+ }
2616
+ const text2 = String(value).trim();
2617
+ return text2 || void 0;
2618
+ }
2619
+ function readWsfexNumber(value) {
2620
+ const text2 = readWsfexText(value);
2621
+ return text2 !== void 0 && /^-?\d+(?:\.\d+)?$/.test(text2) ? Number(text2) : void 0;
2622
+ }
2623
+ function readWsfexInteger(value, min, max) {
2624
+ const text2 = readWsfexText(value);
2625
+ if (text2 === void 0 || !/^\d+$/.test(text2)) {
2626
+ return void 0;
2627
+ }
2628
+ const parsed = Number(text2);
2629
+ return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : void 0;
2630
+ }
2631
+ function parseWsfexInteger(value, options) {
2632
+ const parsed = readWsfexInteger(value, options.min, options.max);
2633
+ if (parsed === void 0) {
2634
+ throw new ArcaInvalidSoapResponseError(`Invalid WSFEX ${options.field}`, {
2635
+ service: "wsfex",
2636
+ operation: options.operation
2637
+ });
2638
+ }
2639
+ return parsed;
2640
+ }
2641
+ function parseWsfexNumber(value, operation, field) {
2642
+ const parsed = readWsfexNumber(value);
2643
+ if (parsed === void 0) {
2644
+ throw new ArcaInvalidSoapResponseError(`Invalid WSFEX ${field}`, {
2645
+ service: "wsfex",
2646
+ operation
2647
+ });
2648
+ }
2649
+ return parsed;
2650
+ }
2651
+ function normalizeWsfexResult(value) {
2652
+ return readWsfexText(value)?.toUpperCase();
2653
+ }
2654
+ function assignWsfexValue(target, key, value) {
2655
+ if (value !== void 0) {
2656
+ target[key] = value;
2657
+ }
2658
+ }
2659
+
2660
+ // src/store/types.ts
2661
+ import { createHash } from "crypto";
2662
+ function attemptKey(environment, taxId, key) {
2663
+ return `arca:v1:attempt:${environment}:${taxId}:${key}`;
2664
+ }
2665
+ function sequenceKey(environment, taxId, salesPoint, voucherType) {
2666
+ return `arca:v1:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
2667
+ }
2668
+ function sequenceLockKey(environment, taxId, salesPoint, voucherType) {
2669
+ return `arca:v1:lock:sequence:${environment}:${taxId}:${salesPoint}:${voucherType}`;
2670
+ }
2671
+ function settledKey(environment, taxId, key) {
2672
+ return `arca:v1:settled:${environment}:${taxId}:${key}`;
2673
+ }
2674
+ function canonicalHash(input) {
2675
+ return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
2676
+ }
2677
+ function canonical(value) {
2678
+ if (Array.isArray(value)) {
2679
+ return value.map(canonical);
2680
+ }
2681
+ if (value instanceof Date) {
2682
+ return value.toJSON();
2683
+ }
2684
+ if (value !== null && typeof value === "object") {
2685
+ return Object.fromEntries(
2686
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
2687
+ );
2688
+ }
2689
+ return value;
2690
+ }
2691
+ async function storeCall(fn) {
2692
+ try {
2693
+ return await fn();
2694
+ } catch (cause) {
2695
+ throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
2696
+ }
2697
+ }
2698
+
2699
+ // src/services/wsfe-identity.ts
2700
+ function matchWsfeVoucherIdentity(sent, number, found) {
2701
+ let missing;
2702
+ const compare = (field, expected, actual, normalize) => {
2703
+ if (actual === void 0 || actual === null || expected === void 0) {
2704
+ missing ??= field;
2705
+ return void 0;
2706
+ }
2707
+ try {
2708
+ const left = normalize ? normalize(expected) : expected;
2709
+ const right = normalize ? normalize(actual) : actual;
2710
+ if (left !== right) {
2711
+ return {
2712
+ matches: false,
2713
+ evidence: "conflict",
2714
+ reason: `${field} differs from the sent input`
2715
+ };
2716
+ }
2717
+ } catch {
2718
+ missing ??= field;
2719
+ }
2720
+ return void 0;
2721
+ };
2722
+ const checks = [
2723
+ ["voucherType", sent.voucherType, found.voucherType],
2724
+ ["salesPoint", sent.salesPoint, found.salesPoint],
2725
+ ["number", number, found.voucherNumber, normalizeVoucherNumber],
2726
+ [
2727
+ "date",
1617
2728
  sent.voucherDate,
1618
2729
  found.voucherDate,
1619
2730
  (value) => normalizeWsfeDateInput(value, "date")
@@ -1719,11 +2830,11 @@ function normalizeVoucherNumber(value) {
1719
2830
  return value;
1720
2831
  }
1721
2832
  function normalizeDocument(value) {
1722
- const text = String(value);
1723
- if (!/^\d+$/.test(text)) {
2833
+ const text2 = String(value);
2834
+ if (!/^\d+$/.test(text2)) {
1724
2835
  throw new Error("Invalid document number");
1725
2836
  }
1726
- return BigInt(text);
2837
+ return BigInt(text2);
1727
2838
  }
1728
2839
  function compareVatRates(expected, actual) {
1729
2840
  if (actual === void 0) {
@@ -2145,19 +3256,19 @@ async function postXmlOnce({
2145
3256
  });
2146
3257
  }
2147
3258
  return await new Promise((resolve, reject) => {
2148
- let settled = false;
3259
+ let settled2 = false;
2149
3260
  const settleResolve = (response) => {
2150
- if (settled) {
3261
+ if (settled2) {
2151
3262
  return;
2152
3263
  }
2153
- settled = true;
3264
+ settled2 = true;
2154
3265
  resolve(response);
2155
3266
  };
2156
3267
  const settleReject = (error) => {
2157
- if (settled) {
3268
+ if (settled2) {
2158
3269
  return;
2159
3270
  }
2160
- settled = true;
3271
+ settled2 = true;
2161
3272
  reject(error);
2162
3273
  };
2163
3274
  const request = https.request(
@@ -2805,25 +3916,1343 @@ function createArcaLogger(config) {
2805
3916
  }
2806
3917
  };
2807
3918
  }
2808
- function resolveArcaLogLevel(level) {
2809
- if (isArcaLogLevel(level)) {
2810
- return level;
3919
+ function resolveArcaLogLevel(level) {
3920
+ if (isArcaLogLevel(level)) {
3921
+ return level;
3922
+ }
3923
+ const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
3924
+ if (isArcaLogLevel(envLevel)) {
3925
+ return envLevel;
3926
+ }
3927
+ return "warn";
3928
+ }
3929
+ function shouldLog(threshold, messageLevel) {
3930
+ return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
3931
+ }
3932
+ function isArcaLogLevel(value) {
3933
+ return ARCA_LOG_LEVELS.includes(value);
3934
+ }
3935
+ function defaultArcaLog(level, message, ...args) {
3936
+ const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
3937
+ method(message, ...args);
3938
+ }
3939
+
3940
+ // src/store/lock.ts
3941
+ import { randomUUID } from "crypto";
3942
+ var ARCA_LEASE_MS = 6e4;
3943
+ var RENEW_MS = 2e4;
3944
+ var POLL_MS = 50;
3945
+ var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
3946
+ async function withLease(driver, fn, { signal } = {}) {
3947
+ const owner = randomUUID();
3948
+ const deadline = Date.now() + MAX_WAIT_MS;
3949
+ throwIfAborted(signal);
3950
+ let held = await driver.acquire(owner);
3951
+ while (!held) {
3952
+ if (Date.now() >= deadline) {
3953
+ throw new ArcaLockTimeoutError(
3954
+ "ARCA store lock stayed held; no work was attempted.",
3955
+ { reason: "held" }
3956
+ );
3957
+ }
3958
+ await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS), signal);
3959
+ throwIfAborted(signal);
3960
+ held = await driver.acquire(owner);
3961
+ }
3962
+ if (signal?.aborted) {
3963
+ await driver.release(owner).catch(() => void 0);
3964
+ throwIfAborted(signal);
3965
+ }
3966
+ const renewal = setInterval(() => {
3967
+ driver.renew(owner).catch(() => void 0);
3968
+ }, RENEW_MS);
3969
+ renewal.unref?.();
3970
+ try {
3971
+ return await fn();
3972
+ } finally {
3973
+ clearInterval(renewal);
3974
+ await driver.release(owner).catch(() => void 0);
3975
+ }
3976
+ }
3977
+ function throwIfAborted(signal) {
3978
+ if (signal?.aborted) {
3979
+ throw new ArcaLockTimeoutError(
3980
+ "Stopped waiting for an ARCA store lock: the caller's signal aborted; no work was attempted.",
3981
+ { reason: "aborted", cause: signal.reason }
3982
+ );
3983
+ }
3984
+ }
3985
+ function delay2(ms, signal) {
3986
+ if (signal?.aborted) {
3987
+ return Promise.resolve();
3988
+ }
3989
+ return new Promise((resolve) => {
3990
+ const wake = () => {
3991
+ clearTimeout(timer);
3992
+ resolve();
3993
+ };
3994
+ const timer = setTimeout(() => {
3995
+ signal?.removeEventListener("abort", wake);
3996
+ resolve();
3997
+ }, ms);
3998
+ timer.unref?.();
3999
+ signal?.addEventListener("abort", wake, { once: true });
4000
+ });
4001
+ }
4002
+
4003
+ // src/services/issuance-wsfex.ts
4004
+ var LANGUAGES = { es: 1, en: 2, pt: 3 };
4005
+ var EXPORT_KINDS = { goods: 1, services: 2, other: 4 };
4006
+ var UNPRICED_UNITS = [0, 97, 99];
4007
+ function isExportVoucherType(voucherType) {
4008
+ return typeof voucherType === "number" && EXPORT_TYPES.includes(voucherType);
4009
+ }
4010
+ function isExportInput(input) {
4011
+ return input !== null && typeof input === "object" && "export" in input;
4012
+ }
4013
+ function isExportNoteInput(input) {
4014
+ if (input === null || typeof input !== "object" || !("for" in input)) {
4015
+ return false;
4016
+ }
4017
+ const target = input.for;
4018
+ return target !== null && typeof target === "object" && !Array.isArray(target) && isExportVoucherType(target.voucherType);
4019
+ }
4020
+ function deriveExportInvoice(input, now = /* @__PURE__ */ new Date()) {
4021
+ assertIssueObject(input, "input");
4022
+ assertIssueKeys(
4023
+ input,
4024
+ [
4025
+ "salesPoint",
4026
+ "to",
4027
+ "export",
4028
+ "items",
4029
+ "total",
4030
+ "date",
4031
+ "currency",
4032
+ "exchangeRate",
4033
+ "paidInForeignCurrency",
4034
+ "paymentTerms",
4035
+ "language",
4036
+ "observations",
4037
+ "commercialObservations"
4038
+ ],
4039
+ "input"
4040
+ );
4041
+ assertSalesPoint2(input.salesPoint, "salesPoint");
4042
+ const receiver = deriveReceiver2(input.to);
4043
+ const exported = deriveExport(input.export);
4044
+ const voucherDate = normalizeWsfeDateInput(
4045
+ input.date ?? buenosAiresDate(now),
4046
+ "date"
4047
+ );
4048
+ if (exported.paymentDate !== void 0 && exported.paymentDate < voucherDate) {
4049
+ invalid3("export.paymentDate", "on or after the voucher date");
4050
+ }
4051
+ const currency = deriveCurrency2(input.currency, input.exchangeRate);
4052
+ if (input.paidInForeignCurrency !== void 0) {
4053
+ if (typeof input.paidInForeignCurrency !== "boolean") {
4054
+ invalid3("paidInForeignCurrency", "a boolean");
4055
+ }
4056
+ if (currency.currencyId === ARCA_CURRENCY_IDS.ARS) {
4057
+ invalid3("paidInForeignCurrency", "absent for an invoice in pesos");
4058
+ }
4059
+ }
4060
+ const { items, total } = deriveItems(input.items, "items");
4061
+ const reviewed = reviewedTotal(input.total, total);
4062
+ const data = {
4063
+ voucherType: ARCA_VOUCHER_TYPES.FACTURA_E,
4064
+ salesPoint: input.salesPoint,
4065
+ voucherDate,
4066
+ exportType: exported.exportType,
4067
+ ...exported.permitExists === void 0 ? {} : { permitExists: exported.permitExists },
4068
+ ...exported.permits === void 0 ? {} : { permits: exported.permits },
4069
+ ...receiver,
4070
+ ...currency,
4071
+ ...input.paidInForeignCurrency === void 0 ? {} : {
4072
+ sameCurrencyForeignCancellation: input.paidInForeignCurrency ? "S" : "N"
4073
+ },
4074
+ ...optionalText(
4075
+ input.commercialObservations,
4076
+ "commercialObservations",
4077
+ 4e3
4078
+ ),
4079
+ totalAmount: reviewed / 100,
4080
+ ...optionalText(input.observations, "observations", 1e3),
4081
+ paymentTerms: text(input.paymentTerms, "paymentTerms", 50),
4082
+ ...exported.incoterms === void 0 ? {} : { incoterms: exported.incoterms },
4083
+ ...exported.incotermsDetail === void 0 ? {} : { incotermsDetail: exported.incotermsDetail },
4084
+ language: deriveLanguage(input.language),
4085
+ items,
4086
+ ...exported.paymentDate === void 0 ? {} : { paymentDate: exported.paymentDate }
4087
+ };
4088
+ assertWsfexVoucherInput(data);
4089
+ return {
4090
+ data,
4091
+ amounts: { computedTotal: total, sentTotal: reviewed, vatAdjustment: 0 }
4092
+ };
4093
+ }
4094
+ function deriveExportNote(original, input, kind, now = /* @__PURE__ */ new Date()) {
4095
+ assertIssueKeys(
4096
+ input,
4097
+ [
4098
+ "for",
4099
+ "salesPoint",
4100
+ "date",
4101
+ "exchangeRate",
4102
+ "observations",
4103
+ "commercialObservations",
4104
+ "items",
4105
+ ...kind === "creditNote" ? ["all"] : []
4106
+ ],
4107
+ "input",
4108
+ kind === "creditNote" ? "issueCreditNote()" : "issueDebitNote()"
4109
+ );
4110
+ const exportType = noteExportType(original, kind);
4111
+ const salesPoint = input.salesPoint ?? original.salesPoint;
4112
+ assertSalesPoint2(salesPoint, "salesPoint");
4113
+ const voucherDate = normalizeWsfeDateInput(
4114
+ input.date ?? buenosAiresDate(now),
4115
+ "date"
4116
+ );
4117
+ if (exportType === 2 && original.voucherDate !== void 0 && voucherDate < original.voucherDate) {
4118
+ invalid3("date", "on or after the original's date for services");
4119
+ }
4120
+ const exchangeRate2 = noteRate(original, input.exchangeRate, exportType);
4121
+ const lines = noteLines(original, input);
4122
+ const data = {
4123
+ voucherType: kind === "creditNote" ? ARCA_VOUCHER_TYPES.NOTA_CREDITO_E : ARCA_VOUCHER_TYPES.NOTA_DEBITO_E,
4124
+ salesPoint,
4125
+ voucherDate,
4126
+ exportType,
4127
+ ...inheritedReceiver(original),
4128
+ currencyId: original.currencyId,
4129
+ ...exchangeRate2 === void 0 ? {} : { exchangeRate: exchangeRate2 },
4130
+ ...optionalText(
4131
+ input.commercialObservations,
4132
+ "commercialObservations",
4133
+ 4e3
4134
+ ),
4135
+ totalAmount: lines.total / 100,
4136
+ ...optionalText(input.observations, "observations", 1e3),
4137
+ associatedVouchers: [
4138
+ {
4139
+ voucherType: original.voucherType,
4140
+ salesPoint: original.salesPoint,
4141
+ number: original.number
4142
+ }
4143
+ ],
4144
+ ...original.incoterms === void 0 ? {} : { incoterms: original.incoterms },
4145
+ language: original.language === 2 || original.language === 3 ? original.language : 1,
4146
+ items: lines.items
4147
+ };
4148
+ assertWsfexVoucherInput(data);
4149
+ return {
4150
+ data,
4151
+ amounts: {
4152
+ computedTotal: lines.total,
4153
+ sentTotal: lines.total,
4154
+ vatAdjustment: 0
4155
+ }
4156
+ };
4157
+ }
4158
+ function noteExportType(original, kind) {
4159
+ if (original.result !== void 0 && original.result !== "A") {
4160
+ invalid3("for", "an authorized voucher");
4161
+ }
4162
+ const allowed = kind === "creditNote" ? [ARCA_VOUCHER_TYPES.FACTURA_E, ARCA_VOUCHER_TYPES.NOTA_DEBITO_E] : [ARCA_VOUCHER_TYPES.FACTURA_E];
4163
+ if (!allowed.includes(original.voucherType)) {
4164
+ invalid3(
4165
+ "for.voucherType",
4166
+ kind === "creditNote" ? "19 or 20" : "19, a Factura E"
4167
+ );
4168
+ }
4169
+ const exportType = original.exportType;
4170
+ if (exportType !== 1 && exportType !== 2 && exportType !== 4) {
4171
+ throw new ArcaConfigurationError(
4172
+ "The original voucher reports no export type."
4173
+ );
4174
+ }
4175
+ return exportType;
4176
+ }
4177
+ function noteRate(original, rate, exportType) {
4178
+ if (rate === void 0) {
4179
+ return original.exchangeRate;
4180
+ }
4181
+ if (exportType === 2) {
4182
+ invalid3("exchangeRate", "absent: services notes keep the original's");
4183
+ }
4184
+ return deriveCurrency2(
4185
+ original.currencyId === ARCA_CURRENCY_IDS.ARS ? "ARS" : { id: original.currencyId },
4186
+ rate
4187
+ ).exchangeRate;
4188
+ }
4189
+ function noteLines(original, input) {
4190
+ const all = "all" in input ? input.all : void 0;
4191
+ if (input.items === void 0 === (all === void 0)) {
4192
+ throw new ArcaInputError(
4193
+ "issueCreditNote needs exactly one mode: items for a partial note, or all: true for the whole original.",
4194
+ {
4195
+ code: "ARCA_INPUT_INVALID_VALUE",
4196
+ field: input.items === void 0 ? "items" : "all",
4197
+ expected: "exactly one of items or all: true"
4198
+ }
4199
+ );
4200
+ }
4201
+ if (all === void 0) {
4202
+ return deriveItems(input.items, "items");
4203
+ }
4204
+ if (all !== true) {
4205
+ invalid3("all", "true");
4206
+ }
4207
+ if (original.items.length === 0) {
4208
+ throw new ArcaInputError(
4209
+ "ARCA reports no lines for the original, so all: true has nothing to credit; list the items instead.",
4210
+ {
4211
+ code: "ARCA_INPUT_INVALID_VALUE",
4212
+ field: "all",
4213
+ expected: "items, since the original reports no lines"
4214
+ }
4215
+ );
4216
+ }
4217
+ return { items: original.items, total: originalTotal(original) };
4218
+ }
4219
+ function inheritedReceiver(original) {
4220
+ return {
4221
+ destination: original.destination,
4222
+ receiverName: original.receiverName,
4223
+ ...original.receiverCountryTaxId === void 0 ? {} : { receiverCountryTaxId: original.receiverCountryTaxId },
4224
+ receiverAddress: original.receiverAddress,
4225
+ ...original.receiverTaxId === void 0 ? {} : { receiverTaxId: original.receiverTaxId }
4226
+ };
4227
+ }
4228
+ function assertExportWindow(data, now = /* @__PURE__ */ new Date()) {
4229
+ assertExportDateWindow(
4230
+ data.voucherDate ?? buenosAiresDate(now),
4231
+ data.exportType === 2,
4232
+ buenosAiresDate(now)
4233
+ );
4234
+ }
4235
+ function exportPreview(prepared, originals) {
4236
+ return {
4237
+ voucherClass: "E",
4238
+ voucherType: prepared.data.voucherType,
4239
+ date: isoDate(prepared.data.voucherDate),
4240
+ header: exportHeader(prepared.data),
4241
+ amounts: { ...prepared.amounts },
4242
+ request: structuredClone(prepared.data),
4243
+ service: "wsfex",
4244
+ ...originals === void 0 ? {} : { originals }
4245
+ };
4246
+ }
4247
+ function createExportIssuance(wsfex, context) {
4248
+ return {
4249
+ issue: (prepare, operation, hashInput, options) => issueExport(wsfex, context, prepare, operation, hashInput, options),
4250
+ async original(coordinates, options) {
4251
+ const result = await wsfex.lookupVoucher({
4252
+ ...auth(options),
4253
+ ...coordinates
4254
+ });
4255
+ return result.kind === "found" ? result.voucher : null;
4256
+ },
4257
+ async lookup(coordinates, options) {
4258
+ const result = await wsfex.lookupVoucher({
4259
+ ...auth(options),
4260
+ ...coordinates
4261
+ });
4262
+ return result.kind === "found" ? exportSummary(result.voucher) : null;
4263
+ },
4264
+ lastAuthorized: (sequence, options) => wsfex.getLastVoucherNumber({ ...auth(options), ...sequence }),
4265
+ recover: (key, json, options) => recoverExport(wsfex, context, key, json, options)
4266
+ };
4267
+ }
4268
+ async function recoverExport(wsfex, context, key, json, options) {
4269
+ const first = readExportRecord(json);
4270
+ const issuer = first.representedTaxId ?? context.taxId;
4271
+ if (options.representedTaxId !== void 0 && String(options.representedTaxId) !== issuer) {
4272
+ throw new ArcaInputError(
4273
+ "Reservation belongs to another represented taxpayer",
4274
+ { code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH" }
4275
+ );
4276
+ }
4277
+ const consulted = {
4278
+ ...options,
4279
+ ...first.representedTaxId === void 0 ? {} : { representedTaxId: first.representedTaxId }
4280
+ };
4281
+ return await exportLock(
4282
+ context,
4283
+ issuer,
4284
+ options
4285
+ )(async () => {
4286
+ const store = context.store;
4287
+ const latest = store ? await storeCall(
4288
+ () => store.get(attemptKey(context.environment, context.taxId, key))
4289
+ ) : null;
4290
+ const record = latest === null ? first : readExportRecord(latest);
4291
+ const outcome = await consultReservation(wsfex, record, consulted, issuer);
4292
+ return {
4293
+ ...outcome,
4294
+ service: "wsfex",
4295
+ ...options.include?.request ? {
4296
+ request: {
4297
+ ...structuredClone(record.sent),
4298
+ id: record.requestId,
4299
+ number: record.number
4300
+ }
4301
+ } : {}
4302
+ };
4303
+ });
4304
+ }
4305
+ async function consultReservation(wsfex, record, options, issuer) {
4306
+ const attempted = {
4307
+ salesPoint: record.salesPoint,
4308
+ voucherType: record.voucherType,
4309
+ number: record.number
4310
+ };
4311
+ const attempt = {
4312
+ service: "wsfex",
4313
+ operation: "FEXAuthorize",
4314
+ kind: "indeterminate",
4315
+ reason: "incomplete_response",
4316
+ results: {},
4317
+ errors: [],
4318
+ observations: []
4319
+ };
4320
+ const total = Math.round(record.sent.totalAmount * 100);
4321
+ const amounts = { computedTotal: total, sentTotal: total, vatAdjustment: 0 };
4322
+ let found;
4323
+ try {
4324
+ const result = await wsfex.lookupVoucher({
4325
+ ...auth(options),
4326
+ ...attempted
4327
+ });
4328
+ found = result.kind === "found" ? result.voucher : null;
4329
+ } catch (error) {
4330
+ return {
4331
+ kind: "indeterminate",
4332
+ attempted,
4333
+ attempt,
4334
+ lookup: options.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
4335
+ };
4336
+ }
4337
+ return settled(
4338
+ attempt,
4339
+ attempt,
4340
+ found,
4341
+ record.requestId,
4342
+ attempted,
4343
+ record.sent,
4344
+ (authorization) => issuedVoucher(
4345
+ record.sent,
4346
+ attempted,
4347
+ record.requestId,
4348
+ authorization,
4349
+ amounts,
4350
+ issuer
4351
+ )
4352
+ );
4353
+ }
4354
+ function exportLock(context, issuer, options) {
4355
+ const key = `arca:v1:lock:wsfex:${context.environment}:${issuer}`;
4356
+ return (fn) => context.store?.withLock ? context.store.withLock(key, fn, { signal: options.abortSignal }) : withLocalLock(key, fn, options.abortSignal);
4357
+ }
4358
+ function auth(options) {
4359
+ return {
4360
+ ...options.representedTaxId === void 0 ? {} : { representedTaxId: options.representedTaxId },
4361
+ ...options.forceRefresh === void 0 ? {} : { forceRefresh: options.forceRefresh },
4362
+ ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
4363
+ };
4364
+ }
4365
+ async function issueExport(wsfex, context, prepare, operation, hashInput, options) {
4366
+ if (options.service !== void 0) {
4367
+ invalid3("options.service", "absent: export vouchers always use WSFEX");
4368
+ }
4369
+ if (options.number !== void 0) {
4370
+ invalid3("options.number", "absent: WSFEX numbers are read under a lock");
4371
+ }
4372
+ const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
4373
+ const issuer = representedTaxId ?? context.taxId;
4374
+ const lock = exportLock(context, issuer, options);
4375
+ const idempotencyKey = options.idempotencyKey;
4376
+ if (idempotencyKey === void 0 || !context.store) {
4377
+ const prepared2 = await prepare();
4378
+ assertExportWindow(prepared2.data);
4379
+ return lock(() => fresh(prepared2));
4380
+ }
4381
+ const store = context.store;
4382
+ const recordKey = attemptKey(
4383
+ context.environment,
4384
+ context.taxId,
4385
+ idempotencyKey
4386
+ );
4387
+ const inputHash = canonicalHash({
4388
+ input: hashInput,
4389
+ representedTaxId,
4390
+ service: "wsfex"
4391
+ });
4392
+ const existing = await storeCall(() => store.get(recordKey));
4393
+ if (existing !== null) {
4394
+ return lock(
4395
+ async () => replay(await storeCall(() => store.get(recordKey)) ?? existing)
4396
+ );
4397
+ }
4398
+ const prepared = await prepare();
4399
+ assertExportWindow(prepared.data);
4400
+ return lock(async () => {
4401
+ const prior = await storeCall(() => store.get(recordKey));
4402
+ return prior === null ? fresh(prepared) : replay(prior);
4403
+ });
4404
+ async function fresh(prepared2) {
4405
+ const { data } = prepared2;
4406
+ const number = await wsfex.getLastVoucherNumber({
4407
+ ...auth(options),
4408
+ salesPoint: data.salesPoint,
4409
+ voucherType: data.voucherType
4410
+ }) + 1;
4411
+ const id = await nextRequestId();
4412
+ if (idempotencyKey === void 0 || !context.store) {
4413
+ return submit({ id, number, data, fresh: true });
4414
+ }
4415
+ const record = {
4416
+ v: 2,
4417
+ operation,
4418
+ service: "wsfex",
4419
+ ...representedTaxId === void 0 ? {} : { representedTaxId },
4420
+ salesPoint: data.salesPoint,
4421
+ voucherType: data.voucherType,
4422
+ number,
4423
+ requestId: id,
4424
+ inputHash,
4425
+ sent: data,
4426
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
4427
+ };
4428
+ const store2 = context.store;
4429
+ if (await storeCall(() => store2.add(recordKey, JSON.stringify(record)))) {
4430
+ return submit({ id, number, data, fresh: true, record });
4431
+ }
4432
+ const winner = await storeCall(() => store2.get(recordKey));
4433
+ if (winner === null) {
4434
+ throw new ArcaConfigurationError(
4435
+ "ARCA reservation disappeared after atomic creation lost."
4436
+ );
4437
+ }
4438
+ return replay(winner);
4439
+ }
4440
+ function replay(json) {
4441
+ const stored = readExportRecord(json);
4442
+ if (stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
4443
+ throw new ArcaInputError(
4444
+ "The idempotency key was already used with different input or operation.",
4445
+ {
4446
+ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
4447
+ field: "options.idempotencyKey"
4448
+ }
4449
+ );
4450
+ }
4451
+ return submit({
4452
+ id: stored.requestId,
4453
+ number: stored.number,
4454
+ data: stored.sent,
4455
+ fresh: false,
4456
+ record: stored
4457
+ });
4458
+ }
4459
+ async function submit(claim) {
4460
+ const first = await attemptOnce(claim, claim.id, claim.fresh);
4461
+ if (first !== "collided") {
4462
+ return first;
4463
+ }
4464
+ const id = await nextRequestId();
4465
+ await moveRecord(claim.record, id);
4466
+ const second = await attemptOnce(claim, id, true);
4467
+ if (second === "collided") {
4468
+ throw new ArcaConfigurationError("WSFEX answered a reused id twice.");
4469
+ }
4470
+ return second;
4471
+ }
4472
+ async function attemptOnce(claim, id, mayCollide) {
4473
+ const { number, data } = claim;
4474
+ const attempted = {
4475
+ salesPoint: data.salesPoint,
4476
+ voucherType: data.voucherType,
4477
+ number
4478
+ };
4479
+ const finish = (outcome2) => ({
4480
+ ...outcome2,
4481
+ service: "wsfex",
4482
+ ...options.include?.request ? { request: { ...structuredClone(data), id, number } } : {}
4483
+ });
4484
+ const outcome = await send(data, id, number);
4485
+ const attempt = evidence(outcome);
4486
+ const direct = answered(outcome, attempt, id, attempted);
4487
+ if (direct !== void 0) {
4488
+ return finish(
4489
+ direct.kind === "authorized" ? { ...direct, voucher: issued(data, attempted, id, direct.voucher) } : direct
4490
+ );
4491
+ }
4492
+ const consulted = await consult(outcome, attempt, attempted);
4493
+ if ("outcome" in consulted) {
4494
+ return finish(consulted.outcome);
4495
+ }
4496
+ const { found } = consulted;
4497
+ if (mayCollide && outcome.kind === "authorized" && outcome.reprocessed === true && found === null) {
4498
+ return "collided";
4499
+ }
4500
+ return finish(
4501
+ settled(
4502
+ outcome,
4503
+ attempt,
4504
+ found,
4505
+ id,
4506
+ attempted,
4507
+ data,
4508
+ (voucher) => issued(data, attempted, id, voucher)
4509
+ )
4510
+ );
4511
+ }
4512
+ async function consult(outcome, attempt, attempted) {
4513
+ const aborted = () => ({
4514
+ outcome: {
4515
+ kind: "indeterminate",
4516
+ attempted,
4517
+ attempt,
4518
+ lookup: { kind: "aborted" }
4519
+ }
4520
+ });
4521
+ if (options.abortSignal?.aborted && outcome.kind !== "authorized") {
4522
+ return aborted();
4523
+ }
4524
+ try {
4525
+ const result = await wsfex.lookupVoucher({
4526
+ ...auth(options),
4527
+ ...attempted
4528
+ });
4529
+ return { found: result.kind === "found" ? result.voucher : null };
4530
+ } catch (error) {
4531
+ return options.abortSignal?.aborted ? aborted() : {
4532
+ outcome: {
4533
+ kind: "indeterminate",
4534
+ attempted,
4535
+ attempt,
4536
+ lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
4537
+ }
4538
+ };
4539
+ }
4540
+ }
4541
+ async function nextRequestId() {
4542
+ const received = await wsfex.getLastRequestId(auth(options));
4543
+ const key = requestIdKey(context.environment, issuer);
4544
+ const store2 = context.store;
4545
+ const issued2 = store2 ? readRequestIdMarker(await storeCall(() => store2.get(key))) : localRequestIds.get(key) ?? 0;
4546
+ const id = Math.max(received, issued2) + 1;
4547
+ if (id > MAX_REQUEST_ID) {
4548
+ throw new ArcaConfigurationError("WSFEX request ids are exhausted.");
4549
+ }
4550
+ if (store2) {
4551
+ const marker = { v: 1, id };
4552
+ await storeCall(() => store2.set(key, JSON.stringify(marker)));
4553
+ } else {
4554
+ localRequestIds.set(key, id);
4555
+ }
4556
+ return id;
4557
+ }
4558
+ function send(data, id, number) {
4559
+ const once = (forceRefresh) => wsfex.issue({
4560
+ ...data,
4561
+ id,
4562
+ number,
4563
+ ...auth({
4564
+ ...options,
4565
+ ...forceRefresh ? { forceRefresh: true } : {}
4566
+ })
4567
+ });
4568
+ return once().then(
4569
+ (outcome) => outcome.kind === "indeterminate" && outcome.reason === "authentication_rejected" && options.forceRefresh !== true ? once(true) : outcome
4570
+ );
4571
+ }
4572
+ async function moveRecord(record, id) {
4573
+ const store2 = context.store;
4574
+ if (record === void 0 || store2 === void 0) {
4575
+ return;
4576
+ }
4577
+ const moved = { ...record, requestId: id };
4578
+ await storeCall(() => store2.set(recordKey, JSON.stringify(moved)));
4579
+ }
4580
+ function issued(data, attempted, id, authorization) {
4581
+ const total = Math.round(data.totalAmount * 100);
4582
+ return issuedVoucher(
4583
+ data,
4584
+ attempted,
4585
+ id,
4586
+ authorization,
4587
+ { computedTotal: total, sentTotal: total, vatAdjustment: 0 },
4588
+ issuer
4589
+ );
4590
+ }
4591
+ function evidence(outcome) {
4592
+ const { raw, ...rest } = outcome;
4593
+ return options.include?.rawResponse && raw !== void 0 ? { ...rest, rawResponse: raw } : rest;
4594
+ }
4595
+ }
4596
+ function issuedVoucher(data, attempted, id, authorization, amounts, issuer) {
4597
+ const date = isoDate(data.voucherDate);
4598
+ return {
4599
+ ...attempted,
4600
+ voucherClass: "E",
4601
+ requestId: id,
4602
+ date,
4603
+ header: exportHeader(data),
4604
+ cae: authorization.cae,
4605
+ caeExpiry: isoDate(authorization.caeExpiry) ?? "",
4606
+ amounts: { ...amounts },
4607
+ ...exportQr(issuer, attempted, data, date, authorization.cae)
4608
+ };
4609
+ }
4610
+ function answered(outcome, attempt, id, attempted) {
4611
+ if (outcome.kind === "authorized" && !outcome.reprocessed && echoes(outcome, id, attempted)) {
4612
+ return {
4613
+ kind: "authorized",
4614
+ recoveredByMatch: false,
4615
+ voucher: { cae: outcome.cae, caeExpiry: outcome.caeExpiry },
4616
+ authorization: attempt
4617
+ };
4618
+ }
4619
+ if (outcome.kind === "rejected" && !hasError(outcome, "1535")) {
4620
+ return {
4621
+ kind: "rejected",
4622
+ attempted,
4623
+ issues: [...outcome.errors, ...outcome.observations],
4624
+ authorization: attempt
4625
+ };
4626
+ }
4627
+ return void 0;
4628
+ }
4629
+ function settled(outcome, attempt, found, id, attempted, sent, issue) {
4630
+ if (found !== null && found.id === id && sameVoucher(found, sent)) {
4631
+ const authorized = outcome.kind === "authorized" ? outcome : void 0;
4632
+ const cae = found.cae ?? authorized?.cae;
4633
+ const caeExpiry = found.caeExpiry ?? authorized?.caeExpiry;
4634
+ if (found.result !== "A" || !cae || !caeExpiry) {
4635
+ return {
4636
+ kind: "indeterminate",
4637
+ attempted,
4638
+ attempt,
4639
+ lookup: {
4640
+ kind: "incomplete",
4641
+ reason: "ARCA reports no approved result, CAE and CAE expiry"
4642
+ }
4643
+ };
4644
+ }
4645
+ const voucher = issue({ cae, caeExpiry });
4646
+ return authorized ? {
4647
+ kind: "authorized",
4648
+ recoveredByMatch: false,
4649
+ voucher,
4650
+ authorization: attempt
4651
+ } : {
4652
+ kind: "authorized",
4653
+ recoveredByMatch: true,
4654
+ voucher,
4655
+ attempt,
4656
+ lookup: exportSummary(found)
4657
+ };
4658
+ }
4659
+ if (found !== null) {
4660
+ return {
4661
+ kind: "conflict",
4662
+ attempted,
4663
+ attempt,
4664
+ found: exportSummary(found),
4665
+ reason: found.id === id ? "ARCA stored different fiscal fields under this request id" : "Another request holds this voucher number"
4666
+ };
4667
+ }
4668
+ if (outcome.kind === "rejected") {
4669
+ return {
4670
+ kind: "rejected",
4671
+ attempted,
4672
+ issues: [...outcome.errors, ...outcome.observations],
4673
+ authorization: attempt
4674
+ };
4675
+ }
4676
+ return {
4677
+ kind: "indeterminate",
4678
+ attempted,
4679
+ attempt,
4680
+ lookup: { kind: "not_found" }
4681
+ };
4682
+ }
4683
+ function echoes(outcome, id, attempted) {
4684
+ const echo = outcome.echo ?? {};
4685
+ return (echo.id === void 0 || echo.id === id) && (echo.salesPoint === void 0 || echo.salesPoint === attempted.salesPoint) && (echo.voucherType === void 0 || echo.voucherType === attempted.voucherType) && (echo.number === void 0 || echo.number === attempted.number);
4686
+ }
4687
+ function hasError(outcome, code) {
4688
+ return outcome.errors.some((issue) => issue.code === code);
4689
+ }
4690
+ function sameVoucher(found, sent) {
4691
+ return canonicalHash(fiscalIdentity(found)) === canonicalHash(fiscalIdentity(sent));
4692
+ }
4693
+ function fiscalIdentity(voucher) {
4694
+ return {
4695
+ voucherType: voucher.voucherType,
4696
+ salesPoint: voucher.salesPoint,
4697
+ voucherDate: trimmed(voucher.voucherDate),
4698
+ exportType: voucher.exportType,
4699
+ permitExists: trimmed(voucher.permitExists),
4700
+ permits: (voucher.permits ?? []).map((permit) => [
4701
+ trimmed(permit.id),
4702
+ permit.destination
4703
+ ]),
4704
+ destination: voucher.destination,
4705
+ receiverName: trimmed(voucher.receiverName),
4706
+ receiverCountryTaxId: trimmed(voucher.receiverCountryTaxId),
4707
+ receiverAddress: trimmed(voucher.receiverAddress),
4708
+ receiverTaxId: trimmed(voucher.receiverTaxId),
4709
+ currencyId: trimmed(voucher.currencyId),
4710
+ exchangeRate: decimal(voucher.exchangeRate),
4711
+ sameCurrencyForeignCancellation: trimmed(
4712
+ voucher.sameCurrencyForeignCancellation
4713
+ ),
4714
+ commercialObservations: trimmed(voucher.commercialObservations),
4715
+ totalAmount: decimal(voucher.totalAmount),
4716
+ observations: trimmed(voucher.observations),
4717
+ // ARCA may fill the issuer's CUIT; the original is named by coordinates.
4718
+ associatedVouchers: (voucher.associatedVouchers ?? []).map((original) => [
4719
+ original.voucherType,
4720
+ original.salesPoint,
4721
+ original.number
4722
+ ]),
4723
+ paymentTerms: trimmed(voucher.paymentTerms),
4724
+ incoterms: trimmed(voucher.incoterms),
4725
+ incotermsDetail: trimmed(voucher.incotermsDetail),
4726
+ language: voucher.language,
4727
+ items: voucher.items.map((item) => [
4728
+ trimmed(item.code),
4729
+ trimmed(item.description),
4730
+ decimal(item.quantity ?? 0),
4731
+ item.unit,
4732
+ decimal(item.unitPrice ?? "0"),
4733
+ decimal(item.discount ?? 0),
4734
+ decimal(item.amount)
4735
+ ]),
4736
+ paymentDate: trimmed(voucher.paymentDate),
4737
+ activities: (voucher.activities ?? []).map(
4738
+ (activity) => trimmed(activity.id)?.padStart(6, "0")
4739
+ )
4740
+ };
4741
+ }
4742
+ function trimmed(value) {
4743
+ return value?.replace(/\r\n?/g, "\n").trim() || void 0;
4744
+ }
4745
+ function decimal(value) {
4746
+ if (value === void 0) {
4747
+ return;
4748
+ }
4749
+ const written = typeof value === "number" ? value.toFixed(6) : value.trim();
4750
+ const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(written);
4751
+ if (!match) {
4752
+ return written;
4753
+ }
4754
+ const whole = (match[2] ?? "0").replace(/^0+(?=\d)/, "");
4755
+ const fraction = (match[3] ?? "").replace(/0+$/, "");
4756
+ const sign = whole === "0" && fraction === "" ? "" : match[1] ?? "";
4757
+ return `${sign}${whole}${fraction ? `.${fraction}` : ""}`;
4758
+ }
4759
+ var MAX_REQUEST_ID = 999999999999999;
4760
+ var localRequestIds = /* @__PURE__ */ new Map();
4761
+ function requestIdKey(environment, issuer) {
4762
+ return `arca:v1:wsfex:request-id:${environment}:${issuer}`;
4763
+ }
4764
+ function readRequestIdMarker(json) {
4765
+ if (json === null) {
4766
+ return 0;
4767
+ }
4768
+ try {
4769
+ const marker = JSON.parse(json);
4770
+ if (marker?.v === 1 && Number.isSafeInteger(marker.id) && marker.id >= 0) {
4771
+ return marker.id;
4772
+ }
4773
+ } catch {
4774
+ }
4775
+ throw new ArcaConfigurationError(
4776
+ "Invalid WSFEX request id marker; preserve it for reconciliation."
4777
+ );
4778
+ }
4779
+ var localLocks = /* @__PURE__ */ new Map();
4780
+ async function withLocalLock(key, fn, signal) {
4781
+ const previous = localLocks.get(key) ?? Promise.resolve();
4782
+ let release = () => void 0;
4783
+ const current = new Promise((resolve) => {
4784
+ release = resolve;
4785
+ });
4786
+ const chained = previous.then(() => current);
4787
+ localLocks.set(key, chained);
4788
+ chained.then(() => {
4789
+ if (localLocks.get(key) === chained) {
4790
+ localLocks.delete(key);
4791
+ }
4792
+ });
4793
+ try {
4794
+ await waitForTurn(previous, signal);
4795
+ } catch (error) {
4796
+ release();
4797
+ throw error;
4798
+ }
4799
+ try {
4800
+ return await fn();
4801
+ } finally {
4802
+ release();
4803
+ }
4804
+ }
4805
+ function waitForTurn(previous, signal) {
4806
+ throwIfAborted(signal);
4807
+ let timer;
4808
+ let onAbort;
4809
+ const stopped = new Promise((_, reject) => {
4810
+ timer = setTimeout(
4811
+ () => reject(
4812
+ new ArcaLockTimeoutError(
4813
+ "ARCA store lock stayed held; no work was attempted.",
4814
+ { reason: "held" }
4815
+ )
4816
+ ),
4817
+ MAX_WAIT_MS
4818
+ );
4819
+ timer.unref?.();
4820
+ onAbort = () => {
4821
+ try {
4822
+ throwIfAborted(signal);
4823
+ } catch (error) {
4824
+ reject(error);
4825
+ }
4826
+ };
4827
+ signal?.addEventListener("abort", onAbort, { once: true });
4828
+ });
4829
+ return Promise.race([previous, stopped]).finally(() => {
4830
+ clearTimeout(timer);
4831
+ if (onAbort) {
4832
+ signal?.removeEventListener("abort", onAbort);
4833
+ }
4834
+ });
4835
+ }
4836
+ function readExportRecord(json) {
4837
+ let record;
4838
+ try {
4839
+ record = JSON.parse(json);
4840
+ } catch (cause) {
4841
+ throw new ArcaConfigurationError(
4842
+ "Invalid ARCA reservation record; preserve it for reconciliation.",
4843
+ { cause }
4844
+ );
4845
+ }
4846
+ if (record?.service !== "wsfex") {
4847
+ throw new ArcaInputError(
4848
+ "The idempotency key was already used with different input or operation.",
4849
+ {
4850
+ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
4851
+ field: "options.idempotencyKey"
4852
+ }
4853
+ );
4854
+ }
4855
+ if (record.v !== 2 || !["issue", "creditNote", "debitNote"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || !Number.isSafeInteger(record.number) || !Number.isSafeInteger(record.requestId) || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
4856
+ throw new ArcaConfigurationError(
4857
+ "Invalid ARCA reservation record; preserve it for reconciliation."
4858
+ );
4859
+ }
4860
+ return record;
4861
+ }
4862
+ function exportHeader(data) {
4863
+ const header = {
4864
+ exportType: data.exportType,
4865
+ destination: data.destination,
4866
+ receiverName: data.receiverName,
4867
+ receiverAddress: data.receiverAddress,
4868
+ currencyId: data.currencyId,
4869
+ language: data.language
4870
+ };
4871
+ assign(header, "receiverTaxId", data.receiverTaxId);
4872
+ assign(header, "receiverCountryTaxId", data.receiverCountryTaxId);
4873
+ assign(header, "exchangeRate", data.exchangeRate);
4874
+ assign(
4875
+ header,
4876
+ "paidInForeignCurrency",
4877
+ data.sameCurrencyForeignCancellation === void 0 ? void 0 : data.sameCurrencyForeignCancellation === "S"
4878
+ );
4879
+ assign(header, "incoterms", data.incoterms);
4880
+ assign(header, "incotermsDetail", data.incotermsDetail);
4881
+ assign(
4882
+ header,
4883
+ "permitExists",
4884
+ data.permitExists === void 0 ? void 0 : data.permitExists === "S"
4885
+ );
4886
+ assign(header, "paymentTerms", data.paymentTerms);
4887
+ assign(header, "paymentDate", isoDate(data.paymentDate));
4888
+ return header;
4889
+ }
4890
+ function exportSummary(voucher) {
4891
+ const summary = {
4892
+ voucherClass: "E",
4893
+ number: voucher.number
4894
+ };
4895
+ assign(summary, "salesPoint", voucher.salesPoint);
4896
+ assign(summary, "voucherType", voucher.voucherType);
4897
+ assign(summary, "requestId", voucher.id);
4898
+ assign(summary, "date", isoDate(voucher.voucherDate));
4899
+ assign(summary, "exportType", voucher.exportType);
4900
+ assign(summary, "destination", voucher.destination);
4901
+ assign(summary, "receiverName", voucher.receiverName);
4902
+ assign(summary, "receiverAddress", voucher.receiverAddress);
4903
+ assign(summary, "receiverTaxId", voucher.receiverTaxId);
4904
+ assign(summary, "receiverCountryTaxId", voucher.receiverCountryTaxId);
4905
+ assign(summary, "currencyId", voucher.currencyId);
4906
+ assign(summary, "exchangeRate", voucher.exchangeRate);
4907
+ assign(
4908
+ summary,
4909
+ "totalAmount",
4910
+ voucher.totalAmount === void 0 ? void 0 : Math.round(voucher.totalAmount * 100)
4911
+ );
4912
+ assign(summary, "result", voucher.result);
4913
+ assign(summary, "cae", voucher.cae);
4914
+ assign(summary, "caeExpiry", isoDate(voucher.caeExpiry));
4915
+ return summary;
4916
+ }
4917
+ function exportQr(taxId, attempted, data, date, cae) {
4918
+ try {
4919
+ const qrPayload = arcaQrPayload({
4920
+ taxId,
4921
+ ...attempted,
4922
+ date,
4923
+ total: Math.round(data.totalAmount * 100),
4924
+ currency: data.currencyId,
4925
+ ...data.exchangeRate === void 0 ? {} : { exchangeRate: data.exchangeRate },
4926
+ cae
4927
+ });
4928
+ return { qr: qrUrlForPayload(qrPayload), qrPayload };
4929
+ } catch {
4930
+ return {};
4931
+ }
4932
+ }
4933
+ function originalTotal(original) {
4934
+ return Math.round(original.totalAmount * 100);
4935
+ }
4936
+ function deriveReceiver2(to) {
4937
+ assertIssueObject(to, "to");
4938
+ assertIssueKeys(
4939
+ to,
4940
+ ["country", "name", "address", "taxId", "countryTaxId"],
4941
+ "to"
4942
+ );
4943
+ const receiver = to;
4944
+ if (!(Number.isSafeInteger(receiver.country) && receiver.country >= 1 && receiver.country <= 999)) {
4945
+ invalid3("to.country", "an ARCA destination country code");
4946
+ }
4947
+ if (receiver.taxId === void 0 && receiver.countryTaxId === void 0) {
4948
+ throw new ArcaInputError("to.taxId or to.countryTaxId is required.", {
4949
+ code: "ARCA_INPUT_MISSING_FIELD",
4950
+ field: "to.taxId",
4951
+ expected: "the receiver's tax ID abroad, ARCA's country CUIT, or both"
4952
+ });
4953
+ }
4954
+ if (receiver.countryTaxId !== void 0 && !(typeof receiver.countryTaxId === "string" && /^\d{11}$/.test(receiver.countryTaxId))) {
4955
+ invalid3("to.countryTaxId", "an 11-digit string");
4956
+ }
4957
+ return {
4958
+ destination: receiver.country,
4959
+ receiverName: text(receiver.name, "to.name", 200),
4960
+ ...receiver.countryTaxId === void 0 ? {} : { receiverCountryTaxId: receiver.countryTaxId },
4961
+ receiverAddress: text(receiver.address, "to.address", 300),
4962
+ ...optionalText(receiver.taxId, "receiverTaxId", 50, "to.taxId")
4963
+ };
4964
+ }
4965
+ function deriveExport(value) {
4966
+ assertIssueObject(value, "export");
4967
+ const exported = value;
4968
+ if (exported.kind === "goods") {
4969
+ assertIssueKeys(
4970
+ exported,
4971
+ ["kind", "incoterms", "incotermsDetail", "permits"],
4972
+ "export"
4973
+ );
4974
+ if (typeof exported.incoterms !== "string" || !/^[A-Z]{3}$/.test(exported.incoterms)) {
4975
+ invalid3("export.incoterms", "a three-letter Incoterm such as FOB");
4976
+ }
4977
+ let permits;
4978
+ if (exported.permits !== void 0) {
4979
+ if (!Array.isArray(exported.permits) || exported.permits.length === 0) {
4980
+ invalid3("export.permits", "a non-empty list, or absent while pending");
4981
+ }
4982
+ const seen = /* @__PURE__ */ new Set();
4983
+ permits = exported.permits.map((entry, index) => {
4984
+ const field = `export.permits[${index}]`;
4985
+ const permit = entry;
4986
+ assertIssueObject(permit, field);
4987
+ assertIssueKeys(permit, ["id", "destination"], field);
4988
+ if (typeof permit.id !== "string" || !/^\d{5}[A-Z0-9]{4}\d{6}[A-Z]$/.test(permit.id)) {
4989
+ invalid3(`${field}.id`, "a shipping permit such as 09052EC01006154G");
4990
+ }
4991
+ if (!(typeof permit.destination === "number" && Number.isSafeInteger(permit.destination) && permit.destination >= 1 && permit.destination <= 999)) {
4992
+ invalid3(`${field}.destination`, "an ARCA destination country code");
4993
+ }
4994
+ const pair = `${permit.id}:${permit.destination}`;
4995
+ if (seen.has(pair)) {
4996
+ invalid3(field, "a permit and destination listed once");
4997
+ }
4998
+ seen.add(pair);
4999
+ return {
5000
+ id: permit.id,
5001
+ destination: permit.destination
5002
+ };
5003
+ });
5004
+ }
5005
+ return {
5006
+ exportType: 1,
5007
+ permitExists: permits === void 0 ? "N" : "S",
5008
+ ...permits === void 0 ? {} : { permits },
5009
+ incoterms: exported.incoterms,
5010
+ ...optionalText(
5011
+ exported.incotermsDetail,
5012
+ "incotermsDetail",
5013
+ 20,
5014
+ "export.incotermsDetail"
5015
+ )
5016
+ };
5017
+ }
5018
+ if (exported.kind === "services" || exported.kind === "other") {
5019
+ assertIssueKeys(exported, ["kind", "paymentDate"], "export");
5020
+ if (exported.paymentDate === void 0) {
5021
+ throw new ArcaInputError("export.paymentDate is required.", {
5022
+ code: "ARCA_INPUT_MISSING_FIELD",
5023
+ field: "export.paymentDate",
5024
+ expected: "the date the receiver pays"
5025
+ });
5026
+ }
5027
+ return {
5028
+ exportType: EXPORT_KINDS[exported.kind],
5029
+ paymentDate: normalizeWsfeDateInput(
5030
+ exported.paymentDate,
5031
+ "export.paymentDate"
5032
+ )
5033
+ };
5034
+ }
5035
+ return invalid3("export.kind", "goods, services or other");
5036
+ }
5037
+ function deriveCurrency2(currency, rate) {
5038
+ const value = currency ?? "ARS";
5039
+ let currencyId;
5040
+ if (typeof value === "object" && value !== null) {
5041
+ assertIssueKeys(value, ["id"], "currency");
5042
+ if (typeof value.id !== "string" || !/^[A-Z0-9]{3}$/.test(value.id)) {
5043
+ invalid3("currency.id", "a three-character ARCA currency code");
5044
+ }
5045
+ currencyId = value.id;
5046
+ } else if (value === "ARS" || value === "USD") {
5047
+ currencyId = ARCA_CURRENCY_IDS[value];
5048
+ } else {
5049
+ return invalid3("currency", "ARS, USD or { id }");
5050
+ }
5051
+ if (rate !== void 0 && typeof rate !== "string") {
5052
+ invalid3("exchangeRate", "a decimal string");
5053
+ }
5054
+ if (currencyId === ARCA_CURRENCY_IDS.ARS) {
5055
+ const exchangeRate2 = serializeArcaExchangeRate(rate ?? "1", "exchangeRate");
5056
+ if (Number(exchangeRate2) !== 1) {
5057
+ invalid3("exchangeRate", "1 for pesos");
5058
+ }
5059
+ return { currencyId, exchangeRate: exchangeRate2 };
5060
+ }
5061
+ if (rate === void 0) {
5062
+ throw new ArcaInputError("exchangeRate is required for this currency.", {
5063
+ code: "ARCA_INPUT_MISSING_FIELD",
5064
+ field: "exchangeRate",
5065
+ expected: "ARCA's rate, from wsfex.getExchangeRate()"
5066
+ });
5067
+ }
5068
+ return {
5069
+ currencyId,
5070
+ exchangeRate: serializeArcaExchangeRate(rate, "exchangeRate")
5071
+ };
5072
+ }
5073
+ function deriveLanguage(language) {
5074
+ if (language === void 0) {
5075
+ return 1;
5076
+ }
5077
+ if (typeof language !== "string" || !Object.hasOwn(LANGUAGES, language)) {
5078
+ return invalid3("language", "es, en or pt");
5079
+ }
5080
+ return LANGUAGES[language];
5081
+ }
5082
+ var SCALE_6 = 1000000n;
5083
+ function deriveItems(value, path) {
5084
+ if (!Array.isArray(value) || value.length < 1 || value.length > 9999) {
5085
+ invalid3(path, "a list of 1 to 9999 items");
5086
+ }
5087
+ let total = 0n;
5088
+ const items = value.map((item, index) => {
5089
+ const line = deriveItem(item, `${path}[${index}]`);
5090
+ total += BigInt(item.amount);
5091
+ return line;
5092
+ });
5093
+ if (total < 0n) {
5094
+ invalid3(path, "lines whose total is zero or positive");
5095
+ }
5096
+ return { items, total: Number(total) };
5097
+ }
5098
+ function deriveItem(item, field) {
5099
+ assertIssueObject(item, field);
5100
+ assertIssueKeys(
5101
+ item,
5102
+ [
5103
+ "description",
5104
+ "unit",
5105
+ "quantity",
5106
+ "unitPrice",
5107
+ "discount",
5108
+ "code",
5109
+ "amount"
5110
+ ],
5111
+ field
5112
+ );
5113
+ if (!(Number.isSafeInteger(item.unit) && item.unit >= 0 && item.unit <= 99)) {
5114
+ invalid3(`${field}.unit`, "an ARCA unit of measure");
5115
+ }
5116
+ assertItemAmount(item, field);
5117
+ const line = {
5118
+ ...optionalText(item.code, "code", 50, `${field}.code`),
5119
+ description: text(item.description, `${field}.description`, 4e3),
5120
+ unit: item.unit,
5121
+ amount: item.amount / 100
5122
+ };
5123
+ if (!UNPRICED_UNITS.includes(item.unit)) {
5124
+ return { ...line, ...pricedLine(item, field) };
5125
+ }
5126
+ for (const key of ["quantity", "unitPrice", "discount"]) {
5127
+ if (item[key] !== void 0 && Number(item[key]) !== 0) {
5128
+ invalid3(`${field}.${key}`, `absent for unit ${item.unit}`);
5129
+ }
5130
+ }
5131
+ return line;
5132
+ }
5133
+ function assertItemAmount(item, field) {
5134
+ if (!Number.isSafeInteger(item.amount)) {
5135
+ invalid3(`${field}.amount`, "an integer amount in minor units");
5136
+ }
5137
+ if (item.unit === 99 && item.amount >= 0) {
5138
+ invalid3(`${field}.amount`, "negative for a discount line");
5139
+ }
5140
+ if (item.unit !== 97 && item.unit !== 99 && item.amount < 0) {
5141
+ invalid3(`${field}.amount`, "zero or positive for this unit");
5142
+ }
5143
+ if (Math.abs(item.amount) > 999999999999999) {
5144
+ invalid3(`${field}.amount`, "at most 13 integer digits");
5145
+ }
5146
+ }
5147
+ function pricedLine(item, field) {
5148
+ const quantity = scaledQuantity(item.quantity, `${field}.quantity`);
5149
+ const price = scaledPrice(item.unitPrice, `${field}.unitPrice`);
5150
+ const discount = item.discount ?? 0;
5151
+ if (!(Number.isSafeInteger(discount) && discount >= 0)) {
5152
+ invalid3(`${field}.discount`, "a non-negative amount in minor units");
5153
+ }
5154
+ const exact = quantity * price - BigInt(discount) * 10000000000n;
5155
+ if (exact < 0n) {
5156
+ invalid3(`${field}.discount`, "at most the line's quantity \xD7 unitPrice");
5157
+ }
5158
+ if (!withinLineTolerance(exact, BigInt(item.amount))) {
5159
+ throw new ArcaInputError(
5160
+ `${field}.amount must equal quantity \xD7 unitPrice less discount.`,
5161
+ {
5162
+ code: "ARCA_INPUT_AMOUNT_MISMATCH",
5163
+ field: `${field}.amount`,
5164
+ expected: String(roundHalfEvenRatio(exact, 10000000000n))
5165
+ }
5166
+ );
5167
+ }
5168
+ return {
5169
+ quantity: item.quantity,
5170
+ unitPrice: item.unitPrice,
5171
+ ...discount > 0 ? { discount: discount / 100 } : {}
5172
+ };
5173
+ }
5174
+ function withinLineTolerance(exact, amountMinorUnits) {
5175
+ const computed = roundHalfEvenRatio(exact, 10000000n);
5176
+ const amount = amountMinorUnits * 1000n;
5177
+ const error = computed > amount ? computed - amount : amount - computed;
5178
+ if (error <= 1000n) {
5179
+ return true;
2811
5180
  }
2812
- const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
2813
- if (isArcaLogLevel(envLevel)) {
2814
- return envLevel;
5181
+ const base = computed < amount ? computed : amount;
5182
+ return base > 0n && error * 10000n <= base;
5183
+ }
5184
+ function scaledQuantity(value, field) {
5185
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
5186
+ return invalid3(field, "a positive number");
2815
5187
  }
2816
- return "warn";
5188
+ const fixed = value.toFixed(6);
5189
+ if (Number(fixed) !== value || fixed.split(".")[0].length > 12) {
5190
+ invalid3(field, "at most 12 integer digits and 6 decimals");
5191
+ }
5192
+ const [whole, fraction = ""] = fixed.split(".");
5193
+ return BigInt(whole) * SCALE_6 + BigInt(fraction.padEnd(6, "0"));
2817
5194
  }
2818
- function shouldLog(threshold, messageLevel) {
2819
- return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
5195
+ function scaledPrice(value, field) {
5196
+ if (typeof value !== "string" || !/^\d{1,12}(\.\d{1,6})?$/.test(value)) {
5197
+ return invalid3(field, "a decimal string with at most 6 decimals");
5198
+ }
5199
+ const [whole, fraction = ""] = value.split(".");
5200
+ return BigInt(whole) * SCALE_6 + BigInt(fraction.padEnd(6, "0"));
2820
5201
  }
2821
- function isArcaLogLevel(value) {
2822
- return ARCA_LOG_LEVELS.includes(value);
5202
+ function reviewedTotal(total, computed) {
5203
+ if (total === void 0) {
5204
+ return computed;
5205
+ }
5206
+ if (!Number.isSafeInteger(total) || total < 0) {
5207
+ invalid3("total", "a non-negative integer amount in minor units");
5208
+ }
5209
+ if (total !== computed) {
5210
+ throw new ArcaInputError("total must equal the sum of items.", {
5211
+ code: "ARCA_INPUT_AMOUNT_MISMATCH",
5212
+ field: "total",
5213
+ expected: String(computed)
5214
+ });
5215
+ }
5216
+ return computed;
2823
5217
  }
2824
- function defaultArcaLog(level, message, ...args) {
2825
- const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
2826
- method(message, ...args);
5218
+ function assertSalesPoint2(value, field) {
5219
+ if (!(typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 99998)) {
5220
+ invalid3(field, "an integer from 1 through 99998");
5221
+ }
5222
+ }
5223
+ function text(value, field, max) {
5224
+ if (typeof value !== "string" || value.trim().length === 0) {
5225
+ throw new ArcaInputError(`${field} is required.`, {
5226
+ code: "ARCA_INPUT_MISSING_FIELD",
5227
+ field,
5228
+ expected: `a non-empty string of at most ${max} characters`
5229
+ });
5230
+ }
5231
+ if (value.length > max) {
5232
+ invalid3(field, `at most ${max} characters`);
5233
+ }
5234
+ return value;
5235
+ }
5236
+ function optionalText(value, key, max, field = key) {
5237
+ if (value === void 0) {
5238
+ return {};
5239
+ }
5240
+ return { [key]: text(value, field, max) };
5241
+ }
5242
+ function isoDate(value) {
5243
+ return value === void 0 ? void 0 : toIsoDate(value) ?? value;
5244
+ }
5245
+ function assign(target, key, value) {
5246
+ if (value !== void 0) {
5247
+ target[key] = value;
5248
+ }
5249
+ }
5250
+ function invalid3(field, expected) {
5251
+ throw new ArcaInputError(`${field} must be ${expected}.`, {
5252
+ code: "ARCA_INPUT_INVALID_VALUE",
5253
+ field,
5254
+ expected
5255
+ });
2827
5256
  }
2828
5257
 
2829
5258
  // src/services/issuance-wsmtxca.ts
@@ -2839,7 +5268,7 @@ var iso = (value) => {
2839
5268
  };
2840
5269
  function assertWsmtxcaVoucherType(voucherType) {
2841
5270
  if (voucherFamily(voucherType).voucherClass === "C") {
2842
- invalid3(
5271
+ invalid4(
2843
5272
  "voucherType",
2844
5273
  "WSMTXCA authorizes class A and B vouchers only; issue class C through WSFE"
2845
5274
  );
@@ -2860,7 +5289,7 @@ function wsmtxcaRequest(data, number) {
2860
5289
  (source) => source !== void 0
2861
5290
  ).length;
2862
5291
  if (!lines?.length || sources !== 1) {
2863
- invalid3("items", "WSMTXCA requires items with line detail");
5292
+ invalid4("items", "WSMTXCA requires items with line detail");
2864
5293
  }
2865
5294
  const items = lines.map((line) => ({
2866
5295
  unidadesMtx: line.matrixUnits,
@@ -2887,7 +5316,7 @@ function wsmtxcaRequest(data, number) {
2887
5316
  }
2888
5317
  const tributes = data.taxes?.length ? data.taxes : void 0;
2889
5318
  if (tributes === void 0 && normalizeArcaAmountToMinorUnits(data.taxAmount, "taxes") !== 0n) {
2890
- invalid3("taxes", "Tribute amount requires tribute details");
5319
+ invalid4("taxes", "Tribute amount requires tribute details");
2891
5320
  }
2892
5321
  return {
2893
5322
  comprobanteCAERequest: {
@@ -2956,7 +5385,7 @@ function wsmtxcaRequest(data, number) {
2956
5385
  }
2957
5386
  };
2958
5387
  }
2959
- function invalid3(field, message) {
5388
+ function invalid4(field, message) {
2960
5389
  throw new ArcaInputError(message, {
2961
5390
  code: "ARCA_INPUT_INVALID_VALUE",
2962
5391
  field
@@ -3187,7 +5616,7 @@ function wsmtxcaAdditionalData(fields) {
3187
5616
  }
3188
5617
 
3189
5618
  // src/services/wsfe-credit-note.ts
3190
- function invalid4(reason) {
5619
+ function invalid5(reason) {
3191
5620
  throw new ArcaInputError(`issueCreditNote cannot proceed: ${reason}.`, {
3192
5621
  code: "ARCA_INPUT_INVALID_VALUE"
3193
5622
  });
@@ -3207,7 +5636,7 @@ function originalReceiverDocument(original) {
3207
5636
  }
3208
5637
  function required(value, field) {
3209
5638
  if (value === void 0 || value === null) {
3210
- invalid4(`original is missing ${field}`);
5639
+ invalid5(`original is missing ${field}`);
3211
5640
  }
3212
5641
  return value;
3213
5642
  }
@@ -3236,7 +5665,7 @@ function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Dat
3236
5665
  }
3237
5666
  function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
3238
5667
  if (input.items === void 0 && input.amounts === void 0) {
3239
- invalid4("items is required to credit chosen lines");
5668
+ invalid5("items is required to credit chosen lines");
3240
5669
  }
3241
5670
  const requestedTotal = input.total === void 0 ? void 0 : assertArcaMinorUnits(input.total, "total");
3242
5671
  const { note, header } = prepareCreditNote(originals, input, now, kind);
@@ -3249,7 +5678,7 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
3249
5678
  input.amounts,
3250
5679
  input.taxes
3251
5680
  ) : calculateWsfeAmounts(lineSource);
3252
- const originalTotal = originals.reduce(
5681
+ const originalTotal2 = originals.reduce(
3253
5682
  (sum, original) => sum + normalizeArcaAmountToMinorUnits(
3254
5683
  required(original.totalAmount, "totalAmount"),
3255
5684
  "totalAmount"
@@ -3257,8 +5686,8 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
3257
5686
  0n
3258
5687
  );
3259
5688
  const ceiling = originals.length === 1 ? "the original" : "the sum of the originals";
3260
- if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) > originalTotal) {
3261
- invalid4(
5689
+ if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) > originalTotal2) {
5690
+ invalid5(
3262
5691
  `the note total is greater than ${ceiling}; the SDK does not track earlier notes against an original`
3263
5692
  );
3264
5693
  }
@@ -3281,8 +5710,8 @@ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new
3281
5710
  )
3282
5711
  );
3283
5712
  const sentTotal = input.amounts && requestedTotal !== void 0 ? Number(requestedTotal) : total;
3284
- if (kind === "creditNote" && BigInt(sentTotal) > originalTotal) {
3285
- invalid4(`the note total is greater than ${ceiling}`);
5713
+ if (kind === "creditNote" && BigInt(sentTotal) > originalTotal2) {
5714
+ invalid5(`the note total is greater than ${ceiling}`);
3286
5715
  }
3287
5716
  data.totalAmount = minor(sentTotal, "total");
3288
5717
  amounts.computedTotal += total - amounts.sentTotal;
@@ -3301,7 +5730,7 @@ function prepareCreditNote(originals, input, now, kind) {
3301
5730
  );
3302
5731
  const [first, ...rest] = derived;
3303
5732
  if (first === void 0) {
3304
- invalid4("for must name at least one original");
5733
+ invalid5("for must name at least one original");
3305
5734
  }
3306
5735
  for (const other of rest) {
3307
5736
  assertInheritedHeader(first.header, other.header);
@@ -3324,7 +5753,7 @@ function assertInheritedHeader(first, other) {
3324
5753
  const left = first[field];
3325
5754
  const right = other[field];
3326
5755
  if (JSON.stringify(left) !== JSON.stringify(right)) {
3327
- invalid4(
5756
+ invalid5(
3328
5757
  `the originals disagree on ${field}, which the note inherits from them`
3329
5758
  );
3330
5759
  }
@@ -3335,14 +5764,14 @@ function resolveReceiverCondition(original, input) {
3335
5764
  const reported = original.receiverVatConditionId;
3336
5765
  if (reported === void 0) {
3337
5766
  if (supplied === void 0) {
3338
- invalid4(
5767
+ invalid5(
3339
5768
  "original is missing receiverVatConditionId; pass to.condition with the receiver's condition"
3340
5769
  );
3341
5770
  }
3342
5771
  return supplied;
3343
5772
  }
3344
5773
  if (supplied !== void 0 && supplied !== reported) {
3345
- invalid4(
5774
+ invalid5(
3346
5775
  `to.condition (${supplied}) does not match the original's receiver condition (${reported})`
3347
5776
  );
3348
5777
  }
@@ -3352,14 +5781,14 @@ function prepareOneCreditNote(original, input, now, kind) {
3352
5781
  assertOriginalExtensions(original);
3353
5782
  const family = voucherFamily(original.voucherType ?? 0);
3354
5783
  if (family.types[2] === original.voucherType) {
3355
- invalid4("original must be an invoice or debit note");
5784
+ invalid5("original must be an invoice or debit note");
3356
5785
  }
3357
5786
  const note = {
3358
5787
  voucherClass: family.voucherClass,
3359
5788
  voucherType: family.types[kind === "creditNote" ? 2 : 1]
3360
5789
  };
3361
5790
  if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
3362
- invalid4("original is not authorized");
5791
+ invalid5("original is not authorized");
3363
5792
  }
3364
5793
  const voucherDate = normalizeWsfeDateInput(
3365
5794
  input.date ?? buenosAiresDate(now),
@@ -3370,7 +5799,7 @@ function prepareOneCreditNote(original, input, now, kind) {
3370
5799
  "original.voucherDate"
3371
5800
  );
3372
5801
  if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
3373
- invalid4(
5802
+ invalid5(
3374
5803
  "original date is later than the note and outside its month (10210)"
3375
5804
  );
3376
5805
  }
@@ -3447,7 +5876,7 @@ function assertNoteReceiver(to) {
3447
5876
  try {
3448
5877
  receiverConditionId(to.condition, "to.condition");
3449
5878
  } catch {
3450
- invalid4("to.condition must be a supported receiver condition");
5879
+ invalid5("to.condition must be a supported receiver condition");
3451
5880
  }
3452
5881
  }
3453
5882
  function assertCreditNoteInput(input) {
@@ -3480,7 +5909,7 @@ function assertCreditNoteInput(input) {
3480
5909
  ...input.to === void 0 ? {} : { to: { condition: input.to.condition } }
3481
5910
  };
3482
5911
  if (input.items !== void 0 && input.amounts !== void 0) {
3483
- invalid4("use items or amounts, never both");
5912
+ invalid5("use items or amounts, never both");
3484
5913
  }
3485
5914
  if ((input.items === void 0 && input.amounts === void 0) === (input.all === void 0)) {
3486
5915
  throw new ArcaInputError(
@@ -3617,7 +6046,7 @@ function assertOriginalExtensions(original) {
3617
6046
  ["associatedPeriod", "PeriodoAsoc"]
3618
6047
  ]) {
3619
6048
  if (original.raw[rawField] && original[field] === void 0) {
3620
- invalid4(`original ${field} could not be decoded`);
6049
+ invalid5(`original ${field} could not be decoded`);
3621
6050
  }
3622
6051
  }
3623
6052
  try {
@@ -3648,7 +6077,7 @@ function assertOriginalExtensions(original) {
3648
6077
  );
3649
6078
  }
3650
6079
  } catch {
3651
- invalid4("original extension fields are incomplete or malformed");
6080
+ invalid5("original extension fields are incomplete or malformed");
3652
6081
  }
3653
6082
  }
3654
6083
  function assertFullMode(input) {
@@ -3685,7 +6114,7 @@ function assertFullMode(input) {
3685
6114
  }
3686
6115
 
3687
6116
  // src/services/vouchers.ts
3688
- function createVouchersService(wsfe, context, wsmtxca) {
6117
+ function createVouchersService(wsfe, context, wsmtxca, wsfex) {
3689
6118
  const select = (options = {}) => {
3690
6119
  validateOptions(options);
3691
6120
  if (options.service !== "wsmtxca") {
@@ -3696,63 +6125,192 @@ function createVouchersService(wsfe, context, wsmtxca) {
3696
6125
  }
3697
6126
  return createWsmtxcaIssuanceService(wsmtxca);
3698
6127
  };
3699
- return {
3700
- lookup: async (voucher, options) => lookupVoucher(select(options), voucher, options ?? {}),
3701
- lastAuthorized: async (sequence, options) => lastAuthorizedNumber(select(options), sequence, options ?? {}),
3702
- recover: async (key, options) => recoverOperation(
3703
- select,
3704
- key,
3705
- options === void 0 ? {} : options,
3706
- context
3707
- ),
3708
- issueDebitNote: async (input, options) => issueCreditNote(
6128
+ const exportIssuance = wsfex && context ? createExportIssuance(wsfex, context) : void 0;
6129
+ const exporting = () => {
6130
+ if (!exportIssuance) {
6131
+ throw new ArcaConfigurationError("WSFEX service is not configured");
6132
+ }
6133
+ return exportIssuance;
6134
+ };
6135
+ const exportOptions = (options) => {
6136
+ const cloned = cloneOptions(options ?? {});
6137
+ validateOptions(cloned);
6138
+ validateKeyStore(cloned, context);
6139
+ return cloned;
6140
+ };
6141
+ async function prepareExportNote(input, options, kind) {
6142
+ assertIssueObject(input, "input");
6143
+ assertIssueObject(input.for, "for");
6144
+ assertIssueKeys(input.for, ["salesPoint", "voucherType", "number"], "for");
6145
+ for (const [field, max] of [
6146
+ ["salesPoint", 99998],
6147
+ ["number", 99999999]
6148
+ ]) {
6149
+ const value = input.for[field];
6150
+ if (!(Number.isSafeInteger(value) && value >= 1 && value <= max)) {
6151
+ throw new ArcaInputError(
6152
+ `for.${field} must be an integer from 1 through ${max}.`,
6153
+ {
6154
+ code: "ARCA_INPUT_INVALID_VALUE",
6155
+ field: `for.${field}`
6156
+ }
6157
+ );
6158
+ }
6159
+ }
6160
+ const original = await exporting().original(input.for, options);
6161
+ if (original === null) {
6162
+ throw new ArcaInputError("ARCA has no voucher at for.", {
6163
+ code: "ARCA_INPUT_INVALID_VALUE",
6164
+ field: "for",
6165
+ expected: "an authorized export voucher"
6166
+ });
6167
+ }
6168
+ return { original, prepared: deriveExportNote(original, input, kind) };
6169
+ }
6170
+ async function issueExportNote(input, inputOptions, kind) {
6171
+ const options = exportOptions(inputOptions);
6172
+ const snapshot = structuredClone(input);
6173
+ return await exporting().issue(
6174
+ async () => (await prepareExportNote(snapshot, options, kind)).prepared,
6175
+ kind,
6176
+ snapshot,
6177
+ options
6178
+ );
6179
+ }
6180
+ async function previewExportNote(input, inputOptions, kind) {
6181
+ const options = cloneOptions(inputOptions ?? {});
6182
+ assertIssueObject(options, "options");
6183
+ assertIssueKeys(
6184
+ options,
6185
+ ["representedTaxId", "forceRefresh", "abortSignal"],
6186
+ "options",
6187
+ kind === "creditNote" ? "previewCreditNote()" : "previewDebitNote()"
6188
+ );
6189
+ validateOptions(options);
6190
+ const { original, prepared } = await prepareExportNote(
6191
+ structuredClone(input),
6192
+ options,
6193
+ kind
6194
+ );
6195
+ assertExportWindow(prepared.data);
6196
+ return exportPreview(prepared, [exportSummary(original)]);
6197
+ }
6198
+ async function readReservation(key, inputOptions) {
6199
+ const store = context?.store;
6200
+ if (exportIssuance === void 0 || store === void 0 || !context) {
6201
+ return;
6202
+ }
6203
+ const options = cloneOptions(inputOptions ?? {});
6204
+ assertIssueObject(options, "options");
6205
+ assertIssueKeys(
6206
+ options,
6207
+ ["representedTaxId", "forceRefresh", "include", "abortSignal"],
6208
+ "options"
6209
+ );
6210
+ validateOptions(options);
6211
+ validateKeyStore({ ...options, idempotencyKey: key }, context);
6212
+ const json = await storeCall(
6213
+ () => store.get(attemptKey(context.environment, context.taxId, key))
6214
+ );
6215
+ return { json, options };
6216
+ }
6217
+ const service = {
6218
+ lookup: (async (voucher, options) => {
6219
+ if (!isExportVoucherType(voucher?.voucherType)) {
6220
+ return await lookupVoucher(select(options), voucher, options ?? {});
6221
+ }
6222
+ const checked = cloneOptions(options ?? {});
6223
+ assertLookup(voucher, checked);
6224
+ return await exporting().lookup(voucher, checked);
6225
+ }),
6226
+ lastAuthorized: (async (sequence, options) => {
6227
+ if (!isExportVoucherType(sequence?.voucherType)) {
6228
+ return await lastAuthorizedNumber(
6229
+ select(options),
6230
+ sequence,
6231
+ options ?? {}
6232
+ );
6233
+ }
6234
+ const checked = cloneOptions(options ?? {});
6235
+ assertSequence(sequence, checked);
6236
+ return await exporting().lastAuthorized(sequence, checked);
6237
+ }),
6238
+ recover: (async (key, options) => {
6239
+ const read = await readReservation(key, options);
6240
+ if (typeof read?.json === "string" && isExportRecord(read.json)) {
6241
+ return await exporting().recover(key, read.json, read.options);
6242
+ }
6243
+ return await recoverOperation(
6244
+ select,
6245
+ key,
6246
+ options === void 0 ? {} : options,
6247
+ context,
6248
+ read?.json
6249
+ );
6250
+ }),
6251
+ issueDebitNote: (async (input, options) => isExportNoteInput(input) ? issueExportNote(input, options, "debitNote") : issueCreditNote(
3709
6252
  select(options),
3710
6253
  input,
3711
6254
  options ?? {},
3712
6255
  context,
3713
6256
  "debitNote",
3714
6257
  select
3715
- ),
3716
- previewCreditNote: async (input, options) => previewNote(
6258
+ )),
6259
+ previewCreditNote: (async (input, options) => isExportNoteInput(input) ? previewExportNote(input, options, "creditNote") : previewNote(
3717
6260
  select(options),
3718
6261
  input,
3719
6262
  options ?? {},
3720
6263
  context,
3721
6264
  "creditNote"
3722
- ),
3723
- previewDebitNote: async (input, options) => previewNote(
6265
+ )),
6266
+ previewDebitNote: (async (input, options) => isExportNoteInput(input) ? previewExportNote(input, options, "debitNote") : previewNote(
3724
6267
  select(options),
3725
6268
  input,
3726
6269
  options ?? {},
3727
6270
  context,
3728
6271
  "debitNote"
3729
- ),
3730
- issueCreditNote: async (input, options) => {
3731
- const result = await issueCreditNote(
3732
- select(options),
3733
- input,
3734
- options === void 0 ? {} : options,
3735
- context,
3736
- "creditNote",
3737
- select
3738
- );
3739
- return result;
3740
- },
3741
- issue: async (input, options) => {
3742
- const result = await issueInvoice(
6272
+ )),
6273
+ issueCreditNote: (async (input, options) => isExportNoteInput(input) ? issueExportNote(input, options, "creditNote") : issueCreditNote(
6274
+ select(options),
6275
+ input,
6276
+ options === void 0 ? {} : options,
6277
+ context,
6278
+ "creditNote",
6279
+ select
6280
+ )),
6281
+ issue: (async (input, options) => {
6282
+ if (isExportInput(input)) {
6283
+ const checked = exportOptions(options);
6284
+ const snapshot = structuredClone(input);
6285
+ return await exporting().issue(
6286
+ () => deriveExportInvoice(snapshot),
6287
+ "issue",
6288
+ snapshot,
6289
+ checked
6290
+ );
6291
+ }
6292
+ return await issueInvoice(
3743
6293
  select(options),
3744
6294
  input,
3745
6295
  options === void 0 ? {} : options,
3746
6296
  context,
3747
6297
  select
3748
6298
  );
3749
- return result;
3750
- },
3751
- preview: (input, options) => previewInvoice(
3752
- input,
3753
- options === void 0 ? {} : options
3754
- )
6299
+ }),
6300
+ preview: ((input, options) => {
6301
+ if (isExportInput(input)) {
6302
+ const checked = options ?? {};
6303
+ assertIssueObject(checked, "options");
6304
+ assertIssueKeys(checked, ["representedTaxId"], "options");
6305
+ validateOptions(checked);
6306
+ const prepared = deriveExportInvoice(input);
6307
+ assertExportWindow(prepared.data);
6308
+ return exportPreview(prepared);
6309
+ }
6310
+ return previewInvoice(input, options === void 0 ? {} : options);
6311
+ })
3755
6312
  };
6313
+ return service;
3756
6314
  }
3757
6315
  function previewInvoice(input, options) {
3758
6316
  assertIssueObject(options, "options");
@@ -3831,15 +6389,14 @@ async function issueInvoice(wsfe, input, inputOptions, context, select) {
3831
6389
  const options = cloneOptions(inputOptions);
3832
6390
  validateOptions(options);
3833
6391
  validateKeyStore(options, context);
3834
- const prepared = prepareInvoice(input, options);
6392
+ const snapshot = structuredClone(input);
3835
6393
  return await runOperation(
3836
6394
  wsfe,
3837
6395
  "issue",
3838
- input,
3839
- () => Promise.resolve(prepared),
6396
+ snapshot,
6397
+ async () => prepareInvoice(snapshot, options),
3840
6398
  options,
3841
6399
  context,
3842
- prepared.amounts,
3843
6400
  select
3844
6401
  );
3845
6402
  }
@@ -3850,7 +6407,7 @@ function withIssuerName(input) {
3850
6407
  const name = issuerConditionName(input.issuer);
3851
6408
  return name === void 0 ? input : { ...input, issuer: name };
3852
6409
  }
3853
- async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts, select) {
6410
+ async function runOperation(wsfe, operation, input, prepare, options, context, select) {
3854
6411
  const issuer = issuerTaxId(options, context);
3855
6412
  if (options.idempotencyKey === void 0 || !context?.store) {
3856
6413
  return runAuthorization(
@@ -3871,7 +6428,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3871
6428
  ...options.service === "wsmtxca" ? { service: "wsmtxca" } : {},
3872
6429
  ...options.number === void 0 ? {} : { number: options.number }
3873
6430
  });
3874
- const settled = settledKey(environment, taxId, idempotencyKey);
6431
+ const settled2 = settledKey(environment, taxId, idempotencyKey);
3875
6432
  const existing = await storeCall(() => store.get(key));
3876
6433
  if (existing !== null) {
3877
6434
  return await replay(existing);
@@ -3994,10 +6551,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3994
6551
  current,
3995
6552
  runAuthorization(
3996
6553
  wsfe,
3997
- {
3998
- ...preparedFromRecord(current),
3999
- ...replayAmounts ? { amounts: replayAmounts } : {}
4000
- },
6554
+ preparedFromRecord(current),
4001
6555
  options,
4002
6556
  issuer,
4003
6557
  current.number,
@@ -4015,7 +6569,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
4015
6569
  const uncoordinated = !store.withLock;
4016
6570
  const outcome = await recordConflict(
4017
6571
  store,
4018
- settled,
6572
+ settled2,
4019
6573
  await running,
4020
6574
  !(presumed && uncoordinated)
4021
6575
  );
@@ -4039,7 +6593,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
4039
6593
  async function clearRejection(record) {
4040
6594
  const checked = await settle2(
4041
6595
  record,
4042
- consultReservation(select ?? (() => wsfe), record, options, taxId, true),
6596
+ consultReservation2(select ?? (() => wsfe), record, options, taxId, true),
4043
6597
  { presumed: true }
4044
6598
  );
4045
6599
  if (checked.kind !== "indeterminate" || checked.lookup.kind !== "not_found") {
@@ -4155,11 +6709,11 @@ function readSettledRecord(json) {
4155
6709
  );
4156
6710
  }
4157
6711
  }
4158
- async function settledOutcome(select, reservation, settled, options, taxId, settledFor) {
4159
- if (settled.kind === "conflict") {
4160
- return settledConflict(settled, reservation, options);
6712
+ async function settledOutcome(select, reservation, settled2, options, taxId, settledFor) {
6713
+ if (settled2.kind === "conflict") {
6714
+ return settledConflict(settled2, reservation, options);
4161
6715
  }
4162
- const outcome = await consultReservation(
6716
+ const outcome = await consultReservation2(
4163
6717
  select,
4164
6718
  reservation,
4165
6719
  options,
@@ -4167,14 +6721,14 @@ async function settledOutcome(select, reservation, settled, options, taxId, sett
4167
6721
  true
4168
6722
  );
4169
6723
  const empty = outcome.kind === "indeterminate" && outcome.lookup.kind === "not_found";
4170
- if (!empty && (outcome.kind !== "conflict" || await successionDisputed(settled, taxId, settledFor))) {
6724
+ if (!empty && (outcome.kind !== "conflict" || await successionDisputed(settled2, taxId, settledFor))) {
4171
6725
  return outcome;
4172
6726
  }
4173
6727
  return withheld(
4174
6728
  reservation,
4175
- { kind: "superseded", by: settled.by, byTaxId: settled.byTaxId ?? taxId },
6729
+ { kind: "superseded", by: settled2.by, byTaxId: settled2.byTaxId ?? taxId },
4176
6730
  options,
4177
- settled.number
6731
+ settled2.number
4178
6732
  );
4179
6733
  }
4180
6734
  function withheld(reservation, lookup, options, number = reservation.number) {
@@ -4214,16 +6768,16 @@ async function successionDisputed(superseded, taxId, settledFor) {
4214
6768
  }
4215
6769
  return true;
4216
6770
  }
4217
- function settledConflict(settled, reservation, options) {
6771
+ function settledConflict(settled2, reservation, options) {
4218
6772
  return {
4219
6773
  kind: "conflict",
4220
6774
  attempted: {
4221
6775
  salesPoint: reservation.salesPoint,
4222
6776
  voucherType: reservation.voucherType,
4223
- number: settled.number
6777
+ number: settled2.number
4224
6778
  },
4225
6779
  attempt: replayEvidence(reservation.service),
4226
- found: settled.found,
6780
+ found: settled2.found,
4227
6781
  reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key.",
4228
6782
  ...reservationEvidence(reservation, options)
4229
6783
  };
@@ -4245,6 +6799,15 @@ function validateKeyStore(options, context) {
4245
6799
  }
4246
6800
  }
4247
6801
  function readRecord(json) {
6802
+ if (isExportRecord(json)) {
6803
+ throw new ArcaInputError(
6804
+ "The idempotency key was already used for an export voucher.",
6805
+ {
6806
+ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
6807
+ field: "options.idempotencyKey"
6808
+ }
6809
+ );
6810
+ }
4248
6811
  try {
4249
6812
  const record = JSON.parse(json);
4250
6813
  if (!record || record.v !== 1 && record.v !== 2 || record.v === 2 && record.service === void 0 || !["issue", "creditNote", "debitNote"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || record.rejectedAt !== void 0 && typeof record.rejectedAt !== "string" || !Number.isSafeInteger(record.number) || record.number < 1 || record.number > 99999999 || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
@@ -4265,6 +6828,13 @@ function readRecord(json) {
4265
6828
  );
4266
6829
  }
4267
6830
  }
6831
+ function isExportRecord(json) {
6832
+ try {
6833
+ return JSON.parse(json)?.service === "wsfex";
6834
+ } catch {
6835
+ return false;
6836
+ }
6837
+ }
4268
6838
  function preparedFromRecord(record) {
4269
6839
  const sentTotal = Number(
4270
6840
  normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
@@ -4298,7 +6868,7 @@ async function nextNumber(wsfe, data, options) {
4298
6868
  return number;
4299
6869
  }
4300
6870
  async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, taxId, reservedNumber, { lookupFirst = false, strangerAtNumber = false } = {}) {
4301
- const auth = {
6871
+ const auth2 = {
4302
6872
  representedTaxId: options.representedTaxId,
4303
6873
  forceRefresh: options.forceRefresh,
4304
6874
  ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
@@ -4311,7 +6881,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
4311
6881
  number
4312
6882
  };
4313
6883
  const includedRequest = requestEvidence(data, number, options);
4314
- const voucher = (cae, caeExpiry, lookup) => issuedVoucher(
6884
+ const voucher = (cae, caeExpiry, lookup) => issuedVoucher2(
4315
6885
  { attempted, voucherClass, data, amounts, taxId },
4316
6886
  cae,
4317
6887
  caeExpiry,
@@ -4319,7 +6889,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
4319
6889
  );
4320
6890
  const recovery = {
4321
6891
  wsfe,
4322
- auth,
6892
+ auth: auth2,
4323
6893
  data,
4324
6894
  attempted,
4325
6895
  includeRawResponse,
@@ -4331,7 +6901,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
4331
6901
  const attempt2 = replayEvidence(options.service);
4332
6902
  let lookup;
4333
6903
  try {
4334
- lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
6904
+ lookup = await wsfe.lookupVoucher({ ...auth2, ...attempted });
4335
6905
  } catch (error) {
4336
6906
  return {
4337
6907
  kind: "indeterminate",
@@ -4346,13 +6916,13 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
4346
6916
  }
4347
6917
  }
4348
6918
  let authorization = await wsfe.issue({
4349
- ...auth,
6919
+ ...auth2,
4350
6920
  data,
4351
6921
  voucherNumber: number
4352
6922
  });
4353
- if (authorization.kind === "indeterminate" && authorization.reason === "authentication_rejected" && auth.forceRefresh !== true) {
6923
+ if (authorization.kind === "indeterminate" && authorization.reason === "authentication_rejected" && auth2.forceRefresh !== true) {
4354
6924
  authorization = await wsfe.issue({
4355
- ...auth,
6925
+ ...auth2,
4356
6926
  forceRefresh: true,
4357
6927
  data,
4358
6928
  voucherNumber: number
@@ -4385,7 +6955,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
4385
6955
  return recoverInvoice({
4386
6956
  wsfe,
4387
6957
  service: options.service,
4388
- auth,
6958
+ auth: auth2,
4389
6959
  data,
4390
6960
  attempted,
4391
6961
  attempt,
@@ -4427,7 +6997,7 @@ function mayHoldNumber(issues, service = "wsfe") {
4427
6997
  }
4428
6998
  async function recoverInvoice({
4429
6999
  wsfe,
4430
- auth,
7000
+ auth: auth2,
4431
7001
  data,
4432
7002
  attempted,
4433
7003
  attempt,
@@ -4438,7 +7008,7 @@ async function recoverInvoice({
4438
7008
  service,
4439
7009
  strangerAtNumber = false
4440
7010
  }) {
4441
- if (suppliedLookup === void 0 && auth.abortSignal?.aborted) {
7011
+ if (suppliedLookup === void 0 && auth2.abortSignal?.aborted) {
4442
7012
  return {
4443
7013
  kind: "indeterminate",
4444
7014
  attempted,
@@ -4449,13 +7019,13 @@ async function recoverInvoice({
4449
7019
  }
4450
7020
  let lookup;
4451
7021
  try {
4452
- lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
7022
+ lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth2, ...attempted });
4453
7023
  } catch (error) {
4454
7024
  return {
4455
7025
  kind: "indeterminate",
4456
7026
  attempted,
4457
7027
  attempt,
4458
- lookup: auth.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) },
7028
+ lookup: auth2.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) },
4459
7029
  ...includedRequest
4460
7030
  };
4461
7031
  }
@@ -4675,7 +7245,6 @@ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "credi
4675
7245
  () => prepareNote(wsfe, note, options, context, kind),
4676
7246
  options,
4677
7247
  context,
4678
- void 0,
4679
7248
  select
4680
7249
  );
4681
7250
  }
@@ -4792,8 +7361,7 @@ function assertBounds(value, bounds, field, method) {
4792
7361
  }
4793
7362
  }
4794
7363
  }
4795
- async function lookupVoucher(wsfe, voucher, inputOptions) {
4796
- const options = cloneOptions(inputOptions);
7364
+ function assertLookup(voucher, options) {
4797
7365
  assertIssueKeys(
4798
7366
  options,
4799
7367
  ["representedTaxId", "service", "forceRefresh", "abortSignal"],
@@ -4808,6 +7376,26 @@ async function lookupVoucher(wsfe, voucher, inputOptions) {
4808
7376
  "lookup()"
4809
7377
  );
4810
7378
  assertBounds(voucher, LOOKUP_BOUNDS, "voucher", "lookup()");
7379
+ }
7380
+ function assertSequence(sequence, options) {
7381
+ assertIssueKeys(
7382
+ options,
7383
+ ["representedTaxId", "service", "forceRefresh", "abortSignal"],
7384
+ "options",
7385
+ "lastAuthorized()"
7386
+ );
7387
+ assertIssueObject(sequence, "sequence");
7388
+ assertIssueKeys(
7389
+ sequence,
7390
+ ["salesPoint", "voucherType"],
7391
+ "sequence",
7392
+ "lastAuthorized()"
7393
+ );
7394
+ assertBounds(sequence, SEQUENCE_BOUNDS, "sequence", "lastAuthorized()");
7395
+ }
7396
+ async function lookupVoucher(wsfe, voucher, inputOptions) {
7397
+ const options = cloneOptions(inputOptions);
7398
+ assertLookup(voucher, options);
4811
7399
  const found = await wsfe.lookupVoucher({
4812
7400
  representedTaxId: options.representedTaxId,
4813
7401
  forceRefresh: options.forceRefresh,
@@ -4829,20 +7417,7 @@ async function lookupVoucher(wsfe, voucher, inputOptions) {
4829
7417
  }
4830
7418
  async function lastAuthorizedNumber(wsfe, sequence, inputOptions) {
4831
7419
  const options = cloneOptions(inputOptions);
4832
- assertIssueKeys(
4833
- options,
4834
- ["representedTaxId", "service", "forceRefresh", "abortSignal"],
4835
- "options",
4836
- "lastAuthorized()"
4837
- );
4838
- assertIssueObject(sequence, "sequence");
4839
- assertIssueKeys(
4840
- sequence,
4841
- ["salesPoint", "voucherType"],
4842
- "sequence",
4843
- "lastAuthorized()"
4844
- );
4845
- assertBounds(sequence, SEQUENCE_BOUNDS, "sequence", "lastAuthorized()");
7420
+ assertSequence(sequence, options);
4846
7421
  const next = await wsfe.getNextVoucherNumber({
4847
7422
  representedTaxId: options.representedTaxId,
4848
7423
  forceRefresh: options.forceRefresh,
@@ -4905,7 +7480,7 @@ function requestEvidence(data, number, options) {
4905
7480
  request: options.service === "wsmtxca" ? wsmtxcaRequest(data, number) : data
4906
7481
  } : {};
4907
7482
  }
4908
- async function recoverOperation(select, key, inputOptions, context) {
7483
+ async function recoverOperation(select, key, inputOptions, context, read) {
4909
7484
  const options = cloneOptions(inputOptions);
4910
7485
  assertIssueObject(options, "options");
4911
7486
  assertIssueKeys(
@@ -4917,9 +7492,7 @@ async function recoverOperation(select, key, inputOptions, context) {
4917
7492
  validateKeyStore({ ...options, idempotencyKey: key }, context);
4918
7493
  const scope = keyScope(context, key);
4919
7494
  const { store, environment, taxId } = scope;
4920
- const json = await storeCall(
4921
- () => store.get(attemptKey(environment, taxId, key))
4922
- );
7495
+ const json = read === void 0 ? await storeCall(() => store.get(attemptKey(environment, taxId, key))) : read;
4923
7496
  if (json === null) {
4924
7497
  throw new ArcaInputError("No reservation exists for this idempotency key", {
4925
7498
  code: "ARCA_INPUT_RESERVATION_NOT_FOUND",
@@ -4944,7 +7517,7 @@ async function recoverOperation(select, key, inputOptions, context) {
4944
7517
  return await recordConflict(
4945
7518
  store,
4946
7519
  settledKey(environment, taxId, key),
4947
- await consultReservation(select, current, options, taxId, rejected),
7520
+ await consultReservation2(select, current, options, taxId, rejected),
4948
7521
  !rejected || store.withLock !== void 0
4949
7522
  );
4950
7523
  }
@@ -4961,7 +7534,7 @@ function keyScope(context, idempotencyKey) {
4961
7534
  idempotencyKey
4962
7535
  };
4963
7536
  }
4964
- function consultReservation(select, record, options, taxId, strangerAtNumber = false) {
7537
+ function consultReservation2(select, record, options, taxId, strangerAtNumber = false) {
4965
7538
  const storedOptions = {
4966
7539
  ...options,
4967
7540
  service: record.service ?? "wsfe",
@@ -4987,7 +7560,7 @@ function consultReservation(select, record, options, taxId, strangerAtNumber = f
4987
7560
  record.number,
4988
7561
  storedOptions
4989
7562
  ),
4990
- voucher: (cae, caeExpiry, lookup) => issuedVoucher(
7563
+ voucher: (cae, caeExpiry, lookup) => issuedVoucher2(
4991
7564
  {
4992
7565
  attempted,
4993
7566
  voucherClass: prepared.voucherClass,
@@ -5004,7 +7577,7 @@ function consultReservation(select, record, options, taxId, strangerAtNumber = f
5004
7577
  function issuerTaxId(options, context) {
5005
7578
  return options.representedTaxId === void 0 ? context?.taxId : String(options.representedTaxId);
5006
7579
  }
5007
- function issuedVoucher({
7580
+ function issuedVoucher2({
5008
7581
  attempted,
5009
7582
  voucherClass,
5010
7583
  data,
@@ -5100,8 +7673,8 @@ async function runSequenceBarrier({
5100
7673
  return {};
5101
7674
  }
5102
7675
  const owner = claimed.issuerTaxId ?? taxId;
5103
- const settled = settledKey(environment, owner, claimed.key);
5104
- if (await storeCall(() => store.get(settled)) !== null) {
7676
+ const settled2 = settledKey(environment, owner, claimed.key);
7677
+ if (await storeCall(() => store.get(settled2)) !== null) {
5105
7678
  return {};
5106
7679
  }
5107
7680
  const reservation = await storeCall(
@@ -5123,8 +7696,8 @@ async function runSequenceBarrier({
5123
7696
  };
5124
7697
  const outcome = await recordConflict(
5125
7698
  store,
5126
- settled,
5127
- await consultReservation(
7699
+ settled2,
7700
+ await consultReservation2(
5128
7701
  select,
5129
7702
  // This call's certificate may not be the one that claimed. Without a
5130
7703
  // represented CUIT, it would read its own numbering instead of the one
@@ -5155,7 +7728,7 @@ async function runSequenceBarrier({
5155
7728
  }
5156
7729
  await storeCall(
5157
7730
  () => store.add(
5158
- settled,
7731
+ settled2,
5159
7732
  JSON.stringify({
5160
7733
  v: 1,
5161
7734
  kind: "superseded",
@@ -5385,7 +7958,7 @@ function createArcaClient(config = {}) {
5385
7958
  );
5386
7959
  }
5387
7960
  const logger = createArcaLogger(normalizedConfig.logger);
5388
- const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
7961
+ const auth2 = createWsaaAuthModule({ config: normalizedConfig, logger });
5389
7962
  const soap = createSoapTransport({ config: normalizedConfig, logger });
5390
7963
  const publicConfig = Object.freeze({
5391
7964
  taxId: normalizedConfig.taxId,
@@ -5394,13 +7967,19 @@ function createArcaClient(config = {}) {
5394
7967
  retries: normalizedConfig.retries,
5395
7968
  retryDelay: normalizedConfig.retryDelay
5396
7969
  });
5397
- const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
7970
+ const wsfe = createWsfeService({ config: normalizedConfig, auth: auth2, soap });
5398
7971
  const wsmtxca = createWsmtxcaService({
5399
7972
  config: normalizedConfig,
5400
- auth,
7973
+ auth: auth2,
5401
7974
  soap
5402
7975
  });
5403
- const vouchers = createVouchersService(wsfe, normalizedConfig, wsmtxca);
7976
+ const wsfex = createWsfexService({ config: normalizedConfig, auth: auth2, soap });
7977
+ const vouchers = createVouchersService(
7978
+ wsfe,
7979
+ normalizedConfig,
7980
+ wsmtxca,
7981
+ wsfex
7982
+ );
5404
7983
  return {
5405
7984
  config: publicConfig,
5406
7985
  issue: vouchers.issue,
@@ -5413,8 +7992,9 @@ function createArcaClient(config = {}) {
5413
7992
  lookup: vouchers.lookup,
5414
7993
  lastAuthorized: vouchers.lastAuthorized,
5415
7994
  wsfe,
7995
+ wsfex,
5416
7996
  wsmtxca,
5417
- padron: createPadronService({ config: normalizedConfig, auth, soap })
7997
+ padron: createPadronService({ config: normalizedConfig, auth: auth2, soap })
5418
7998
  };
5419
7999
  }
5420
8000
 
@@ -5432,71 +8012,6 @@ import {
5432
8012
  writeFile
5433
8013
  } from "fs/promises";
5434
8014
  import { join } from "path";
5435
-
5436
- // src/store/lock.ts
5437
- import { randomUUID } from "crypto";
5438
- var ARCA_LEASE_MS = 6e4;
5439
- var RENEW_MS = 2e4;
5440
- var POLL_MS = 50;
5441
- var MAX_WAIT_MS = 2 * ARCA_LEASE_MS;
5442
- async function withLease(key, driver, fn, { signal } = {}) {
5443
- const owner = randomUUID();
5444
- const deadline = Date.now() + MAX_WAIT_MS;
5445
- throwIfAborted(key, signal);
5446
- let held = await driver.acquire(owner);
5447
- while (!held) {
5448
- if (Date.now() >= deadline) {
5449
- throw new ArcaLockTimeoutError(
5450
- `ARCA store lock ${key} stayed held; no work was attempted.`,
5451
- { reason: "held" }
5452
- );
5453
- }
5454
- await delay2(POLL_MS + Math.floor(Math.random() * POLL_MS), signal);
5455
- throwIfAborted(key, signal);
5456
- held = await driver.acquire(owner);
5457
- }
5458
- if (signal?.aborted) {
5459
- await driver.release(owner).catch(() => void 0);
5460
- throwIfAborted(key, signal);
5461
- }
5462
- const renewal = setInterval(() => {
5463
- driver.renew(owner).catch(() => void 0);
5464
- }, RENEW_MS);
5465
- renewal.unref?.();
5466
- try {
5467
- return await fn();
5468
- } finally {
5469
- clearInterval(renewal);
5470
- await driver.release(owner).catch(() => void 0);
5471
- }
5472
- }
5473
- function throwIfAborted(key, signal) {
5474
- if (signal?.aborted) {
5475
- throw new ArcaLockTimeoutError(
5476
- `Stopped waiting for ARCA store lock ${key}: the caller's signal aborted; no work was attempted.`,
5477
- { reason: "aborted", cause: signal.reason }
5478
- );
5479
- }
5480
- }
5481
- function delay2(ms, signal) {
5482
- if (signal?.aborted) {
5483
- return Promise.resolve();
5484
- }
5485
- return new Promise((resolve) => {
5486
- const wake = () => {
5487
- clearTimeout(timer);
5488
- resolve();
5489
- };
5490
- const timer = setTimeout(() => {
5491
- signal?.removeEventListener("abort", wake);
5492
- resolve();
5493
- }, ms);
5494
- timer.unref?.();
5495
- signal?.addEventListener("abort", wake, { once: true });
5496
- });
5497
- }
5498
-
5499
- // src/store/file.ts
5500
8015
  function createFileStore(directory) {
5501
8016
  const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
5502
8017
  const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
@@ -5545,7 +8060,7 @@ function createFileStore(directory) {
5545
8060
  withLock: (key, fn, options) => (
5546
8061
  // A lock directory: mkdir is the atomic claim on every POSIX filesystem
5547
8062
  // and on Windows, and the holder file inside carries the lease.
5548
- withLease(key, fileLease(`${path(key)}.lock`, ensure), fn, options)
8063
+ withLease(fileLease(`${path(key)}.lock`, ensure), fn, options)
5549
8064
  )
5550
8065
  };
5551
8066
  }
@@ -5716,19 +8231,20 @@ export {
5716
8231
  discoverArcaClientConfig,
5717
8232
  storeCall,
5718
8233
  describeVoucherType,
8234
+ ARCA_LEASE_MS,
8235
+ withLease,
8236
+ throwIfAborted,
5719
8237
  ARCA_QR_URL,
5720
8238
  arcaQrUrl,
5721
8239
  arcaQrPayload,
5722
8240
  voucherDateWindow,
8241
+ createWsfexService,
5723
8242
  matchWsfeVoucherIdentity,
5724
8243
  abortable,
5725
8244
  createMemoryWsaaSessionStore,
5726
8245
  createWsaaAuthModule,
5727
8246
  createWsaaStoreAdapter,
5728
8247
  createArcaClient,
5729
- ARCA_LEASE_MS,
5730
- withLease,
5731
- throwIfAborted,
5732
8248
  createFileStore
5733
8249
  };
5734
- //# sourceMappingURL=chunk-WEEV76IC.mjs.map
8250
+ //# sourceMappingURL=chunk-R4MOVPYT.mjs.map