facturas 0.12.3 → 0.13.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 (44) hide show
  1. package/README.md +41 -35
  2. package/dist/{chunk-QHYJ424J.mjs → chunk-BPBTQDCO.mjs} +18 -175
  3. package/dist/chunk-BPBTQDCO.mjs.map +1 -0
  4. package/dist/{chunk-ZOSNASXB.mjs → chunk-BYEP3ABS.mjs} +1 -1
  5. package/dist/chunk-BYEP3ABS.mjs.map +1 -0
  6. package/dist/{chunk-G7BWMDFT.mjs → chunk-KPWAQMR5.mjs} +881 -216
  7. package/dist/chunk-KPWAQMR5.mjs.map +1 -0
  8. package/dist/{chunk-GX5ERXT6.mjs → chunk-LQMNKQ2V.mjs} +1 -1
  9. package/dist/chunk-LQMNKQ2V.mjs.map +1 -0
  10. package/dist/{chunk-UDX6HLAU.mjs → chunk-PHXH34L6.mjs} +2 -2
  11. package/dist/{chunk-ZPVXXSUC.mjs → chunk-WN62NMUJ.mjs} +15 -15
  12. package/dist/chunk-WN62NMUJ.mjs.map +1 -0
  13. package/dist/{chunk-7LEKQ4P7.mjs → chunk-WVKVJTFG.mjs} +2 -2
  14. package/dist/cli.d.ts +5 -5
  15. package/dist/cli.mjs +7 -7
  16. package/dist/{client-BT_F1N8o.d.ts → client-bblLZeUb.d.ts} +87 -45
  17. package/dist/constants.d.ts +1 -1
  18. package/dist/constants.mjs +1 -1
  19. package/dist/errors.d.ts +2 -2
  20. package/dist/errors.mjs +1 -1
  21. package/dist/{fiscal-evidence-Dpj-Iao8.d.ts → fiscal-evidence-DaCyGd5o.d.ts} +3 -3
  22. package/dist/{index-CT1ffEAL.d.ts → index-DAC3edtw.d.ts} +1 -1
  23. package/dist/index.d.ts +6 -7
  24. package/dist/index.mjs +11 -17
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/padron.d.ts +2 -2
  27. package/dist/padron.mjs +2 -2
  28. package/dist/{types-SdBQAflw.d.ts → types-Su7QwloQ.d.ts} +1 -1
  29. package/dist/types.d.ts +1 -1
  30. package/dist/wsfe.d.ts +255 -31
  31. package/dist/wsfe.mjs +3 -10
  32. package/dist/wsmtxca.d.ts +6 -6
  33. package/dist/wsmtxca.mjs +3 -3
  34. package/package.json +1 -1
  35. package/dist/chunk-G7BWMDFT.mjs.map +0 -1
  36. package/dist/chunk-GX5ERXT6.mjs.map +0 -1
  37. package/dist/chunk-QHYJ424J.mjs.map +0 -1
  38. package/dist/chunk-VGNLXU4T.mjs +0 -169
  39. package/dist/chunk-VGNLXU4T.mjs.map +0 -1
  40. package/dist/chunk-ZOSNASXB.mjs.map +0 -1
  41. package/dist/chunk-ZPVXXSUC.mjs.map +0 -1
  42. package/dist/decimal-xie0oMOQ.d.ts +0 -272
  43. /package/dist/{chunk-UDX6HLAU.mjs.map → chunk-PHXH34L6.mjs.map} +0 -0
  44. /package/dist/{chunk-7LEKQ4P7.mjs.map → chunk-WVKVJTFG.mjs.map} +0 -0
@@ -1,29 +1,30 @@
1
+ import {
2
+ ARCA_CURRENCY_IDS,
3
+ ARCA_DOCUMENT_TYPES,
4
+ ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS,
5
+ ARCA_INVOICE_CLASS_BY_ISSUER,
6
+ ARCA_ISSUER_CONDITION_IDS,
7
+ ARCA_RECEIVER_CONDITION_IDS,
8
+ ARCA_VAT_RATES,
9
+ ARCA_VOUCHER_TYPES
10
+ } from "./chunk-LQMNKQ2V.mjs";
1
11
  import {
2
12
  createPadronService
3
- } from "./chunk-7LEKQ4P7.mjs";
13
+ } from "./chunk-WVKVJTFG.mjs";
4
14
  import {
5
15
  arcaMinorUnitsToNumber,
6
16
  assertArcaMinorUnits,
7
- calculateWsfeAmounts,
8
17
  createWsfeService,
9
18
  isWithinArcaTolerance,
10
19
  normalizeArcaAmountToMinorUnits,
11
20
  normalizeWsfeDateInput,
12
21
  normalizeWsfeVoucherInput,
22
+ roundHalfEvenRatio,
13
23
  serializeArcaExchangeRate
14
- } from "./chunk-QHYJ424J.mjs";
15
- import {
16
- ARCA_CURRENCY_IDS,
17
- ARCA_DOCUMENT_TYPES,
18
- ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS,
19
- ARCA_INVOICE_CLASS_BY_ISSUER,
20
- ARCA_ISSUER_CONDITION_IDS,
21
- ARCA_RECEIVER_CONDITION_IDS,
22
- ARCA_VOUCHER_TYPES
23
- } from "./chunk-GX5ERXT6.mjs";
24
+ } from "./chunk-BPBTQDCO.mjs";
24
25
  import {
25
26
  createWsmtxcaService
26
- } from "./chunk-ZPVXXSUC.mjs";
27
+ } from "./chunk-WN62NMUJ.mjs";
27
28
  import {
28
29
  ArcaConfigurationError,
29
30
  ArcaError,
@@ -35,7 +36,7 @@ import {
35
36
  createResponseBodyDiagnostic,
36
37
  createSafeErrorDiagnostic,
37
38
  toArcaSafeErrorMetadata
38
- } from "./chunk-ZOSNASXB.mjs";
39
+ } from "./chunk-BYEP3ABS.mjs";
39
40
 
40
41
  // src/config.ts
41
42
  var ARCA_ENVIRONMENTS = ["production", "test"];
@@ -1190,7 +1191,7 @@ function createWsaaAuthModule(options) {
1190
1191
  }
1191
1192
  return {
1192
1193
  login(service, authOptions = {}) {
1193
- return abortable(runLogin(service, authOptions), authOptions.signal);
1194
+ return abortable(runLogin(service, authOptions), authOptions.abortSignal);
1194
1195
  }
1195
1196
  };
1196
1197
  function runLogin(service, authOptions) {
@@ -1698,6 +1699,17 @@ function validateIssuanceFields(fields) {
1698
1699
  if (fields.concept !== void 0 && !["products", "services", "products_and_services"].includes(fields.concept)) {
1699
1700
  bad("concept");
1700
1701
  }
1702
+ if (fields.fce !== void 0) {
1703
+ objectKeys(fields.fce, "fce", [
1704
+ "cbu",
1705
+ "alias",
1706
+ "transfer",
1707
+ "annulment",
1708
+ "reference"
1709
+ ]);
1710
+ validateFceOptions(fields.fce);
1711
+ }
1712
+ normalizedFceAnnulment(fields);
1701
1713
  }
1702
1714
  function positiveId(value, field) {
1703
1715
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
@@ -1772,6 +1784,7 @@ function applyFceFields(data, fce) {
1772
1784
  bad("fce");
1773
1785
  }
1774
1786
  validateFceOptions(fce);
1787
+ normalizedFceAnnulment({ fce, optionalFields: data.optionalFields });
1775
1788
  const extra = [
1776
1789
  ...fce.cbu === void 0 ? [] : [{ id: "2101", value: fce.cbu }],
1777
1790
  ...fce.alias === void 0 ? [] : [{ id: "2102", value: fce.alias }],
@@ -1785,6 +1798,26 @@ function applyFceFields(data, fce) {
1785
1798
  }
1786
1799
  data.optionalFields = options;
1787
1800
  }
1801
+ function normalizedFceAnnulment(fields) {
1802
+ const encoded = (fields.optionalFields ?? []).filter(
1803
+ (field) => field.id === "22"
1804
+ );
1805
+ if (encoded.length > 1) {
1806
+ bad("optionalFields");
1807
+ }
1808
+ const encodedValue = encoded[0]?.value;
1809
+ if (encodedValue !== void 0 && !["S", "N"].includes(encodedValue)) {
1810
+ bad("fce.annulment");
1811
+ }
1812
+ const direct = fields.fce?.annulment;
1813
+ if (direct !== void 0 && encodedValue !== void 0) {
1814
+ throw new ArcaInputError(
1815
+ direct === (encodedValue === "S") ? "FCE annulment is duplicated in fce and optionalFields." : "FCE annulment conflicts between fce and optionalFields.",
1816
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "fce.annulment" }
1817
+ );
1818
+ }
1819
+ return direct ?? (encodedValue === void 0 ? void 0 : encodedValue === "S");
1820
+ }
1788
1821
  function validateFceHeader(data) {
1789
1822
  const family = voucherFamily(data.voucherType);
1790
1823
  if (family.family === "fce") {
@@ -1833,53 +1866,36 @@ var iso = (value) => {
1833
1866
  return `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
1834
1867
  };
1835
1868
  function wsmtxcaRequest(data, number) {
1836
- if (!data.details?.length) {
1837
- invalid("details", "WSMTXCA requires detailed items");
1838
- }
1839
- const items = data.details.map((item, index) => {
1840
- if (!item || typeof item !== "object" || Object.keys(item).some(
1841
- (k) => ![
1842
- "description",
1843
- "quantity",
1844
- "unit",
1845
- "unitPrice",
1846
- "discount",
1847
- "vatCondition",
1848
- "vatAmount",
1849
- "amount",
1850
- "code",
1851
- "matrixCode",
1852
- "matrixUnits"
1853
- ].includes(k)
1854
- )) {
1855
- invalid(`details[${index}]`, "Invalid detailed item fields");
1856
- }
1857
- if (typeof item.description !== "string" || !(item.description.trim() && Number.isFinite(item.quantity)) || item.quantity <= 0 || !Number.isInteger(item.unit) || item.unit < 0 || typeof item.unitPrice !== "string" || !/^\d+(\.\d{1,6})?$/.test(item.unitPrice) || !Number.isInteger(item.vatCondition)) {
1858
- invalid(`details[${index}]`, "Invalid detailed item");
1859
- }
1860
- return {
1861
- unidadesMtx: item.matrixUnits,
1862
- codigoMtx: item.matrixCode,
1863
- codigo: item.code,
1864
- descripcion: item.description,
1865
- cantidad: item.quantity,
1866
- codigoUnidadMedida: item.unit,
1867
- precioUnitario: item.unitPrice,
1868
- importeBonificacion: minor(item.discount ?? 0, "details.discount"),
1869
- codigoCondicionIVA: item.vatCondition,
1870
- ...item.vatAmount === void 0 ? {} : { importeIVA: minor(item.vatAmount, "details.vatAmount") },
1871
- importeItem: minor(item.amount, "details.amount")
1872
- };
1873
- });
1874
- const itemTotal = data.details.reduce(
1875
- (sum, item) => sum + BigInt(item.amount),
1876
- 0n
1877
- );
1869
+ const legacy = data.details !== void 0;
1870
+ const authorized = data.authorizedLines !== void 0;
1871
+ const lines = data.lines ?? data.authorizedLines ?? data.details;
1872
+ const sources = [data.lines, data.authorizedLines, data.details].filter(
1873
+ (source) => source !== void 0
1874
+ ).length;
1875
+ if (!lines?.length || sources !== 1) {
1876
+ invalid("items", "WSMTXCA requires items with line detail");
1877
+ }
1878
+ const items = lines.map((line) => ({
1879
+ unidadesMtx: line.matrixUnits,
1880
+ codigoMtx: line.matrixCode,
1881
+ codigo: line.code,
1882
+ descripcion: line.description,
1883
+ cantidad: line.quantity,
1884
+ codigoUnidadMedida: line.unit,
1885
+ precioUnitario: line.unitPrice,
1886
+ // Old durable records omitted a zero discount. Rebuild the exact request
1887
+ // those releases sent without rewriting the stored fiscal evidence.
1888
+ importeBonificacion: minor(line.discount ?? 0, "items.discount"),
1889
+ codigoCondicionIVA: line.vatCondition,
1890
+ ...line.vatAmount === void 0 ? {} : { importeIVA: minor(line.vatAmount, "items.vatAmount") },
1891
+ importeItem: minor(line.amount, "items.amount")
1892
+ }));
1893
+ const itemTotal = lines.reduce((sum, line) => sum + BigInt(line.amount), 0n);
1878
1894
  const expected = normalizeArcaAmountToMinorUnits(data.totalAmount, "total") - normalizeArcaAmountToMinorUnits(data.taxAmount, "taxes");
1879
- if (!isWithinArcaTolerance(itemTotal, expected, 1)) {
1880
- invalid(
1881
- "details",
1882
- "Item totals must equal the voucher total excluding tributes"
1895
+ if (legacy || authorized ? !isWithinArcaTolerance(itemTotal, expected, 1) : itemTotal !== expected) {
1896
+ throw new ArcaError(
1897
+ "The derived WSMTXCA items do not sum to the voucher header excluding tributes. This is an SDK invariant failure.",
1898
+ "ARCA_ISSUE_INVARIANT"
1883
1899
  );
1884
1900
  }
1885
1901
  const tributes = data.taxes?.length ? data.taxes : void 0;
@@ -1974,7 +1990,7 @@ function rows(value, key) {
1974
1990
  function wsmtxcaHeader(found) {
1975
1991
  const raw = found.raw;
1976
1992
  const items = rows(raw.arrayItems, "item");
1977
- const details = items?.map((i) => ({
1993
+ const lines = items?.map((i) => ({
1978
1994
  description: String(i.descripcion ?? ""),
1979
1995
  quantity: Number(i.cantidad),
1980
1996
  unit: Number(i.codigoUnidadMedida),
@@ -2004,12 +2020,12 @@ function wsmtxcaHeader(found) {
2004
2020
  serviceStartDate: raw.fechaServicioDesde === void 0 ? void 0 : String(raw.fechaServicioDesde),
2005
2021
  serviceEndDate: raw.fechaServicioHasta === void 0 ? void 0 : String(raw.fechaServicioHasta),
2006
2022
  paymentDueDate: raw.fechaVencimientoPago === void 0 ? void 0 : String(raw.fechaVencimientoPago),
2007
- details,
2023
+ lines,
2008
2024
  vatRates: rows(raw.arraySubtotalesIVA, "subtotalIVA")?.map((v) => ({
2009
2025
  id: Number(v.codigo),
2010
2026
  amount: Number(v.importe),
2011
2027
  baseAmount: Number(
2012
- (details ?? []).filter((i) => i.vatCondition === Number(v.codigo)).reduce((sum, i) => sum + BigInt(i.amount), 0n) - normalizeArcaAmountToMinorUnits(Number(v.importe), "vat")
2028
+ (lines ?? []).filter((i) => i.vatCondition === Number(v.codigo)).reduce((sum, i) => sum + BigInt(i.amount), 0n) - normalizeArcaAmountToMinorUnits(Number(v.importe), "vat")
2013
2029
  ) / 100
2014
2030
  })),
2015
2031
  taxes: rows(raw.arrayOtrosTributos, "otroTributo")?.map((t) => ({
@@ -2150,7 +2166,7 @@ function createWsmtxcaIssuanceService(wsmtxca) {
2150
2166
  issue: (input) => wsmtxca.issue({
2151
2167
  representedTaxId: input.representedTaxId,
2152
2168
  forceRefresh: input.forceRefresh,
2153
- signal: input.signal,
2169
+ abortSignal: input.abortSignal,
2154
2170
  data: wsmtxcaRequest(input.data, input.voucherNumber)
2155
2171
  }),
2156
2172
  lookupVoucher: async (input) => {
@@ -2175,6 +2191,385 @@ function wsmtxcaAdditionalData(fields) {
2175
2191
  };
2176
2192
  }
2177
2193
 
2194
+ // src/services/wsfe-amounts.ts
2195
+ var RATES = {
2196
+ 0: { id: ARCA_VAT_RATES.IVA_0, basisPoints: 0n },
2197
+ 2.5: { id: ARCA_VAT_RATES.IVA_2_5, basisPoints: 250n },
2198
+ 5: { id: ARCA_VAT_RATES.IVA_5, basisPoints: 500n },
2199
+ 10.5: { id: ARCA_VAT_RATES.IVA_10_5, basisPoints: 1050n },
2200
+ 21: { id: ARCA_VAT_RATES.IVA_21, basisPoints: 2100n },
2201
+ 27: { id: ARCA_VAT_RATES.IVA_27, basisPoints: 2700n }
2202
+ };
2203
+ function calculateWsfeAmounts(input) {
2204
+ if (!Array.isArray(input.items) || input.items.length === 0) {
2205
+ invalidItem("items", "a non-empty array of items");
2206
+ }
2207
+ const isVat = input.voucherClass === "A" || input.voucherClass === "B";
2208
+ if (!(isVat || input.voucherClass === "C")) {
2209
+ invalidItem("voucherClass", "A, B or C");
2210
+ }
2211
+ const totals = collectItems(input.items, isVat);
2212
+ let net = totals.net;
2213
+ let vat = 0n;
2214
+ const { exempt, untaxed, groups } = totals;
2215
+ const vatRates = [];
2216
+ for (const [rate, group] of groups) {
2217
+ const { id, basisPoints } = RATES[rate];
2218
+ const netFromGross = roundHalfEvenRatio(
2219
+ group.gross * 10000n,
2220
+ 10000n + basisPoints
2221
+ );
2222
+ const base = group.net + netFromGross;
2223
+ const tax = roundHalfEvenRatio(group.net * basisPoints, 10000n) + group.gross - netFromGross;
2224
+ if (base === 0n) {
2225
+ continue;
2226
+ }
2227
+ net += base;
2228
+ vat += tax;
2229
+ vatRates.push({
2230
+ id,
2231
+ baseAmount: arcaMinorUnitsToNumber(base, "netAmount"),
2232
+ amount: arcaMinorUnitsToNumber(tax, "vatAmount")
2233
+ });
2234
+ }
2235
+ const computed = net + vat + exempt + untaxed;
2236
+ arcaMinorUnitsToNumber(computed, "totalAmount");
2237
+ const sent = input.total === void 0 ? computed : assertArcaMinorUnits(input.total, "total");
2238
+ const adjustment = sent - computed;
2239
+ const allowance = BigInt(vatRates.length);
2240
+ if (adjustment < -allowance || adjustment > allowance || vat + adjustment < 0n) {
2241
+ throw new ArcaInputError(
2242
+ "total does not match the computed amount within the VAT allowance.",
2243
+ {
2244
+ code: "ARCA_INPUT_AMOUNT_MISMATCH",
2245
+ field: "total",
2246
+ expected: `${computed} minor units (at most ${allowance} minor units of VAT adjustment, with non-negative VAT)`
2247
+ }
2248
+ );
2249
+ }
2250
+ return {
2251
+ data: {
2252
+ totalAmount: arcaMinorUnitsToNumber(sent, "totalAmount"),
2253
+ netAmount: arcaMinorUnitsToNumber(net, "netAmount"),
2254
+ vatAmount: arcaMinorUnitsToNumber(vat + adjustment, "vatAmount"),
2255
+ nonTaxableAmount: arcaMinorUnitsToNumber(untaxed, "nonTaxableAmount"),
2256
+ exemptAmount: arcaMinorUnitsToNumber(exempt, "exemptAmount"),
2257
+ taxAmount: 0,
2258
+ ...isVat ? { vatRates } : {}
2259
+ },
2260
+ amounts: {
2261
+ computedTotal: Number(computed),
2262
+ sentTotal: Number(sent),
2263
+ vatAdjustment: Number(adjustment)
2264
+ }
2265
+ };
2266
+ }
2267
+ function collectItems(items, isVat) {
2268
+ let net = 0n;
2269
+ let exempt = 0n;
2270
+ let untaxed = 0n;
2271
+ const groups = /* @__PURE__ */ new Map();
2272
+ for (const [index, item] of items.entries()) {
2273
+ const path = `items[${index}]`;
2274
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
2275
+ invalidItem(path, "an item object");
2276
+ }
2277
+ if (!isVat) {
2278
+ net += classCAmount(item, path);
2279
+ continue;
2280
+ }
2281
+ const { amount, field, rate } = vatItemAmount(item, path);
2282
+ if (rate === "exempt") {
2283
+ exempt += amount;
2284
+ continue;
2285
+ }
2286
+ if (rate === "untaxed") {
2287
+ untaxed += amount;
2288
+ continue;
2289
+ }
2290
+ const group = groups.get(rate) ?? { net: 0n, gross: 0n };
2291
+ group[field] += amount;
2292
+ groups.set(rate, group);
2293
+ }
2294
+ return { net, exempt, untaxed, groups };
2295
+ }
2296
+ function classCAmount(item, path) {
2297
+ if ("vat" in item || "net" in item || "gross" in item) {
2298
+ invalidItem("items", "amount items for a class C voucher");
2299
+ }
2300
+ return assertArcaMinorUnits(item.amount, `${path}.amount`);
2301
+ }
2302
+ function vatItemAmount(item, path) {
2303
+ if ("amount" in item || "net" in item === "gross" in item) {
2304
+ invalidItem(
2305
+ "items",
2306
+ "exactly one of net or gross, and vat, for a class A or B voucher"
2307
+ );
2308
+ }
2309
+ const field = "net" in item ? "net" : "gross";
2310
+ const amount = assertArcaMinorUnits(
2311
+ item[field],
2312
+ `${path}.${field}`
2313
+ );
2314
+ const rate = item.vat;
2315
+ if (rate !== "exempt" && rate !== "untaxed" && (typeof rate !== "number" || !Object.hasOwn(RATES, rate))) {
2316
+ invalidItem(`${path}.vat`, "0, 2.5, 5, 10.5, 21, 27, exempt, or untaxed");
2317
+ }
2318
+ return { amount, field, rate };
2319
+ }
2320
+ function invalidItem(field, expected) {
2321
+ throw new ArcaInputError(`${field} must be ${expected}.`, {
2322
+ code: "ARCA_INPUT_INVALID_VALUE",
2323
+ field,
2324
+ expected
2325
+ });
2326
+ }
2327
+ var UNTAXED_CONDITION = 1;
2328
+ var EXEMPT_CONDITION = 2;
2329
+ function deriveWsmtxcaSettlement(input, vatAdjustment) {
2330
+ if (!Array.isArray(input.items) || input.items.length === 0) {
2331
+ invalidItem("items", "a non-empty array of items");
2332
+ }
2333
+ const isVat = input.voucherClass === "A" || input.voucherClass === "B";
2334
+ const drafts = [];
2335
+ const groups = /* @__PURE__ */ new Map();
2336
+ for (const [index, item] of input.items.entries()) {
2337
+ const path = `items[${index}]`;
2338
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
2339
+ invalidItem(path, "an item object");
2340
+ }
2341
+ drafts.push(lineDraft(item, path, isVat, groups));
2342
+ }
2343
+ reconcileGroups(drafts, groups);
2344
+ absorbAdjustment(drafts, BigInt(vatAdjustment));
2345
+ const lines = drafts.map(({ line, vat, amount }) => ({
2346
+ ...line,
2347
+ amount: Number(amount),
2348
+ ...input.voucherClass === "A" ? { vatAmount: Number(vat) } : {}
2349
+ }));
2350
+ const vatByCondition = /* @__PURE__ */ new Map();
2351
+ for (const draft of drafts) {
2352
+ if (draft.rate === void 0) {
2353
+ continue;
2354
+ }
2355
+ const condition = RATES[draft.rate].id;
2356
+ vatByCondition.set(
2357
+ condition,
2358
+ (vatByCondition.get(condition) ?? 0) + Number(draft.vat)
2359
+ );
2360
+ }
2361
+ return { lines, vatByCondition };
2362
+ }
2363
+ function lineDraft(item, path, isVat, groups) {
2364
+ const line = assertItemLine(item, path);
2365
+ if (!isVat) {
2366
+ const amount2 = classCAmount(item, path);
2367
+ return {
2368
+ line: { ...line, vatCondition: RATES[0].id, amount: 0 },
2369
+ vat: 0n,
2370
+ amount: amount2,
2371
+ amountFormula: { numerator: amount2, denominator: 1n }
2372
+ };
2373
+ }
2374
+ const { amount, field, rate } = vatItemAmount(item, path);
2375
+ if (rate === "exempt" || rate === "untaxed") {
2376
+ return {
2377
+ line: {
2378
+ ...line,
2379
+ vatCondition: rate === "exempt" ? EXEMPT_CONDITION : UNTAXED_CONDITION,
2380
+ amount: 0
2381
+ },
2382
+ vat: 0n,
2383
+ amount,
2384
+ amountFormula: { numerator: amount, denominator: 1n }
2385
+ };
2386
+ }
2387
+ const { id, basisPoints } = RATES[rate];
2388
+ const group = groups.get(rate) ?? { net: 0n, gross: 0n };
2389
+ group[field] += amount;
2390
+ groups.set(rate, group);
2391
+ const vat = field === "gross" ? amount - roundHalfEvenRatio(amount * 10000n, 10000n + basisPoints) : roundHalfEvenRatio(amount * basisPoints, 10000n);
2392
+ return {
2393
+ line: { ...line, vatCondition: id, amount: 0 },
2394
+ vat,
2395
+ amount: field === "gross" ? amount : amount + vat,
2396
+ rate,
2397
+ field,
2398
+ vatFormula: field === "gross" ? {
2399
+ numerator: amount * basisPoints,
2400
+ denominator: 10000n + basisPoints
2401
+ } : { numerator: amount * basisPoints, denominator: 10000n },
2402
+ amountFormula: field === "gross" ? { numerator: amount, denominator: 1n } : {
2403
+ numerator: amount * (10000n + basisPoints),
2404
+ denominator: 10000n
2405
+ }
2406
+ };
2407
+ }
2408
+ function reconcileGroups(drafts, groups) {
2409
+ for (const [rate, group] of groups) {
2410
+ const { basisPoints } = RATES[rate];
2411
+ const netFromGross = roundHalfEvenRatio(
2412
+ group.gross * 10000n,
2413
+ 10000n + basisPoints
2414
+ );
2415
+ settle(
2416
+ drafts,
2417
+ (draft) => draft.rate === rate && draft.field === "net",
2418
+ roundHalfEvenRatio(group.net * basisPoints, 10000n) - sumVat(drafts, (d) => d.rate === rate && d.field === "net"),
2419
+ true
2420
+ );
2421
+ settle(
2422
+ drafts,
2423
+ (draft) => draft.rate === rate && draft.field === "gross",
2424
+ group.gross - netFromGross - sumVat(drafts, (d) => d.rate === rate && d.field === "gross"),
2425
+ false
2426
+ );
2427
+ }
2428
+ }
2429
+ function sumVat(drafts, of) {
2430
+ return drafts.reduce((sum, draft) => of(draft) ? sum + draft.vat : sum, 0n);
2431
+ }
2432
+ function settle(drafts, of, amount, movesItem) {
2433
+ let remaining = amount;
2434
+ const targets = drafts.filter(of);
2435
+ let cursor = 0;
2436
+ while (remaining !== 0n) {
2437
+ const step = remaining > 0n ? 1n : -1n;
2438
+ let target;
2439
+ for (let offset = 0; offset < targets.length; offset++) {
2440
+ const index = (cursor + offset) % targets.length;
2441
+ const candidate = targets[index];
2442
+ if (candidate && canMove(candidate, step, movesItem)) {
2443
+ target = candidate;
2444
+ cursor = (index + 1) % targets.length;
2445
+ break;
2446
+ }
2447
+ }
2448
+ if (target === void 0) {
2449
+ throw new ArcaError(
2450
+ "The derived WSMTXCA VAT does not fit its lines. This is an SDK invariant failure.",
2451
+ "ARCA_ISSUE_INVARIANT"
2452
+ );
2453
+ }
2454
+ applyMove(target, step, movesItem);
2455
+ remaining -= step;
2456
+ }
2457
+ }
2458
+ function canMove(draft, step, movesItem) {
2459
+ const vat = draft.vat + step;
2460
+ if (vat < 0n || draft.rate === 0 && vat !== 0n) {
2461
+ return false;
2462
+ }
2463
+ if (draft.vatFormula !== void 0 && !withinAbsoluteCent(vat, draft.vatFormula)) {
2464
+ return false;
2465
+ }
2466
+ return !movesItem || withinAbsoluteCent(draft.amount + step, draft.amountFormula);
2467
+ }
2468
+ function withinAbsoluteCent(value, formula) {
2469
+ const difference = value * formula.denominator - formula.numerator;
2470
+ return (difference < 0n ? -difference : difference) <= formula.denominator;
2471
+ }
2472
+ function applyMove(draft, step, movesItem) {
2473
+ draft.vat += step;
2474
+ if (movesItem) {
2475
+ draft.amount += step;
2476
+ }
2477
+ }
2478
+ function absorbAdjustment(drafts, adjustment) {
2479
+ if (adjustment === 0n) {
2480
+ return;
2481
+ }
2482
+ const step = adjustment > 0n ? 1n : -1n;
2483
+ let remaining = adjustment;
2484
+ const rates = [
2485
+ ...new Set(
2486
+ drafts.map((draft) => draft.rate).filter(
2487
+ (rate) => rate !== void 0 && rate > 0
2488
+ )
2489
+ )
2490
+ ];
2491
+ for (const rate of rates) {
2492
+ const target = drafts.find(
2493
+ (draft) => draft.rate === rate && canMove(draft, step, true)
2494
+ );
2495
+ if (target === void 0) {
2496
+ continue;
2497
+ }
2498
+ applyMove(target, step, true);
2499
+ remaining -= step;
2500
+ if (remaining === 0n) {
2501
+ return;
2502
+ }
2503
+ }
2504
+ throw new ArcaInputError(
2505
+ "total cannot be reconciled with WSMTXCA item formulas within ARCA's tolerance.",
2506
+ {
2507
+ code: "ARCA_INPUT_AMOUNT_MISMATCH",
2508
+ field: "total"
2509
+ }
2510
+ );
2511
+ }
2512
+ var ITEM_KEYS = [
2513
+ "net",
2514
+ "gross",
2515
+ "amount",
2516
+ "vat",
2517
+ "description",
2518
+ "quantity",
2519
+ "unit",
2520
+ "unitPrice",
2521
+ "discount",
2522
+ "code",
2523
+ "matrixCode",
2524
+ "matrixUnits"
2525
+ ];
2526
+ function assertItemLine(item, path) {
2527
+ assertRequiredLineFields(item, path);
2528
+ for (const key of ["code", "matrixCode"]) {
2529
+ if (item[key] !== void 0 && typeof item[key] !== "string") {
2530
+ invalidItem(`${path}.${key}`, "a string");
2531
+ }
2532
+ }
2533
+ if (item.matrixUnits !== void 0 && !Number.isFinite(item.matrixUnits)) {
2534
+ invalidItem(`${path}.matrixUnits`, "a number");
2535
+ }
2536
+ return {
2537
+ ...item.matrixUnits === void 0 ? {} : { matrixUnits: item.matrixUnits },
2538
+ ...item.matrixCode === void 0 ? {} : { matrixCode: item.matrixCode },
2539
+ ...item.code === void 0 ? {} : { code: item.code },
2540
+ description: item.description,
2541
+ quantity: item.quantity,
2542
+ unit: item.unit,
2543
+ unitPrice: item.unitPrice,
2544
+ discount: Number(
2545
+ assertArcaMinorUnits(item.discount ?? 0, `${path}.discount`)
2546
+ )
2547
+ };
2548
+ }
2549
+ function assertRequiredLineFields(item, path) {
2550
+ for (const key of Object.keys(item)) {
2551
+ if (!ITEM_KEYS.includes(key)) {
2552
+ invalidItem(`${path}.${key}`, "a supported item field");
2553
+ }
2554
+ }
2555
+ const { description, quantity, unit, unitPrice } = item;
2556
+ if (typeof description !== "string" || description.trim() === "") {
2557
+ invalidItem(`${path}.description`, "a non-empty description");
2558
+ }
2559
+ if (!Number.isFinite(quantity) || quantity <= 0) {
2560
+ invalidItem(`${path}.quantity`, "a positive quantity");
2561
+ }
2562
+ if (!Number.isInteger(unit) || unit < 0) {
2563
+ invalidItem(`${path}.unit`, "an ARCA unit of measure code");
2564
+ }
2565
+ if (typeof unitPrice !== "string" || !/^\d+(\.\d{1,6})?$/.test(unitPrice)) {
2566
+ invalidItem(
2567
+ `${path}.unitPrice`,
2568
+ "a major-unit decimal string with at most six decimals"
2569
+ );
2570
+ }
2571
+ }
2572
+
2178
2573
  // src/services/wsfe-derive.ts
2179
2574
  var INVOICE_TYPES = {
2180
2575
  A: ARCA_VOUCHER_TYPES.FACTURA_A,
@@ -2188,7 +2583,6 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
2188
2583
  [
2189
2584
  ...ISSUANCE_KEYS,
2190
2585
  "family",
2191
- "details",
2192
2586
  "issuer",
2193
2587
  "items",
2194
2588
  "salesPoint",
@@ -2209,11 +2603,12 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
2209
2603
  assertSalesPoint(input.salesPoint);
2210
2604
  const receiver = deriveReceiver(input.to);
2211
2605
  const voucherClass = resolveInvoiceClass(input.issuer, input.to.condition);
2212
- const { data: amountsData, amounts } = input.amounts ? reviewedInvoiceAmounts(input.amounts, input.taxes) : calculateWsfeAmounts({
2606
+ const lineSource = input.amounts ? void 0 : {
2213
2607
  voucherClass,
2214
2608
  items: input.items,
2215
2609
  total: input.total === void 0 ? void 0 : input.total - tributeTotal(input.taxes ?? [])
2216
- });
2610
+ };
2611
+ const { data: amountsData, amounts } = lineSource === void 0 ? reviewedInvoiceAmounts(input.amounts, input.taxes) : calculateWsfeAmounts(lineSource);
2217
2612
  const currency = deriveCurrency(input);
2218
2613
  const voucherDate = normalizeWsfeDateInput(
2219
2614
  input.date === void 0 ? buenosAiresDate(now) : input.date,
@@ -2258,12 +2653,17 @@ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
2258
2653
  throw cause;
2259
2654
  }
2260
2655
  throw new ArcaError(
2261
- "The derived invoice failed exact WSFE validation. This is an SDK invariant failure.",
2656
+ "The derived invoice failed WSFE validation. This is an SDK invariant failure.",
2262
2657
  "ARCA_ISSUE_INVARIANT",
2263
2658
  { cause }
2264
2659
  );
2265
2660
  }
2266
- return { data, voucherClass, amounts };
2661
+ return {
2662
+ data,
2663
+ voucherClass,
2664
+ amounts,
2665
+ ...lineSource === void 0 ? {} : { lineSource }
2666
+ };
2267
2667
  }
2268
2668
  function reviewedInvoiceAmounts(amounts, taxes) {
2269
2669
  const taxTotal = taxes === void 0 ? 0 : tributeTotal(taxes);
@@ -2438,7 +2838,7 @@ function assertIssueKeys(value, keys, prefix, method = "issue()") {
2438
2838
  throw new ArcaInputError(`${field} is not supported by ${method}.`, {
2439
2839
  code: "ARCA_INPUT_RESERVED_FIELD",
2440
2840
  field,
2441
- expected: "a supported high-level API field; use the exact API for other fiscal fields"
2841
+ expected: "a field the facade supports"
2442
2842
  });
2443
2843
  }
2444
2844
  }
@@ -2492,10 +2892,9 @@ function deriveNumericReceiver(to) {
2492
2892
 
2493
2893
  // src/services/wsfe-credit-note.ts
2494
2894
  function invalid3(reason) {
2495
- throw new ArcaInputError(
2496
- `issueCreditNote cannot proceed: ${reason}. Use the exact service API for manual control.`,
2497
- { code: "ARCA_INPUT_INVALID_VALUE" }
2498
- );
2895
+ throw new ArcaInputError(`issueCreditNote cannot proceed: ${reason}.`, {
2896
+ code: "ARCA_INPUT_INVALID_VALUE"
2897
+ });
2499
2898
  }
2500
2899
  function required(value, field) {
2501
2900
  if (value === void 0 || value === null) {
@@ -2504,7 +2903,7 @@ function required(value, field) {
2504
2903
  return value;
2505
2904
  }
2506
2905
  function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
2507
- const { note, header } = prepareCreditNote(original, input, now, kind);
2906
+ const { note, header } = prepareCreditNote([original], input, now, kind);
2508
2907
  const data = {
2509
2908
  ...header,
2510
2909
  ...original.taxes ? { taxes: structuredClone(original.taxes) } : {},
@@ -2526,24 +2925,32 @@ function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Dat
2526
2925
  amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
2527
2926
  };
2528
2927
  }
2529
- function deriveWsfePartialCreditNote(original, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
2928
+ function deriveWsfePartialCreditNote(originals, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
2530
2929
  if (input.items === void 0 && input.amounts === void 0) {
2531
2930
  invalid3("items is required to credit chosen lines");
2532
2931
  }
2533
2932
  const requestedTotal = input.total === void 0 ? void 0 : assertArcaMinorUnits(input.total, "total");
2534
- const { note, header } = prepareCreditNote(original, input, now, kind);
2535
- const { data: amountsData, amounts } = input.amounts ? reviewedInvoiceAmounts(input.amounts, input.taxes) : calculateWsfeAmounts({
2933
+ const { note, header } = prepareCreditNote(originals, input, now, kind);
2934
+ const lineSource = input.amounts ? void 0 : {
2536
2935
  voucherClass: note.voucherClass,
2537
2936
  items: input.items,
2538
2937
  total: input.total === void 0 ? void 0 : input.total - tributeTotal(input.taxes ?? [])
2539
- });
2540
- const originalTotal = normalizeArcaAmountToMinorUnits(
2541
- required(original.totalAmount, "totalAmount"),
2542
- "totalAmount"
2938
+ };
2939
+ const { data: amountsData, amounts } = lineSource === void 0 ? reviewedInvoiceAmounts(
2940
+ input.amounts,
2941
+ input.taxes
2942
+ ) : calculateWsfeAmounts(lineSource);
2943
+ const originalTotal = originals.reduce(
2944
+ (sum, original) => sum + normalizeArcaAmountToMinorUnits(
2945
+ required(original.totalAmount, "totalAmount"),
2946
+ "totalAmount"
2947
+ ),
2948
+ 0n
2543
2949
  );
2950
+ const ceiling = originals.length === 1 ? "the original" : "the sum of the originals";
2544
2951
  if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) > originalTotal) {
2545
2952
  invalid3(
2546
- "the note total is greater than the original; the SDK does not track earlier notes against an original"
2953
+ `the note total is greater than ${ceiling}; the SDK does not track earlier notes against an original`
2547
2954
  );
2548
2955
  }
2549
2956
  const data = { ...header, ...amountsData };
@@ -2566,15 +2973,55 @@ function deriveWsfePartialCreditNote(original, input, now = /* @__PURE__ */ new
2566
2973
  );
2567
2974
  const sentTotal = input.amounts && requestedTotal !== void 0 ? Number(requestedTotal) : total;
2568
2975
  if (kind === "creditNote" && BigInt(sentTotal) > originalTotal) {
2569
- invalid3("the note total is greater than the original");
2976
+ invalid3(`the note total is greater than ${ceiling}`);
2570
2977
  }
2571
2978
  data.totalAmount = minor(sentTotal, "total");
2572
2979
  amounts.computedTotal += total - amounts.sentTotal;
2573
2980
  amounts.sentTotal = sentTotal;
2574
2981
  normalizeWsfeVoucherInput(data);
2575
- return { data, voucherClass: note.voucherClass, amounts };
2982
+ return {
2983
+ data,
2984
+ voucherClass: note.voucherClass,
2985
+ amounts,
2986
+ ...lineSource === void 0 ? {} : { lineSource }
2987
+ };
2988
+ }
2989
+ function prepareCreditNote(originals, input, now, kind) {
2990
+ const derived = originals.map(
2991
+ (original) => prepareOneCreditNote(original, input, now, kind)
2992
+ );
2993
+ const [first, ...rest] = derived;
2994
+ if (first === void 0) {
2995
+ invalid3("for must name at least one original");
2996
+ }
2997
+ for (const other of rest) {
2998
+ assertInheritedHeader(first.header, other.header);
2999
+ }
3000
+ return {
3001
+ note: first.note,
3002
+ header: {
3003
+ ...first.header,
3004
+ associatedVouchers: derived.flatMap(
3005
+ (one) => one.header.associatedVouchers ?? []
3006
+ )
3007
+ }
3008
+ };
2576
3009
  }
2577
- function prepareCreditNote(original, input, now, kind) {
3010
+ function assertInheritedHeader(first, other) {
3011
+ for (const field of /* @__PURE__ */ new Set([...Object.keys(first), ...Object.keys(other)])) {
3012
+ if (field === "associatedVouchers") {
3013
+ continue;
3014
+ }
3015
+ const left = first[field];
3016
+ const right = other[field];
3017
+ if (JSON.stringify(left) !== JSON.stringify(right)) {
3018
+ invalid3(
3019
+ `the originals disagree on ${field}, which the note inherits from them`
3020
+ );
3021
+ }
3022
+ }
3023
+ }
3024
+ function prepareOneCreditNote(original, input, now, kind) {
2578
3025
  assertOriginalExtensions(original);
2579
3026
  const family = voucherFamily(original.voucherType ?? 0);
2580
3027
  if (family.types[2] === original.voucherType) {
@@ -2660,7 +3107,6 @@ var CREDIT_NOTE_KEYS = [
2660
3107
  "taxes",
2661
3108
  "amounts",
2662
3109
  "optionalFields",
2663
- "details",
2664
3110
  "fce"
2665
3111
  ];
2666
3112
  var TARGET_BOUNDS = [
@@ -2673,7 +3119,7 @@ function assertCreditNoteInput(input) {
2673
3119
  validateIssuanceFields(input);
2674
3120
  if ("associatedPeriod" in input) {
2675
3121
  throw new ArcaInputError(
2676
- "issueCreditNote does not support associatedPeriod; a note against a period is exact-layer work. Use the exact service API for manual control.",
3122
+ "issueCreditNote adjusts either the originals named in for or a period, never both; drop one of them.",
2677
3123
  {
2678
3124
  code: "ARCA_INPUT_RESERVED_FIELD",
2679
3125
  field: "associatedPeriod",
@@ -2682,7 +3128,7 @@ function assertCreditNoteInput(input) {
2682
3128
  );
2683
3129
  }
2684
3130
  assertIssueKeys(input, CREDIT_NOTE_KEYS, "input", "issueCreditNote()");
2685
- const target = assertCreditNoteTarget(input.for);
3131
+ const target = assertCreditNoteTargets(input.for);
2686
3132
  if (input.salesPoint !== void 0) {
2687
3133
  assertCreditNoteBound(input.salesPoint, 99999, "salesPoint");
2688
3134
  }
@@ -2690,7 +3136,6 @@ function assertCreditNoteInput(input) {
2690
3136
  const common = {
2691
3137
  ...input.fce === void 0 ? {} : { fce: structuredClone(input.fce) },
2692
3138
  ...input.taxes === void 0 ? {} : { taxes: structuredClone(input.taxes) },
2693
- ...input.details === void 0 ? {} : { details: structuredClone(input.details) },
2694
3139
  ...input.optionalFields === void 0 ? {} : { optionalFields: structuredClone(input.optionalFields) },
2695
3140
  for: target,
2696
3141
  ...input.salesPoint === void 0 ? {} : { salesPoint: input.salesPoint },
@@ -2727,13 +3172,47 @@ function assertCreditNoteInput(input) {
2727
3172
  ...input.total === void 0 ? {} : { total: input.total }
2728
3173
  };
2729
3174
  }
2730
- function assertCreditNoteTarget(value) {
3175
+ function assertCreditNoteTargets(value) {
3176
+ if (Array.isArray(value)) {
3177
+ if (value.length === 0) {
3178
+ throw new ArcaInputError(
3179
+ "issueCreditNote requires for to name at least one authorized original.",
3180
+ {
3181
+ code: "ARCA_INPUT_MISSING_FIELD",
3182
+ field: "input.for",
3183
+ expected: "a non-empty array of { salesPoint, voucherType, number }"
3184
+ }
3185
+ );
3186
+ }
3187
+ const targets = value.map(
3188
+ (target, index) => assertCreditNoteTarget(target, `for[${index}]`)
3189
+ );
3190
+ const seen = /* @__PURE__ */ new Set();
3191
+ for (const [index, target] of targets.entries()) {
3192
+ const key = `${target.salesPoint}:${target.voucherType}:${target.number}`;
3193
+ if (seen.has(key)) {
3194
+ throw new ArcaInputError(
3195
+ "issueCreditNote requires each original in for to be unique.",
3196
+ {
3197
+ code: "ARCA_INPUT_INVALID_VALUE",
3198
+ field: `input.for[${index}]`,
3199
+ expected: "unique voucher coordinates"
3200
+ }
3201
+ );
3202
+ }
3203
+ seen.add(key);
3204
+ }
3205
+ return targets;
3206
+ }
3207
+ return assertCreditNoteTarget(value, "for");
3208
+ }
3209
+ function assertCreditNoteTarget(value, path) {
2731
3210
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
2732
3211
  throw new ArcaInputError(
2733
3212
  "issueCreditNote requires for: the coordinates of the authorized invoice the note corrects.",
2734
3213
  {
2735
3214
  code: "ARCA_INPUT_MISSING_FIELD",
2736
- field: "input.for",
3215
+ field: `input.${path}`,
2737
3216
  expected: "{ salesPoint, voucherType, number }"
2738
3217
  }
2739
3218
  );
@@ -2741,20 +3220,20 @@ function assertCreditNoteTarget(value) {
2741
3220
  assertIssueKeys(
2742
3221
  value,
2743
3222
  ["salesPoint", "voucherType", "number"],
2744
- "input.for",
3223
+ `input.${path}`,
2745
3224
  "issueCreditNote()"
2746
3225
  );
2747
3226
  for (const [field, max] of TARGET_BOUNDS) {
2748
- assertCreditNoteBound(value[field], max, `for.${field}`);
3227
+ assertCreditNoteBound(value[field], max, `${path}.${field}`);
2749
3228
  }
2750
3229
  if (![1, 2, 6, 7, 11, 12, 51, 52, 201, 202, 206, 207, 211, 212].includes(
2751
3230
  value.voucherType
2752
3231
  )) {
2753
3232
  throw new ArcaInputError(
2754
- "issueCreditNote requires an authorized invoice or debit note in a supported family in for.voucherType. Use the exact service API for manual control.",
3233
+ "issueCreditNote requires an authorized invoice or debit note in a supported family in for.voucherType.",
2755
3234
  {
2756
3235
  code: "ARCA_INPUT_INVALID_VALUE",
2757
- field: "input.for.voucherType",
3236
+ field: `input.${path}.voucherType`,
2758
3237
  expected: "1, 6 or 11"
2759
3238
  }
2760
3239
  );
@@ -2765,6 +3244,9 @@ function assertCreditNoteTarget(value) {
2765
3244
  number: value.number
2766
3245
  };
2767
3246
  }
3247
+ function creditNoteTargets(input) {
3248
+ return Array.isArray(input.for) ? input.for : [input.for];
3249
+ }
2768
3250
  function assertCreditNoteBound(value, max, path) {
2769
3251
  if (!Number.isSafeInteger(value) || value < 1 || value > max) {
2770
3252
  throw new ArcaInputError(
@@ -2852,6 +3334,16 @@ function assertFullMode(input) {
2852
3334
  }
2853
3335
  );
2854
3336
  }
3337
+ if (Array.isArray(input.for) && input.for.length > 1) {
3338
+ throw new ArcaInputError(
3339
+ "issueCreditNote takes all: true against one original; a full note of several originals has no single total. Pass items or amounts instead.",
3340
+ {
3341
+ code: "ARCA_INPUT_INVALID_VALUE",
3342
+ field: "input.all",
3343
+ expected: "one original in for when all is true"
3344
+ }
3345
+ );
3346
+ }
2855
3347
  }
2856
3348
 
2857
3349
  // src/services/vouchers.ts
@@ -2938,21 +3430,37 @@ function previewInvoice(input, options) {
2938
3430
  }
2939
3431
  function prepareInvoice(input, options) {
2940
3432
  const prepared = deriveWsfeInvoice(input);
2941
- if (input.details !== void 0) {
2942
- prepared.data.details = structuredClone(input.details);
2943
- }
3433
+ attachLines(prepared, options);
2944
3434
  validatePrepared(prepared, options);
2945
3435
  return prepared;
2946
3436
  }
3437
+ function attachLines(prepared, options) {
3438
+ if (options.service !== "wsmtxca") {
3439
+ return;
3440
+ }
3441
+ if (prepared.lineSource === void 0) {
3442
+ throw new ArcaInputError(
3443
+ "WSMTXCA needs items with line detail; a reviewed amounts breakdown has no lines.",
3444
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "amounts" }
3445
+ );
3446
+ }
3447
+ const settlement = deriveWsmtxcaSettlement(
3448
+ prepared.lineSource,
3449
+ prepared.amounts.vatAdjustment
3450
+ );
3451
+ prepared.data.lines = settlement.lines;
3452
+ prepared.data.vatRates = prepared.data.vatRates?.map((rate) => ({
3453
+ ...rate,
3454
+ amount: arcaMinorUnitsToNumber(
3455
+ BigInt(settlement.vatByCondition.get(rate.id) ?? 0),
3456
+ "vatAmount"
3457
+ )
3458
+ }));
3459
+ }
2947
3460
  function validatePrepared(prepared, options) {
2948
3461
  validateFiscalHeader(prepared.data);
2949
3462
  if (options.service === "wsmtxca") {
2950
3463
  wsmtxcaRequest(prepared.data);
2951
- } else if (prepared.data.details !== void 0) {
2952
- throw new ArcaInputError("Detailed items require service: wsmtxca", {
2953
- code: "ARCA_INPUT_INVALID_VALUE",
2954
- field: "details"
2955
- });
2956
3464
  }
2957
3465
  }
2958
3466
  function toPreview(prepared, options) {
@@ -3038,12 +3546,15 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3038
3546
  supersededBy: idempotencyKey,
3039
3547
  readNext: () => nextNumber(wsfe, prepared.data, options)
3040
3548
  });
3041
- return "blocked" in barrier ? barrier.blocked : await claim(barrier.reserved);
3549
+ return "blocked" in barrier ? {
3550
+ ...barrier.blocked,
3551
+ ...requestEvidence(prepared.data, void 0, options)
3552
+ } : await claim(barrier.reserved);
3042
3553
  }
3043
3554
  );
3044
3555
  function reservation(number) {
3045
3556
  const service = options.service ?? "wsfe";
3046
- const versioned = service === "wsmtxca" || prepared.data.details !== void 0;
3557
+ const versioned = service === "wsmtxca" || prepared.data.lines !== void 0;
3047
3558
  return {
3048
3559
  v: versioned ? 2 : 1,
3049
3560
  operation,
@@ -3075,7 +3586,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3075
3586
  await storeCall(() => store.set(sequenceRecord, JSON.stringify(claimed)));
3076
3587
  }
3077
3588
  if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
3078
- const outcome = await settle(
3589
+ const outcome = await settle2(
3079
3590
  runAuthorization(wsfe, prepared, options, number)
3080
3591
  );
3081
3592
  if (coordinated && outcome.kind !== "indeterminate") {
@@ -3118,7 +3629,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3118
3629
  (other) => storeCall(() => store.get(settledKey(environment, taxId, other)))
3119
3630
  );
3120
3631
  }
3121
- return await settle(
3632
+ return await settle2(
3122
3633
  runAuthorization(
3123
3634
  wsfe,
3124
3635
  {
@@ -3144,7 +3655,7 @@ async function runOperation(wsfe, operation, input, prepare, options, context, r
3144
3655
  consult
3145
3656
  );
3146
3657
  }
3147
- async function settle(running) {
3658
+ async function settle2(running) {
3148
3659
  return await recordConflict(store, settled, await running);
3149
3660
  }
3150
3661
  }
@@ -3157,7 +3668,7 @@ async function recordConflict(store, key, outcome) {
3157
3668
  kind: "conflict",
3158
3669
  number: outcome.attempted.number,
3159
3670
  found: Object.fromEntries(
3160
- Object.entries(outcome.found).filter(([field]) => field !== "raw")
3671
+ Object.entries(outcome.found).filter(([field]) => field !== "rawResponse")
3161
3672
  ),
3162
3673
  settledAt: (/* @__PURE__ */ new Date()).toISOString()
3163
3674
  };
@@ -3180,7 +3691,7 @@ function readSettledRecord(json) {
3180
3691
  }
3181
3692
  async function settledOutcome(select, reservation, settled, options, settledFor) {
3182
3693
  if (settled.kind === "conflict") {
3183
- return settledConflict(settled, reservation);
3694
+ return settledConflict(settled, reservation, options);
3184
3695
  }
3185
3696
  const outcome = await consultReservation(select, reservation, options, true);
3186
3697
  const empty = outcome.kind === "indeterminate" && outcome.lookup.kind === "not_found";
@@ -3195,7 +3706,12 @@ async function settledOutcome(select, reservation, settled, options, settledFor)
3195
3706
  number: settled.number
3196
3707
  },
3197
3708
  attempt: replayEvidence(reservation.service),
3198
- lookup: { kind: "superseded", by: settled.by }
3709
+ lookup: { kind: "superseded", by: settled.by },
3710
+ ...requestEvidence(
3711
+ preparedFromRecord(reservation).data,
3712
+ reservation.number,
3713
+ { ...options, service: reservation.service ?? "wsfe" }
3714
+ )
3199
3715
  };
3200
3716
  }
3201
3717
  async function successionDisputed(key, settledFor) {
@@ -3213,7 +3729,7 @@ async function successionDisputed(key, settledFor) {
3213
3729
  }
3214
3730
  return true;
3215
3731
  }
3216
- function settledConflict(settled, reservation) {
3732
+ function settledConflict(settled, reservation, options) {
3217
3733
  return {
3218
3734
  kind: "conflict",
3219
3735
  attempted: {
@@ -3223,7 +3739,12 @@ function settledConflict(settled, reservation) {
3223
3739
  },
3224
3740
  attempt: replayEvidence(reservation.service),
3225
3741
  found: settled.found,
3226
- reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key."
3742
+ reason: "This key already recorded another voucher at the reserved number. Reconcile before issuing under a new key.",
3743
+ ...requestEvidence(
3744
+ preparedFromRecord(reservation).data,
3745
+ reservation.number,
3746
+ { ...options, service: reservation.service ?? "wsfe" }
3747
+ )
3227
3748
  };
3228
3749
  }
3229
3750
  function validateKeyStore(options, context) {
@@ -3280,7 +3801,7 @@ async function nextNumber(wsfe, data, options) {
3280
3801
  const number = await wsfe.getNextVoucherNumber({
3281
3802
  representedTaxId: options.representedTaxId,
3282
3803
  forceRefresh: options.forceRefresh,
3283
- ...options.signal === void 0 ? {} : { signal: options.signal },
3804
+ ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal },
3284
3805
  salesPoint: data.salesPoint,
3285
3806
  voucherType: data.voucherType
3286
3807
  });
@@ -3299,17 +3820,16 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3299
3820
  const auth = {
3300
3821
  representedTaxId: options.representedTaxId,
3301
3822
  forceRefresh: options.forceRefresh,
3302
- ...options.signal === void 0 ? {} : { signal: options.signal }
3823
+ ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
3303
3824
  };
3304
- const includeRaw = options.include?.raw === true;
3305
- const includeExact = options.include?.exactInput === true;
3825
+ const includeRawResponse = options.include?.rawResponse === true;
3306
3826
  const number = reservedNumber ?? await nextNumber(wsfe, data, options);
3307
3827
  const attempted = {
3308
3828
  salesPoint: data.salesPoint,
3309
3829
  voucherType: data.voucherType,
3310
3830
  number
3311
3831
  };
3312
- const exact = exactEvidence(data, number, options, includeExact);
3832
+ const includedRequest = requestEvidence(data, number, options);
3313
3833
  const voucher = (cae, caeExpiry) => ({
3314
3834
  ...attempted,
3315
3835
  voucherClass,
@@ -3323,8 +3843,8 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3323
3843
  auth,
3324
3844
  data,
3325
3845
  attempted,
3326
- includeRaw,
3327
- exact,
3846
+ includeRawResponse,
3847
+ includedRequest,
3328
3848
  voucher,
3329
3849
  service: options.service
3330
3850
  };
@@ -3338,7 +3858,8 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3338
3858
  kind: "indeterminate",
3339
3859
  attempted,
3340
3860
  attempt: attempt2,
3341
- lookup: options.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3861
+ lookup: options.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) },
3862
+ ...includedRequest
3342
3863
  };
3343
3864
  }
3344
3865
  if (lookup.kind === "found") {
@@ -3355,8 +3876,8 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3355
3876
  kind: "authorized",
3356
3877
  recoveredByMatch: false,
3357
3878
  voucher: voucher(authorization.cae, authorization.caeExpiry),
3358
- authorization: projectEvidence(authorization, includeRaw),
3359
- ...exact
3879
+ authorization: projectEvidence(authorization, includeRawResponse),
3880
+ ...includedRequest
3360
3881
  };
3361
3882
  }
3362
3883
  if (authorization.kind === "rejected") {
@@ -3364,7 +3885,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3364
3885
  authorization,
3365
3886
  recovery,
3366
3887
  options,
3367
- includeRaw,
3888
+ includeRawResponse,
3368
3889
  replay
3369
3890
  );
3370
3891
  }
@@ -3373,7 +3894,7 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3373
3894
  kind: "indeterminate",
3374
3895
  reason: authorization.voucherNumber === number ? "incomplete_response" : "contradictory_response"
3375
3896
  };
3376
- const attempt = projectEvidence(uncertain, includeRaw);
3897
+ const attempt = projectEvidence(uncertain, includeRawResponse);
3377
3898
  return recoverInvoice({
3378
3899
  wsfe,
3379
3900
  service: options.service,
@@ -3381,12 +3902,12 @@ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options,
3381
3902
  data,
3382
3903
  attempted,
3383
3904
  attempt,
3384
- includeRaw,
3385
- exact,
3905
+ includeRawResponse,
3906
+ includedRequest,
3386
3907
  voucher
3387
3908
  });
3388
3909
  }
3389
- async function resolveRejection(authorization, recovery, options, includeRaw, replay) {
3910
+ async function resolveRejection(authorization, recovery, options, includeRawResponse, replay) {
3390
3911
  const issues = [...authorization.errors, ...authorization.observations];
3391
3912
  const consult = options.idempotencyKey !== void 0 && (options.service === "wsmtxca" || issues.some((issue) => issue.code === "10016"));
3392
3913
  if (consult) {
@@ -3398,7 +3919,7 @@ async function resolveRejection(authorization, recovery, options, includeRaw, re
3398
3919
  kind: "indeterminate",
3399
3920
  reason: "contradictory_response"
3400
3921
  },
3401
- includeRaw
3922
+ includeRawResponse
3402
3923
  ),
3403
3924
  strangerAtNumber: !replay
3404
3925
  });
@@ -3410,7 +3931,8 @@ async function resolveRejection(authorization, recovery, options, includeRaw, re
3410
3931
  kind: "rejected",
3411
3932
  attempted: recovery.attempted,
3412
3933
  issues: issues.map(projectIssue),
3413
- authorization: projectEvidence(authorization, includeRaw)
3934
+ authorization: projectEvidence(authorization, includeRawResponse),
3935
+ ...recovery.includedRequest
3414
3936
  };
3415
3937
  }
3416
3938
  async function recoverInvoice({
@@ -3419,19 +3941,20 @@ async function recoverInvoice({
3419
3941
  data,
3420
3942
  attempted,
3421
3943
  attempt,
3422
- includeRaw,
3423
- exact,
3944
+ includeRawResponse,
3945
+ includedRequest,
3424
3946
  voucher,
3425
3947
  lookup: suppliedLookup,
3426
3948
  service,
3427
3949
  strangerAtNumber = false
3428
3950
  }) {
3429
- if (suppliedLookup === void 0 && auth.signal?.aborted) {
3951
+ if (suppliedLookup === void 0 && auth.abortSignal?.aborted) {
3430
3952
  return {
3431
3953
  kind: "indeterminate",
3432
3954
  attempted,
3433
3955
  attempt,
3434
- lookup: { kind: "aborted" }
3956
+ lookup: { kind: "aborted" },
3957
+ ...includedRequest
3435
3958
  };
3436
3959
  }
3437
3960
  let lookup;
@@ -3442,16 +3965,18 @@ async function recoverInvoice({
3442
3965
  kind: "indeterminate",
3443
3966
  attempted,
3444
3967
  attempt,
3445
- lookup: auth.signal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3968
+ lookup: auth.abortSignal?.aborted ? { kind: "aborted" } : { kind: "failed", error: toArcaSafeErrorMetadata(error) },
3969
+ ...includedRequest
3446
3970
  };
3447
3971
  }
3448
- const raw = includeRaw ? { raw: lookup.raw } : {};
3972
+ const rawResponse = includeRawResponse ? { rawResponse: lookup.raw } : {};
3449
3973
  if (lookup.kind === "not_found") {
3450
3974
  return {
3451
3975
  kind: "indeterminate",
3452
3976
  attempted,
3453
3977
  attempt,
3454
- lookup: { kind: "not_found", ...raw }
3978
+ lookup: { kind: "not_found", ...rawResponse },
3979
+ ...includedRequest
3455
3980
  };
3456
3981
  }
3457
3982
  if (strangerAtNumber) {
@@ -3459,8 +3984,9 @@ async function recoverInvoice({
3459
3984
  kind: "conflict",
3460
3985
  attempted,
3461
3986
  attempt,
3462
- found: { ...toVoucherSummary(lookup.voucher), ...raw },
3463
- reason: "ARCA refused the number this call reserved and another voucher occupies it"
3987
+ found: { ...toVoucherSummary(lookup.voucher), ...rawResponse },
3988
+ reason: "ARCA refused the number this call reserved and another voucher occupies it",
3989
+ ...includedRequest
3464
3990
  };
3465
3991
  }
3466
3992
  const detailsMatch = service === "wsmtxca" ? matchWsmtxcaDetails(data, attempted.number, lookup.voucher.raw) : void 0;
@@ -3475,15 +4001,21 @@ async function recoverInvoice({
3475
4001
  kind: "conflict",
3476
4002
  attempted,
3477
4003
  attempt,
3478
- found: { ...toVoucherSummary(lookup.voucher), ...raw },
3479
- reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`
4004
+ found: { ...toVoucherSummary(lookup.voucher), ...rawResponse },
4005
+ reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`,
4006
+ ...includedRequest
3480
4007
  };
3481
4008
  }
3482
4009
  return {
3483
4010
  kind: "indeterminate",
3484
4011
  attempted,
3485
4012
  attempt,
3486
- lookup: { kind: "incomplete", reason: matched.reason, ...raw }
4013
+ lookup: {
4014
+ kind: "incomplete",
4015
+ reason: matched.reason,
4016
+ ...rawResponse
4017
+ },
4018
+ ...includedRequest
3487
4019
  };
3488
4020
  }
3489
4021
  return {
@@ -3494,8 +4026,8 @@ async function recoverInvoice({
3494
4026
  lookup.voucher.caeExpiry
3495
4027
  ),
3496
4028
  attempt,
3497
- lookup: { ...toVoucherSummary(lookup.voucher), ...raw },
3498
- ...exact
4029
+ lookup: { ...toVoucherSummary(lookup.voucher), ...rawResponse },
4030
+ ...includedRequest
3499
4031
  };
3500
4032
  }
3501
4033
  function projectIssue(issue) {
@@ -3509,7 +4041,7 @@ function projectIssue(issue) {
3509
4041
  ...issue.resultLevel === void 0 ? {} : { resultLevel: issue.resultLevel }
3510
4042
  };
3511
4043
  }
3512
- function projectEvidence(evidence, includeRaw) {
4044
+ function projectEvidence(evidence, includeRawResponse) {
3513
4045
  const base = {
3514
4046
  kind: evidence.kind,
3515
4047
  service: evidence.service,
@@ -3521,7 +4053,7 @@ function projectEvidence(evidence, includeRaw) {
3521
4053
  },
3522
4054
  errors: evidence.errors.map(projectIssue),
3523
4055
  observations: evidence.observations.map(projectIssue),
3524
- ...includeRaw && evidence.raw !== void 0 ? { raw: evidence.raw } : {}
4056
+ ...includeRawResponse && evidence.raw !== void 0 ? { rawResponse: evidence.raw } : {}
3525
4057
  };
3526
4058
  const projected = { ...base };
3527
4059
  for (const field of [
@@ -3548,10 +4080,10 @@ function projectEvidence(evidence, includeRaw) {
3548
4080
  }
3549
4081
  function cloneOptions(options) {
3550
4082
  assertIssueObject(options, "options");
3551
- const { signal, ...rest } = options;
4083
+ const { abortSignal, ...rest } = options;
3552
4084
  return {
3553
4085
  ...structuredClone(rest),
3554
- ...signal === void 0 ? {} : { signal }
4086
+ ...abortSignal === void 0 ? {} : { abortSignal }
3555
4087
  };
3556
4088
  }
3557
4089
  function validateOptions(options) {
@@ -3565,14 +4097,14 @@ function validateOptions(options) {
3565
4097
  "idempotencyKey",
3566
4098
  "service",
3567
4099
  "number",
3568
- "signal"
4100
+ "abortSignal"
3569
4101
  ],
3570
4102
  "options"
3571
4103
  );
3572
- if (options.signal !== void 0 && (typeof options.signal !== "object" || options.signal === null || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
3573
- throw new ArcaInputError("options.signal must be an AbortSignal.", {
4104
+ if (options.abortSignal !== void 0 && (typeof options.abortSignal !== "object" || options.abortSignal === null || typeof options.abortSignal.aborted !== "boolean" || typeof options.abortSignal.addEventListener !== "function")) {
4105
+ throw new ArcaInputError("options.abortSignal must be an AbortSignal.", {
3574
4106
  code: "ARCA_INPUT_INVALID_VALUE",
3575
- field: "options.signal"
4107
+ field: "options.abortSignal"
3576
4108
  });
3577
4109
  }
3578
4110
  if (options.service !== void 0 && options.service !== "wsfe" && options.service !== "wsmtxca") {
@@ -3603,8 +4135,12 @@ function validateOptions(options) {
3603
4135
  }
3604
4136
  if (options.include !== void 0) {
3605
4137
  assertIssueObject(options.include, "options.include");
3606
- assertIssueKeys(options.include, ["raw", "exactInput"], "options.include");
3607
- for (const field of ["raw", "exactInput"]) {
4138
+ assertIssueKeys(
4139
+ options.include,
4140
+ ["request", "rawResponse"],
4141
+ "options.include"
4142
+ );
4143
+ for (const field of ["request", "rawResponse"]) {
3608
4144
  if (options.include[field] !== void 0 && typeof options.include[field] !== "boolean") {
3609
4145
  throw new ArcaInputError(
3610
4146
  `options.include.${field} must be a boolean.`,
@@ -3652,16 +4188,17 @@ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "credi
3652
4188
  );
3653
4189
  }
3654
4190
  async function previewNote(wsfe, input, inputOptions, context, kind) {
3655
- const options = structuredClone(inputOptions);
4191
+ const options = cloneOptions(inputOptions);
3656
4192
  assertIssueKeys(
3657
4193
  options,
3658
- ["representedTaxId", "service", "forceRefresh"],
4194
+ ["representedTaxId", "service", "forceRefresh", "abortSignal"],
3659
4195
  "options"
3660
4196
  );
3661
- return toPreview(
3662
- await prepareNote(wsfe, input, options, context, kind),
3663
- options
3664
- );
4197
+ const prepared = await prepareNote(wsfe, input, options, context, kind);
4198
+ return {
4199
+ ...toPreview(prepared, options),
4200
+ ...prepared.originals === void 0 ? {} : { originals: prepared.originals }
4201
+ };
3665
4202
  }
3666
4203
  async function prepareNote(wsfe, input, options, context, kind) {
3667
4204
  validateOptions(options);
@@ -3675,10 +4212,72 @@ async function prepareNote(wsfe, input, options, context, kind) {
3675
4212
  code: "ARCA_INPUT_INVALID_VALUE"
3676
4213
  });
3677
4214
  }
3678
- const target = note.for;
4215
+ const targets = creditNoteTargets(note);
4216
+ validateFceAssociations(note, targets, options.service ?? "wsfe");
4217
+ const originals = [];
4218
+ for (const target of targets) {
4219
+ originals.push(await lookupOriginal(wsfe, target, options));
4220
+ }
4221
+ const [firstOriginal] = originals;
4222
+ const prepared = note.all === true ? deriveWsfeFullCreditNote(firstOriginal, note) : deriveWsfePartialCreditNote(originals, note, /* @__PURE__ */ new Date(), kind);
4223
+ if (note.all === true) {
4224
+ const mirrored = firstOriginal.lines;
4225
+ if (mirrored !== void 0) {
4226
+ prepared.data.authorizedLines = structuredClone(mirrored);
4227
+ }
4228
+ } else {
4229
+ attachLines(prepared, options);
4230
+ }
4231
+ if (voucherFamily(prepared.data.voucherType).family === "fce") {
4232
+ const taxId = options.representedTaxId ?? context?.taxId;
4233
+ if (!taxId) {
4234
+ throw new ArcaInputError("FCE association requires the issuer tax ID", {
4235
+ code: "ARCA_INPUT_MISSING_FIELD",
4236
+ field: "representedTaxId"
4237
+ });
4238
+ }
4239
+ for (const associated of prepared.data.associatedVouchers ?? []) {
4240
+ associated.taxId = String(taxId);
4241
+ }
4242
+ }
4243
+ validatePrepared(prepared, options);
4244
+ return { ...prepared, originals: originals.map(toVoucherSummary) };
4245
+ }
4246
+ function validateFceAssociations(note, targets, service) {
4247
+ const fceTargets = targets.filter(
4248
+ (target2) => voucherFamily(target2.voucherType).family === "fce"
4249
+ );
4250
+ if (fceTargets.length === 0) {
4251
+ return;
4252
+ }
4253
+ const nonAnnulment = normalizedFceAnnulment(note) === false;
4254
+ if ((service === "wsmtxca" || nonAnnulment) && targets.length !== 1) {
4255
+ throw new ArcaInputError(
4256
+ service === "wsmtxca" ? "WSMTXCA FCE notes require exactly one associated original." : "Non-annulment FCE notes require exactly one associated invoice.",
4257
+ {
4258
+ code: "ARCA_INPUT_INVALID_VALUE",
4259
+ field: "input.for",
4260
+ expected: "one FCE original"
4261
+ }
4262
+ );
4263
+ }
4264
+ const [target] = targets;
4265
+ if (nonAnnulment && target !== void 0 && voucherFamily(target.voucherType).types[0] !== target.voucherType) {
4266
+ throw new ArcaInputError(
4267
+ "Non-annulment FCE notes must be associated to an FCE invoice.",
4268
+ {
4269
+ code: "ARCA_INPUT_INVALID_VALUE",
4270
+ field: "input.for.voucherType",
4271
+ expected: "an FCE invoice type"
4272
+ }
4273
+ );
4274
+ }
4275
+ }
4276
+ async function lookupOriginal(wsfe, target, options) {
3679
4277
  const original = await wsfe.lookupVoucher({
3680
4278
  representedTaxId: options.representedTaxId,
3681
4279
  forceRefresh: options.forceRefresh,
4280
+ ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal },
3682
4281
  ...target
3683
4282
  });
3684
4283
  if (original.kind !== "found") {
@@ -3693,28 +4292,7 @@ async function prepareNote(wsfe, input, options, context, kind) {
3693
4292
  { code: "ARCA_INPUT_INVALID_VALUE", field: "input.for" }
3694
4293
  );
3695
4294
  }
3696
- const prepared = note.all === true ? deriveWsfeFullCreditNote(original.voucher, note) : deriveWsfePartialCreditNote(original.voucher, note, /* @__PURE__ */ new Date(), kind);
3697
- if (note.details !== void 0) {
3698
- prepared.data.details = structuredClone(note.details);
3699
- } else if (note.all && "details" in original.voucher) {
3700
- prepared.data.details = structuredClone(
3701
- original.voucher.details
3702
- );
3703
- }
3704
- if (voucherFamily(prepared.data.voucherType).family === "fce") {
3705
- const taxId = options.representedTaxId ?? context?.taxId;
3706
- if (!taxId) {
3707
- throw new ArcaInputError("FCE association requires the issuer tax ID", {
3708
- code: "ARCA_INPUT_MISSING_FIELD",
3709
- field: "representedTaxId"
3710
- });
3711
- }
3712
- for (const associated of prepared.data.associatedVouchers ?? []) {
3713
- associated.taxId = String(taxId);
3714
- }
3715
- }
3716
- validatePrepared(prepared, options);
3717
- return prepared;
4295
+ return original.voucher;
3718
4296
  }
3719
4297
  function preparePeriodNote(input, options, kind) {
3720
4298
  const { associatedPeriod, ...invoice } = input;
@@ -3743,9 +4321,9 @@ function preparePeriodNote(input, options, kind) {
3743
4321
  validatePrepared(prepared, options);
3744
4322
  return prepared;
3745
4323
  }
3746
- function exactEvidence(data, number, options, include) {
3747
- return include ? {
3748
- sent: options.service === "wsmtxca" ? wsmtxcaRequest(data, number) : data
4324
+ function requestEvidence(data, number, options) {
4325
+ return options.include?.request === true ? {
4326
+ request: options.service === "wsmtxca" ? wsmtxcaRequest(data, number) : data
3749
4327
  } : {};
3750
4328
  }
3751
4329
  async function recoverOperation(select, key, inputOptions, context) {
@@ -3753,7 +4331,7 @@ async function recoverOperation(select, key, inputOptions, context) {
3753
4331
  assertIssueObject(options, "options");
3754
4332
  assertIssueKeys(
3755
4333
  options,
3756
- ["representedTaxId", "forceRefresh", "include", "signal"],
4334
+ ["representedTaxId", "forceRefresh", "include", "abortSignal"],
3757
4335
  "options"
3758
4336
  );
3759
4337
  validateOptions(options);
@@ -3769,7 +4347,7 @@ async function recoverOperation(select, key, inputOptions, context) {
3769
4347
  );
3770
4348
  if (json === null) {
3771
4349
  throw new ArcaInputError("No reservation exists for this idempotency key", {
3772
- code: "ARCA_INPUT_INVALID_VALUE",
4350
+ code: "ARCA_INPUT_RESERVATION_NOT_FOUND",
3773
4351
  field: "idempotencyKey"
3774
4352
  });
3775
4353
  }
@@ -3830,12 +4408,11 @@ function consultReservation(select, record, options, strangerAtNumber = false) {
3830
4408
  strangerAtNumber,
3831
4409
  attempted,
3832
4410
  attempt: replayEvidence(storedOptions.service),
3833
- includeRaw: options.include?.raw === true,
3834
- exact: exactEvidence(
4411
+ includeRawResponse: options.include?.rawResponse === true,
4412
+ includedRequest: requestEvidence(
3835
4413
  prepared.data,
3836
4414
  record.number,
3837
- storedOptions,
3838
- options.include?.exactInput === true
4415
+ storedOptions
3839
4416
  ),
3840
4417
  voucher: (cae, caeExpiry) => ({
3841
4418
  ...attempted,
@@ -3890,7 +4467,7 @@ async function runSequenceBarrier({
3890
4467
  settled,
3891
4468
  await consultReservation(select, record, {
3892
4469
  forceRefresh: options.forceRefresh,
3893
- ...options.signal === void 0 ? {} : { signal: options.signal }
4470
+ ...options.abortSignal === void 0 ? {} : { abortSignal: options.abortSignal }
3894
4471
  })
3895
4472
  );
3896
4473
  if (outcome.kind === "authorized" || outcome.kind === "conflict") {
@@ -4088,10 +4665,12 @@ function createArcaClient(config = {}) {
4088
4665
  // src/store/file.ts
4089
4666
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
4090
4667
  import {
4668
+ link,
4091
4669
  mkdir,
4670
+ open,
4092
4671
  readFile,
4093
4672
  rename,
4094
- rm,
4673
+ rmdir,
4095
4674
  stat,
4096
4675
  unlink,
4097
4676
  writeFile
@@ -4194,35 +4773,28 @@ function fileLease(directory, ensure) {
4194
4773
  owner,
4195
4774
  expiresAt: new Date(Date.now() + ARCA_LEASE_MS).toISOString()
4196
4775
  });
4197
- const read = async () => {
4198
- try {
4199
- return JSON.parse(await readFile(holder, "utf8"));
4200
- } catch {
4201
- return null;
4202
- }
4203
- };
4204
4776
  return {
4205
4777
  acquire: (owner) => storeCall(async () => {
4206
4778
  await ensure();
4207
4779
  if (!await createDirectory(directory)) {
4208
- if (await stillHeld(directory, await read())) {
4780
+ const current = await readHolder(holder);
4781
+ if (await stillHeld(directory, current?.value ?? null)) {
4209
4782
  return false;
4210
4783
  }
4211
- await rm(directory, { recursive: true, force: true });
4212
- if (!await createDirectory(directory)) {
4213
- return false;
4784
+ if (current) {
4785
+ await retireHolder(directory, holder, current.raw);
4786
+ } else {
4787
+ await removeEmptyDirectory(directory);
4214
4788
  }
4789
+ return false;
4215
4790
  }
4216
4791
  return await writeHolder(holder, claim(owner));
4217
4792
  }),
4218
- renew: (owner) => storeCall(async () => {
4219
- if ((await read())?.owner === owner) {
4220
- await writeFile(holder, claim(owner), { mode: 384 });
4221
- }
4222
- }),
4793
+ renew: (owner) => storeCall(() => renewHolder(holder, owner, claim(owner))),
4223
4794
  release: (owner) => storeCall(async () => {
4224
- if ((await read())?.owner === owner) {
4225
- await rm(directory, { recursive: true, force: true });
4795
+ const current = await readHolder(holder);
4796
+ if (current?.value?.owner === owner) {
4797
+ await retireHolder(directory, holder, current.raw);
4226
4798
  }
4227
4799
  })
4228
4800
  };
@@ -4243,12 +4815,105 @@ async function writeHolder(holder, value) {
4243
4815
  await writeFile(holder, value, { mode: 384, flag: "wx" });
4244
4816
  return true;
4245
4817
  } catch (error) {
4246
- if (error.code === "EEXIST") {
4818
+ if (error.code === "EEXIST" || error.code === "ENOENT") {
4247
4819
  return false;
4248
4820
  }
4249
4821
  throw error;
4250
4822
  }
4251
4823
  }
4824
+ async function readHolder(holder) {
4825
+ let raw;
4826
+ try {
4827
+ raw = await readFile(holder, "utf8");
4828
+ } catch (error) {
4829
+ if (error.code === "ENOENT") {
4830
+ return null;
4831
+ }
4832
+ throw error;
4833
+ }
4834
+ try {
4835
+ return { raw, value: JSON.parse(raw) };
4836
+ } catch {
4837
+ return { raw, value: null };
4838
+ }
4839
+ }
4840
+ async function renewHolder(holder, owner, value) {
4841
+ let file;
4842
+ try {
4843
+ file = await open(holder, "r+");
4844
+ } catch (error) {
4845
+ if (error.code === "ENOENT") {
4846
+ return;
4847
+ }
4848
+ throw error;
4849
+ }
4850
+ try {
4851
+ let current = null;
4852
+ try {
4853
+ current = JSON.parse(await file.readFile("utf8"));
4854
+ } catch {
4855
+ }
4856
+ if (current?.owner === owner) {
4857
+ await file.truncate(0);
4858
+ await file.write(value, 0, "utf8");
4859
+ }
4860
+ } finally {
4861
+ await file.close();
4862
+ }
4863
+ }
4864
+ async function retireHolder(directory, holder, expected) {
4865
+ const retired = `${directory}.${randomUUID2()}.stale`;
4866
+ try {
4867
+ await rename(holder, retired);
4868
+ } catch (error) {
4869
+ if (error.code === "ENOENT") {
4870
+ return;
4871
+ }
4872
+ throw error;
4873
+ }
4874
+ let removed = false;
4875
+ try {
4876
+ if (await readFile(retired, "utf8") !== expected) {
4877
+ return;
4878
+ }
4879
+ try {
4880
+ await rmdir(directory);
4881
+ removed = true;
4882
+ } catch (error) {
4883
+ if (!isDirectoryContention(error)) {
4884
+ throw error;
4885
+ }
4886
+ }
4887
+ } finally {
4888
+ if (!removed) {
4889
+ await restoreHolder(retired, holder);
4890
+ }
4891
+ await unlink(retired).catch(() => void 0);
4892
+ }
4893
+ }
4894
+ async function restoreHolder(retired, holder) {
4895
+ try {
4896
+ await link(retired, holder);
4897
+ } catch (error) {
4898
+ const code = error.code;
4899
+ if (code !== "EEXIST" && code !== "ENOENT") {
4900
+ throw error;
4901
+ }
4902
+ }
4903
+ }
4904
+ async function removeEmptyDirectory(directory) {
4905
+ try {
4906
+ await rmdir(directory);
4907
+ } catch (error) {
4908
+ if (!isDirectoryContention(error)) {
4909
+ throw error;
4910
+ }
4911
+ }
4912
+ }
4913
+ function isDirectoryContention(error) {
4914
+ const code = error.code;
4915
+ return code === "ENOENT" || code === "ENOTEMPTY" || code === "EEXIST";
4916
+ }
4252
4917
  async function stillHeld(directory, holder) {
4253
4918
  if (holder) {
4254
4919
  return Date.parse(holder.expiresAt) > Date.now();
@@ -4278,4 +4943,4 @@ export {
4278
4943
  withLease,
4279
4944
  createFileStore
4280
4945
  };
4281
- //# sourceMappingURL=chunk-G7BWMDFT.mjs.map
4946
+ //# sourceMappingURL=chunk-KPWAQMR5.mjs.map