facturas 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1602 @@
1
+ import {
2
+ ARCA_CURRENCY_IDS,
3
+ ARCA_VAT_RATES,
4
+ ARCA_VOUCHER_TYPES
5
+ } from "./chunk-VVY2LZIZ.mjs";
6
+ import {
7
+ classifyArcaAuthenticationError,
8
+ classifyArcaAuthenticationIssues,
9
+ createArcaAuthenticationErrorFromEvidence,
10
+ createArcaAuthenticationEvidence,
11
+ executeWithAuthenticationRecovery
12
+ } from "./chunk-IOKZX6CA.mjs";
13
+ import {
14
+ ArcaInputError,
15
+ ArcaInvalidSoapResponseError,
16
+ ArcaServiceError,
17
+ ArcaSoapFaultError,
18
+ ArcaTransportError
19
+ } from "./chunk-MBWOFO67.mjs";
20
+
21
+ // src/internal/decimal.ts
22
+ var AMOUNT_SCALE = 100;
23
+ var EXCHANGE_RATE_SCALE = 1e6;
24
+ var EXCHANGE_RATE_SCALE_BIGINT = 1000000n;
25
+ var PERCENTAGE_SCALE = 100;
26
+ var MAX_ARCA_AMOUNT_MINOR_UNITS = 999999999999999n;
27
+ var MAX_ARCA_EXCHANGE_RATE_SCALED = 9999999999n;
28
+ var MAX_ARCA_PERCENTAGE_HUNDREDTHS = 99999n;
29
+ var VAT_RATE_BASIS_POINTS = {
30
+ 0: 0n,
31
+ 2.5: 250n,
32
+ 5: 500n,
33
+ 10.5: 1050n,
34
+ 21: 2100n,
35
+ 27: 2700n
36
+ };
37
+ function normalizeArcaAmountToMinorUnits(value, field) {
38
+ return normalizeScaledNumber({
39
+ value,
40
+ field,
41
+ scale: AMOUNT_SCALE,
42
+ maximum: MAX_ARCA_AMOUNT_MINOR_UNITS,
43
+ expected: "a finite non-negative amount with at most 2 decimal places"
44
+ });
45
+ }
46
+ function serializeArcaAmount(value, field) {
47
+ return formatScaledInteger(normalizeArcaAmountToMinorUnits(value, field), 2);
48
+ }
49
+ function serializeArcaPercentage(value, field) {
50
+ const scaled = normalizeScaledNumber({
51
+ value,
52
+ field,
53
+ scale: PERCENTAGE_SCALE,
54
+ maximum: MAX_ARCA_PERCENTAGE_HUNDREDTHS,
55
+ expected: "a finite non-negative percentage with at most 2 decimal places"
56
+ });
57
+ return formatScaledInteger(scaled, 2);
58
+ }
59
+ function serializeArcaExchangeRate(value, field) {
60
+ const scaled = typeof value === "number" ? normalizeExchangeRateNumber(value, field) : normalizeExchangeRateString(value, field);
61
+ if (scaled <= 0n || scaled > MAX_ARCA_EXCHANGE_RATE_SCALED) {
62
+ throwInvalidExchangeRate(field);
63
+ }
64
+ return formatScaledInteger(scaled, 6, true);
65
+ }
66
+ function assertArcaMinorUnits(value, field) {
67
+ if (!(Number.isSafeInteger(value) && value >= 0)) {
68
+ throw new ArcaInputError(
69
+ `${field} must be a non-negative safe integer in currency minor units.`,
70
+ {
71
+ code: "ARCA_INPUT_INVALID_AMOUNT",
72
+ field,
73
+ expected: "a non-negative safe integer in currency minor units"
74
+ }
75
+ );
76
+ }
77
+ const minorUnits = BigInt(value);
78
+ if (minorUnits > MAX_ARCA_AMOUNT_MINOR_UNITS) {
79
+ throw new ArcaInputError(`${field} exceeds the WSFE amount limit.`, {
80
+ code: "ARCA_INPUT_INVALID_AMOUNT",
81
+ field,
82
+ expected: "at most 13 integer digits and 2 decimal places"
83
+ });
84
+ }
85
+ return minorUnits;
86
+ }
87
+ function arcaMinorUnitsToNumber(minorUnits, field) {
88
+ if (minorUnits < 0n || minorUnits > MAX_ARCA_AMOUNT_MINOR_UNITS) {
89
+ throw new ArcaInputError(`${field} exceeds the WSFE amount limit.`, {
90
+ code: "ARCA_INPUT_INVALID_AMOUNT",
91
+ field,
92
+ expected: "at most 13 integer digits and 2 decimal places"
93
+ });
94
+ }
95
+ return Number(formatScaledInteger(minorUnits, 2));
96
+ }
97
+ function calculateVatMinorUnits(taxableMinorUnits, vatRate, field) {
98
+ const basisPoints = VAT_RATE_BASIS_POINTS[vatRate];
99
+ if (basisPoints === void 0) {
100
+ throw new ArcaInputError(`${field} is not a supported VAT rate.`, {
101
+ code: "ARCA_INPUT_INVALID_VALUE",
102
+ field,
103
+ expected: "one of 0, 2.5, 5, 10.5, 21, or 27"
104
+ });
105
+ }
106
+ return roundHalfEvenRatio(taxableMinorUnits * basisPoints, 10000n);
107
+ }
108
+ function roundHalfEvenRatio(numerator, denominator) {
109
+ if (numerator < 0n || denominator <= 0n) {
110
+ throw new RangeError(
111
+ "roundHalfEvenRatio requires a non-negative numerator and a positive denominator."
112
+ );
113
+ }
114
+ const quotient = numerator / denominator;
115
+ const doubledRemainder = numerator % denominator * 2n;
116
+ if (doubledRemainder > denominator) {
117
+ return quotient + 1n;
118
+ }
119
+ if (doubledRemainder < denominator) {
120
+ return quotient;
121
+ }
122
+ return quotient % 2n === 0n ? quotient : quotient + 1n;
123
+ }
124
+ function isWithinArcaTolerance(actualMinorUnits, expectedMinorUnits, absoluteCentAllowance = 1) {
125
+ const difference = absoluteBigInt(actualMinorUnits - expectedMinorUnits);
126
+ if (difference <= BigInt(Math.max(1, absoluteCentAllowance))) {
127
+ return true;
128
+ }
129
+ const comparisonBase = absoluteBigInt(expectedMinorUnits);
130
+ return comparisonBase > 0n && difference * 10000n <= comparisonBase;
131
+ }
132
+ function normalizeScaledNumber({
133
+ value,
134
+ field,
135
+ scale,
136
+ maximum,
137
+ expected
138
+ }) {
139
+ if (!(Number.isFinite(value) && value >= 0)) {
140
+ throw new ArcaInputError(`${field} must be ${expected}.`, {
141
+ code: "ARCA_INPUT_INVALID_AMOUNT",
142
+ field,
143
+ expected
144
+ });
145
+ }
146
+ const scaled = value * scale;
147
+ const nearestInteger = Math.round(scaled);
148
+ const representationTolerance = Math.max(
149
+ 1e-9,
150
+ Math.abs(scaled) * Number.EPSILON * 4
151
+ );
152
+ if (Math.abs(scaled - nearestInteger) > representationTolerance) {
153
+ throw new ArcaInputError(
154
+ `${field} has more precision than its ARCA field allows.`,
155
+ {
156
+ code: "ARCA_INPUT_AMOUNT_PRECISION",
157
+ field,
158
+ expected
159
+ }
160
+ );
161
+ }
162
+ if (!Number.isSafeInteger(nearestInteger)) {
163
+ throw new ArcaInputError(`${field} exceeds the safely supported range.`, {
164
+ code: "ARCA_INPUT_INVALID_AMOUNT",
165
+ field,
166
+ expected
167
+ });
168
+ }
169
+ const normalized = BigInt(nearestInteger);
170
+ if (normalized > maximum) {
171
+ throw new ArcaInputError(`${field} exceeds the ARCA field limit.`, {
172
+ code: "ARCA_INPUT_INVALID_AMOUNT",
173
+ field,
174
+ expected
175
+ });
176
+ }
177
+ return normalized;
178
+ }
179
+ function normalizeExchangeRateNumber(value, field) {
180
+ if (!(Number.isFinite(value) && value > 0)) {
181
+ throwInvalidExchangeRate(field);
182
+ }
183
+ const scaled = value * EXCHANGE_RATE_SCALE;
184
+ const nearestInteger = Math.round(scaled);
185
+ const representationTolerance = Math.max(
186
+ 1e-9,
187
+ Math.abs(scaled) * Number.EPSILON * 4
188
+ );
189
+ if (Math.abs(scaled - nearestInteger) > representationTolerance || !Number.isSafeInteger(nearestInteger)) {
190
+ throwInvalidExchangeRate(field);
191
+ }
192
+ return BigInt(nearestInteger);
193
+ }
194
+ function normalizeExchangeRateString(value, field) {
195
+ const match = value.match(/^(0|[1-9]\d{0,3})(?:\.(\d{1,6}))?$/);
196
+ if (!match) {
197
+ throwInvalidExchangeRate(field);
198
+ }
199
+ const [, integerPart, fractionPart = ""] = match;
200
+ return BigInt(integerPart) * EXCHANGE_RATE_SCALE_BIGINT + BigInt(fractionPart.padEnd(6, "0"));
201
+ }
202
+ function throwInvalidExchangeRate(field) {
203
+ throw new ArcaInputError(
204
+ `${field} must be a positive decimal with at most 4 integer and 6 fractional digits.`,
205
+ {
206
+ code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
207
+ field,
208
+ expected: "a positive decimal with up to 4 integer and 6 fractional digits"
209
+ }
210
+ );
211
+ }
212
+ function formatScaledInteger(value, fractionDigits, trimTrailingZeros = false) {
213
+ const scale = 10n ** BigInt(fractionDigits);
214
+ const integerPart = value / scale;
215
+ const fractionPart = (value % scale).toString().padStart(fractionDigits, "0");
216
+ if (trimTrailingZeros) {
217
+ const trimmedFraction = fractionPart.replace(/0+$/, "");
218
+ return trimmedFraction.length === 0 ? integerPart.toString() : `${integerPart}.${trimmedFraction}`;
219
+ }
220
+ return `${integerPart}.${fractionPart}`;
221
+ }
222
+ function absoluteBigInt(value) {
223
+ return value < 0n ? -value : value;
224
+ }
225
+
226
+ // src/services/wsfe.ts
227
+ function createWsfeService(options) {
228
+ async function executeWsfeAuthenticatedRawOperation(operation, input, body = {}, retries) {
229
+ const auth = await options.auth.login("wsfe", {
230
+ representedTaxId: input.representedTaxId,
231
+ forceRefresh: input.forceRefresh
232
+ });
233
+ const response = await options.soap.execute({
234
+ service: "wsfe",
235
+ operation,
236
+ ...retries === void 0 ? {} : { retries },
237
+ body: {
238
+ Auth: createWsfeAuth(
239
+ input.representedTaxId ?? options.config.taxId,
240
+ auth.token,
241
+ auth.sign
242
+ ),
243
+ ...body
244
+ }
245
+ });
246
+ return unwrapWsfeOperationEnvelope(operation, response.result);
247
+ }
248
+ function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
249
+ return executeWithAuthenticationRecovery({
250
+ service: "wsfe",
251
+ operation,
252
+ forceRefresh: input.forceRefresh,
253
+ async execute(forceRefresh) {
254
+ const result = await executeWsfeAuthenticatedRawOperation(
255
+ operation,
256
+ { representedTaxId: input.representedTaxId, forceRefresh },
257
+ body
258
+ );
259
+ throwForWsfeOperationErrors(operation, result);
260
+ return result;
261
+ }
262
+ });
263
+ }
264
+ async function executeWsfeOperation(operation, body = {}) {
265
+ const response = await options.soap.execute({
266
+ service: "wsfe",
267
+ operation,
268
+ body
269
+ });
270
+ const result = unwrapWsfeOperationEnvelope(operation, response.result);
271
+ throwForWsfeOperationErrors(operation, result);
272
+ return result;
273
+ }
274
+ async function getNextVoucherNumber({
275
+ representedTaxId,
276
+ salesPoint,
277
+ voucherType,
278
+ forceRefresh
279
+ }) {
280
+ const result = await executeWsfeAuthenticatedOperation(
281
+ "FECompUltimoAutorizado",
282
+ {
283
+ representedTaxId,
284
+ forceRefresh
285
+ },
286
+ {
287
+ PtoVta: salesPoint,
288
+ CbteTipo: voucherType
289
+ }
290
+ );
291
+ return Number(result.CbteNro ?? 0) + 1;
292
+ }
293
+ async function getWsfeCatalog(operation, resultKey, input) {
294
+ const result = await executeWsfeAuthenticatedOperation(operation, {
295
+ representedTaxId: input.representedTaxId,
296
+ forceRefresh: input.forceRefresh
297
+ });
298
+ return getWsfeResultEntries(result, resultKey).map(mapWsfeCatalogEntry);
299
+ }
300
+ function authorizeVoucher({
301
+ representedTaxId,
302
+ data,
303
+ voucherNumber,
304
+ forceRefresh
305
+ }) {
306
+ const normalizedInput = normalizeWsfeVoucherInput(data);
307
+ return authorizeNormalizedVoucher({
308
+ representedTaxId,
309
+ data: normalizedInput,
310
+ voucherNumber,
311
+ forceRefresh
312
+ });
313
+ }
314
+ function authorizeVoucherOutcome({
315
+ representedTaxId,
316
+ data,
317
+ voucherNumber,
318
+ forceRefresh
319
+ }) {
320
+ const normalizedInput = normalizeWsfeVoucherInput(data);
321
+ return executeWsfeAuthorization({
322
+ representedTaxId,
323
+ data: normalizedInput,
324
+ voucherNumber,
325
+ forceRefresh
326
+ }).then(({ outcome }) => outcome);
327
+ }
328
+ function authorizeNormalizedVoucher({
329
+ representedTaxId,
330
+ data: normalizedInput,
331
+ voucherNumber,
332
+ forceRefresh,
333
+ allowAuthenticationRecovery
334
+ }) {
335
+ return executeWithAuthenticationRecovery({
336
+ service: "wsfe",
337
+ operation: "FECAESolicitar",
338
+ forceRefresh,
339
+ allowRetry: allowAuthenticationRecovery,
340
+ execute: (attemptForceRefresh) => authorizeNormalizedVoucherOnce({
341
+ representedTaxId,
342
+ data: normalizedInput,
343
+ voucherNumber,
344
+ forceRefresh: attemptForceRefresh
345
+ })
346
+ });
347
+ }
348
+ async function authorizeNormalizedVoucherOnce({
349
+ representedTaxId,
350
+ data: normalizedInput,
351
+ voucherNumber,
352
+ forceRefresh
353
+ }) {
354
+ const execution = await executeWsfeAuthorization({
355
+ representedTaxId,
356
+ data: normalizedInput,
357
+ voucherNumber,
358
+ forceRefresh
359
+ });
360
+ if (execution.error) {
361
+ throw execution.error;
362
+ }
363
+ if (execution.outcome.kind !== "authorized") {
364
+ throw createWsfeOutcomeError(execution.outcome);
365
+ }
366
+ const { cae, caeExpiry, raw } = execution.outcome;
367
+ if (!(caeExpiry && raw)) {
368
+ throw new ArcaServiceError("WSFE did not return CAE authorization data", {
369
+ service: "wsfe",
370
+ operation: "FECAESolicitar",
371
+ result: execution.outcome.result,
372
+ resultLevel: execution.outcome.resultLevel,
373
+ results: execution.outcome.results,
374
+ cae,
375
+ issues: [
376
+ ...execution.outcome.errors,
377
+ ...execution.outcome.observations
378
+ ]
379
+ });
380
+ }
381
+ return {
382
+ cae,
383
+ caeExpiry,
384
+ voucherNumber,
385
+ raw
386
+ };
387
+ }
388
+ async function executeWsfeAuthorization({
389
+ representedTaxId,
390
+ data: normalizedInput,
391
+ voucherNumber,
392
+ forceRefresh
393
+ }) {
394
+ const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
395
+ try {
396
+ const result = await executeWsfeAuthenticatedRawOperation(
397
+ "FECAESolicitar",
398
+ { representedTaxId, forceRefresh },
399
+ {
400
+ FeCAEReq: {
401
+ FeCabReq: {
402
+ CantReg: 1,
403
+ PtoVta: normalizedInput.salesPoint,
404
+ CbteTipo: normalizedInput.voucherType
405
+ },
406
+ FeDetReq: {
407
+ FECAEDetRequest: requestData
408
+ }
409
+ }
410
+ },
411
+ 0
412
+ );
413
+ return {
414
+ outcome: classifyWsfeAuthorization(result, voucherNumber)
415
+ };
416
+ } catch (error) {
417
+ return {
418
+ outcome: createWsfeIndeterminateOutcome(error),
419
+ error
420
+ };
421
+ }
422
+ }
423
+ function lookupVoucher({
424
+ representedTaxId,
425
+ number,
426
+ salesPoint,
427
+ voucherType,
428
+ forceRefresh
429
+ }) {
430
+ return executeWithAuthenticationRecovery({
431
+ service: "wsfe",
432
+ operation: "FECompConsultar",
433
+ forceRefresh,
434
+ execute: (attemptForceRefresh) => lookupVoucherOnce({
435
+ representedTaxId,
436
+ number,
437
+ salesPoint,
438
+ voucherType,
439
+ forceRefresh: attemptForceRefresh
440
+ })
441
+ });
442
+ }
443
+ async function lookupVoucherOnce({
444
+ representedTaxId,
445
+ number,
446
+ salesPoint,
447
+ voucherType,
448
+ forceRefresh
449
+ }) {
450
+ const operation = "FECompConsultar";
451
+ const result = await executeWsfeAuthenticatedRawOperation(
452
+ operation,
453
+ { representedTaxId, forceRefresh },
454
+ {
455
+ FeCompConsReq: {
456
+ CbteNro: number,
457
+ PtoVta: salesPoint,
458
+ CbteTipo: voucherType
459
+ }
460
+ }
461
+ );
462
+ const errors = extractWsfeGlobalIssues(result, operation);
463
+ if (errors.length > 0 && errors.every((issue) => issue.code === "602")) {
464
+ return {
465
+ kind: "not_found",
466
+ service: "wsfe",
467
+ operation,
468
+ errors,
469
+ observations: [],
470
+ raw: result
471
+ };
472
+ }
473
+ if (errors.length > 0) {
474
+ throw createWsfeServiceError(operation, errors);
475
+ }
476
+ const raw = toWsfeRecord(result.ResultGet);
477
+ if (!raw) {
478
+ throw new ArcaServiceError("WSFE did not return the consulted voucher", {
479
+ service: "wsfe",
480
+ operation
481
+ });
482
+ }
483
+ return {
484
+ kind: "found",
485
+ service: "wsfe",
486
+ operation,
487
+ voucher: mapWsfeVoucherInfo(raw),
488
+ observations: [],
489
+ raw: result
490
+ };
491
+ }
492
+ return {
493
+ authorizeVoucherOutcome,
494
+ authorizeVoucher,
495
+ async createNextVoucher({ representedTaxId, data, forceRefresh }) {
496
+ const normalizedInput = normalizeWsfeVoucherInput(data);
497
+ const voucherNumber = await getNextVoucherNumber({
498
+ representedTaxId,
499
+ salesPoint: normalizedInput.salesPoint,
500
+ voucherType: normalizedInput.voucherType,
501
+ forceRefresh
502
+ });
503
+ return authorizeNormalizedVoucher({
504
+ representedTaxId,
505
+ data: normalizedInput,
506
+ voucherNumber,
507
+ allowAuthenticationRecovery: forceRefresh !== true
508
+ });
509
+ },
510
+ getNextVoucherNumber,
511
+ getLastVoucher(input) {
512
+ return getNextVoucherNumber(input);
513
+ },
514
+ async getSalesPoints({ representedTaxId, forceRefresh }) {
515
+ const result = await executeWsfeAuthenticatedOperation(
516
+ "FEParamGetPtosVenta",
517
+ {
518
+ representedTaxId,
519
+ forceRefresh
520
+ }
521
+ );
522
+ const rawPoints = result.ResultGet?.PtoVenta;
523
+ if (!rawPoints) {
524
+ return [];
525
+ }
526
+ const entries = Array.isArray(rawPoints) ? rawPoints : [rawPoints];
527
+ return entries.map(mapWsfeSalesPoint);
528
+ },
529
+ getVoucherTypes(input) {
530
+ return getWsfeCatalog("FEParamGetTiposCbte", "CbteTipo", input);
531
+ },
532
+ getDocumentTypes(input) {
533
+ return getWsfeCatalog("FEParamGetTiposDoc", "DocTipo", input);
534
+ },
535
+ getConceptTypes(input) {
536
+ return getWsfeCatalog("FEParamGetTiposConcepto", "ConceptoTipo", input);
537
+ },
538
+ async getCurrencyTypes({ representedTaxId, forceRefresh }) {
539
+ const result = await executeWsfeAuthenticatedOperation(
540
+ "FEParamGetTiposMonedas",
541
+ {
542
+ representedTaxId,
543
+ forceRefresh
544
+ }
545
+ );
546
+ return getWsfeResultEntries(result, "Moneda").map(mapWsfeCurrencyType);
547
+ },
548
+ getVatRates(input) {
549
+ return getWsfeCatalog("FEParamGetTiposIva", "IvaTipo", input);
550
+ },
551
+ getTaxTypes(input) {
552
+ return getWsfeCatalog("FEParamGetTiposTributos", "TributoTipo", input);
553
+ },
554
+ getOptionalTypes(input) {
555
+ return getWsfeCatalog("FEParamGetTiposOpcional", "OpcionalTipo", input);
556
+ },
557
+ async getActivities({ representedTaxId, forceRefresh }) {
558
+ const result = await executeWsfeAuthenticatedOperation(
559
+ "FEParamGetActividades",
560
+ {
561
+ representedTaxId,
562
+ forceRefresh
563
+ }
564
+ );
565
+ return getWsfeResultEntries(result, "ActividadesTipo").map(
566
+ mapWsfeActivityType
567
+ );
568
+ },
569
+ async getReceiverVatConditions({
570
+ representedTaxId,
571
+ voucherClass,
572
+ forceRefresh
573
+ }) {
574
+ const result = await executeWsfeAuthenticatedOperation(
575
+ "FEParamGetCondicionIvaReceptor",
576
+ {
577
+ representedTaxId,
578
+ forceRefresh
579
+ },
580
+ {
581
+ ...voucherClass === void 0 ? {} : { ClaseCmp: voucherClass }
582
+ }
583
+ );
584
+ return getWsfeResultEntries(result, "CondicionIvaReceptor").map(
585
+ mapWsfeReceiverVatCondition
586
+ );
587
+ },
588
+ async getServerStatus() {
589
+ const result = await executeWsfeOperation("FEDummy");
590
+ return mapWsfeServerStatus(result);
591
+ },
592
+ async getQuotation({ currencyId, representedTaxId, forceRefresh }) {
593
+ const result = await executeWsfeAuthenticatedOperation(
594
+ "FEParamGetCotizacion",
595
+ {
596
+ representedTaxId,
597
+ forceRefresh
598
+ },
599
+ {
600
+ MonId: currencyId
601
+ }
602
+ );
603
+ const raw = result.ResultGet ?? {};
604
+ return mapWsfeQuotation(raw);
605
+ },
606
+ async getVoucherInfo(input) {
607
+ const lookup = await lookupVoucher(input);
608
+ return lookup.kind === "found" ? lookup.voucher : null;
609
+ },
610
+ lookupVoucher
611
+ };
612
+ }
613
+ function mapWsfeVoucherInput(input, voucherNumber) {
614
+ const data = {
615
+ Concepto: input.concept,
616
+ DocTipo: input.documentType,
617
+ DocNro: input.documentNumber,
618
+ CbteDesde: voucherNumber,
619
+ CbteHasta: voucherNumber,
620
+ CbteFch: input.voucherDate,
621
+ ImpTotal: input.totalAmount,
622
+ ImpTotConc: input.nonTaxableAmount,
623
+ ImpNeto: input.netAmount,
624
+ ImpOpEx: input.exemptAmount,
625
+ ImpTrib: input.taxAmount,
626
+ ImpIVA: input.vatAmount,
627
+ MonId: input.currencyId,
628
+ CondicionIVAReceptorId: input.receiverVatConditionId,
629
+ PtoVta: input.salesPoint,
630
+ CbteTipo: input.voucherType
631
+ };
632
+ if (input.exchangeRate !== void 0) {
633
+ data.MonCotiz = input.exchangeRate;
634
+ }
635
+ if (input.currencyId !== "PES" && input.sameCurrencyForeignCancellation !== void 0) {
636
+ data.CanMisMonExt = input.sameCurrencyForeignCancellation;
637
+ }
638
+ if (input.serviceStartDate !== void 0) {
639
+ data.FchServDesde = input.serviceStartDate;
640
+ }
641
+ if (input.serviceEndDate !== void 0) {
642
+ data.FchServHasta = input.serviceEndDate;
643
+ }
644
+ if (input.paymentDueDate !== void 0) {
645
+ data.FchVtoPago = input.paymentDueDate;
646
+ }
647
+ if (input.associatedVouchers) {
648
+ data.CbtesAsoc = {
649
+ CbteAsoc: input.associatedVouchers.map((v) => ({
650
+ Tipo: v.type,
651
+ PtoVta: v.salesPoint,
652
+ Nro: v.number,
653
+ ...v.taxId === void 0 ? {} : { Cuit: v.taxId },
654
+ ...v.voucherDate === void 0 ? {} : { CbteFch: v.voucherDate }
655
+ }))
656
+ };
657
+ }
658
+ if (input.associatedPeriod) {
659
+ data.PeriodoAsoc = {
660
+ FchDesde: input.associatedPeriod.startDate,
661
+ FchHasta: input.associatedPeriod.endDate
662
+ };
663
+ }
664
+ if (input.taxes) {
665
+ data.Tributos = {
666
+ Tributo: input.taxes.map((t) => ({
667
+ Id: t.id,
668
+ ...t.description === void 0 ? {} : { Desc: t.description },
669
+ BaseImp: t.baseAmount,
670
+ Alic: t.rate,
671
+ Importe: t.amount
672
+ }))
673
+ };
674
+ }
675
+ if (input.vatRates) {
676
+ data.Iva = {
677
+ AlicIva: input.vatRates.map((v) => ({
678
+ Id: v.id,
679
+ BaseImp: v.baseAmount,
680
+ Importe: v.amount
681
+ }))
682
+ };
683
+ }
684
+ if (input.optionalFields) {
685
+ data.Opcionales = {
686
+ Opcional: input.optionalFields.map((o) => ({
687
+ Id: o.id,
688
+ Valor: o.value
689
+ }))
690
+ };
691
+ }
692
+ if (input.buyers) {
693
+ data.Compradores = {
694
+ Comprador: input.buyers.map((b) => ({
695
+ DocTipo: b.documentType,
696
+ DocNro: b.documentNumber,
697
+ Porcentaje: b.percentage
698
+ }))
699
+ };
700
+ }
701
+ if (input.activities) {
702
+ data.Actividades = {
703
+ Actividad: input.activities.map((a) => ({
704
+ Id: a.id
705
+ }))
706
+ };
707
+ }
708
+ return data;
709
+ }
710
+ function normalizeWsfeVoucherInput(input) {
711
+ if (input.receiverVatConditionId === void 0) {
712
+ throw new ArcaInputError("receiverVatConditionId is required.", {
713
+ code: "ARCA_INPUT_MISSING_FIELD",
714
+ field: "receiverVatConditionId",
715
+ expected: "a receiver VAT condition accepted for the voucher class"
716
+ });
717
+ }
718
+ const {
719
+ voucherDate,
720
+ exchangeRate,
721
+ serviceStartDate,
722
+ serviceEndDate,
723
+ paymentDueDate,
724
+ associatedVouchers,
725
+ associatedPeriod,
726
+ taxes,
727
+ vatRates,
728
+ ...rest
729
+ } = input;
730
+ const normalizedExchangeRate = normalizeWsfeExchangeRate(input, exchangeRate);
731
+ const normalizedAmounts = normalizeAndValidateWsfeAmounts(input);
732
+ return {
733
+ ...rest,
734
+ ...normalizedAmounts,
735
+ voucherDate: normalizeWsfeDateInput(voucherDate, "voucherDate"),
736
+ ...normalizedExchangeRate === void 0 ? {} : { exchangeRate: normalizedExchangeRate },
737
+ ...serviceStartDate === void 0 ? {} : {
738
+ serviceStartDate: normalizeWsfeDateInput(
739
+ serviceStartDate,
740
+ "serviceStartDate"
741
+ )
742
+ },
743
+ ...serviceEndDate === void 0 ? {} : {
744
+ serviceEndDate: normalizeWsfeDateInput(
745
+ serviceEndDate,
746
+ "serviceEndDate"
747
+ )
748
+ },
749
+ ...paymentDueDate === void 0 ? {} : {
750
+ paymentDueDate: normalizeWsfeDateInput(
751
+ paymentDueDate,
752
+ "paymentDueDate"
753
+ )
754
+ },
755
+ ...associatedVouchers === void 0 ? {} : {
756
+ associatedVouchers: associatedVouchers.map((voucher, index) => {
757
+ const { voucherDate: associatedVoucherDate, ...associatedRest } = voucher;
758
+ return {
759
+ ...associatedRest,
760
+ ...associatedVoucherDate === void 0 ? {} : {
761
+ voucherDate: normalizeWsfeDateInput(
762
+ associatedVoucherDate,
763
+ `associatedVouchers[${index}].voucherDate`
764
+ )
765
+ }
766
+ };
767
+ })
768
+ },
769
+ ...associatedPeriod === void 0 ? {} : {
770
+ associatedPeriod: {
771
+ startDate: normalizeWsfeDateInput(
772
+ associatedPeriod.startDate,
773
+ "associatedPeriod.startDate"
774
+ ),
775
+ endDate: normalizeWsfeDateInput(
776
+ associatedPeriod.endDate,
777
+ "associatedPeriod.endDate"
778
+ )
779
+ }
780
+ },
781
+ ...taxes === void 0 ? {} : {
782
+ taxes: taxes.map((tax, index) => ({
783
+ ...tax,
784
+ baseAmount: serializeArcaAmount(
785
+ tax.baseAmount,
786
+ `taxes[${index}].baseAmount`
787
+ ),
788
+ rate: serializeArcaPercentage(tax.rate, `taxes[${index}].rate`),
789
+ amount: serializeArcaAmount(tax.amount, `taxes[${index}].amount`)
790
+ }))
791
+ },
792
+ ...vatRates === void 0 ? {} : {
793
+ vatRates: vatRates.map((vatRate, index) => ({
794
+ ...vatRate,
795
+ baseAmount: serializeArcaAmount(
796
+ vatRate.baseAmount,
797
+ `vatRates[${index}].baseAmount`
798
+ ),
799
+ amount: serializeArcaAmount(
800
+ vatRate.amount,
801
+ `vatRates[${index}].amount`
802
+ )
803
+ }))
804
+ }
805
+ };
806
+ }
807
+ function normalizeAndValidateWsfeAmounts(input) {
808
+ const totalAmount = normalizeArcaAmountToMinorUnits(
809
+ input.totalAmount,
810
+ "totalAmount"
811
+ );
812
+ const nonTaxableAmount = normalizeArcaAmountToMinorUnits(
813
+ input.nonTaxableAmount,
814
+ "nonTaxableAmount"
815
+ );
816
+ const netAmount = normalizeArcaAmountToMinorUnits(
817
+ input.netAmount,
818
+ "netAmount"
819
+ );
820
+ const exemptAmount = normalizeArcaAmountToMinorUnits(
821
+ input.exemptAmount,
822
+ "exemptAmount"
823
+ );
824
+ const taxAmount = normalizeArcaAmountToMinorUnits(
825
+ input.taxAmount,
826
+ "taxAmount"
827
+ );
828
+ const vatAmount = normalizeArcaAmountToMinorUnits(
829
+ input.vatAmount,
830
+ "vatAmount"
831
+ );
832
+ const decomposedTotal = nonTaxableAmount + netAmount + exemptAmount + taxAmount + vatAmount;
833
+ assertWsfeAmountMatch(
834
+ totalAmount,
835
+ decomposedTotal,
836
+ "totalAmount",
837
+ "the sum of nonTaxableAmount, netAmount, exemptAmount, taxAmount, and vatAmount"
838
+ );
839
+ const vatRates = input.vatRates ?? [];
840
+ if (vatAmount > 0n && vatRates.length === 0) {
841
+ throw new ArcaInputError(
842
+ "vatRates is required when vatAmount is greater than zero.",
843
+ {
844
+ code: "ARCA_INPUT_MISSING_FIELD",
845
+ field: "vatRates",
846
+ expected: "VAT detail whose amounts reconcile with vatAmount"
847
+ }
848
+ );
849
+ }
850
+ if (vatRates.length > 0) {
851
+ const normalizedVatRates = vatRates.map((vatRate, index) => ({
852
+ baseAmount: normalizeArcaAmountToMinorUnits(
853
+ vatRate.baseAmount,
854
+ `vatRates[${index}].baseAmount`
855
+ ),
856
+ amount: normalizeArcaAmountToMinorUnits(
857
+ vatRate.amount,
858
+ `vatRates[${index}].amount`
859
+ )
860
+ }));
861
+ const vatRateAmountSum = normalizedVatRates.reduce(
862
+ (sum, vatRate) => sum + vatRate.amount,
863
+ 0n
864
+ );
865
+ const vatRateBaseSum = normalizedVatRates.reduce(
866
+ (sum, vatRate) => sum + vatRate.baseAmount,
867
+ 0n
868
+ );
869
+ assertWsfeAmountMatch(
870
+ vatAmount,
871
+ vatRateAmountSum,
872
+ "vatAmount",
873
+ "the sum of vatRates[].amount",
874
+ vatRates.length
875
+ );
876
+ if (requiresWsfeVatBaseReconciliation(input.voucherType)) {
877
+ assertWsfeAmountMatch(
878
+ netAmount,
879
+ vatRateBaseSum,
880
+ "netAmount",
881
+ "the sum of vatRates[].baseAmount",
882
+ vatRates.length
883
+ );
884
+ }
885
+ }
886
+ const taxes = input.taxes ?? [];
887
+ if (taxAmount > 0n && taxes.length === 0) {
888
+ throw new ArcaInputError(
889
+ "taxes is required when taxAmount is greater than zero.",
890
+ {
891
+ code: "ARCA_INPUT_MISSING_FIELD",
892
+ field: "taxes",
893
+ expected: "tax detail whose amounts reconcile with taxAmount"
894
+ }
895
+ );
896
+ }
897
+ if (taxes.length > 0) {
898
+ const taxAmountSum = taxes.reduce((sum, tax, index) => {
899
+ normalizeArcaAmountToMinorUnits(
900
+ tax.baseAmount,
901
+ `taxes[${index}].baseAmount`
902
+ );
903
+ serializeArcaPercentage(tax.rate, `taxes[${index}].rate`);
904
+ return sum + normalizeArcaAmountToMinorUnits(tax.amount, `taxes[${index}].amount`);
905
+ }, 0n);
906
+ assertWsfeAmountMatch(
907
+ taxAmount,
908
+ taxAmountSum,
909
+ "taxAmount",
910
+ "the sum of taxes[].amount",
911
+ taxes.length
912
+ );
913
+ }
914
+ return {
915
+ totalAmount: serializeArcaAmount(input.totalAmount, "totalAmount"),
916
+ nonTaxableAmount: serializeArcaAmount(
917
+ input.nonTaxableAmount,
918
+ "nonTaxableAmount"
919
+ ),
920
+ netAmount: serializeArcaAmount(input.netAmount, "netAmount"),
921
+ exemptAmount: serializeArcaAmount(input.exemptAmount, "exemptAmount"),
922
+ taxAmount: serializeArcaAmount(input.taxAmount, "taxAmount"),
923
+ vatAmount: serializeArcaAmount(input.vatAmount, "vatAmount")
924
+ };
925
+ }
926
+ function requiresWsfeVatBaseReconciliation(voucherType) {
927
+ return ![2, 3, 7, 8, 11, 12, 13, 15, 52, 53].includes(voucherType);
928
+ }
929
+ function assertWsfeAmountMatch(actual, expectedAmount, field, expectedDescription, absoluteCentAllowance = 1) {
930
+ if (!isWithinArcaTolerance(actual, expectedAmount, absoluteCentAllowance)) {
931
+ throw new ArcaInputError(
932
+ `${field} does not reconcile within ARCA's documented tolerance.`,
933
+ {
934
+ code: "ARCA_INPUT_AMOUNT_MISMATCH",
935
+ field,
936
+ expected: `within ARCA tolerance of ${expectedDescription}`
937
+ }
938
+ );
939
+ }
940
+ }
941
+ function normalizeWsfeExchangeRate(input, exchangeRate) {
942
+ if (input.currencyId === "PES") {
943
+ if (exchangeRate !== void 0 && serializeArcaExchangeRate(exchangeRate, "exchangeRate") !== "1") {
944
+ throw new ArcaInputError(
945
+ "exchangeRate must be 1 when currencyId is PES.",
946
+ {
947
+ code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
948
+ field: "exchangeRate",
949
+ expected: "1 when currencyId is PES"
950
+ }
951
+ );
952
+ }
953
+ return "1";
954
+ }
955
+ if (exchangeRate === void 0) {
956
+ if (input.sameCurrencyForeignCancellation === "S") {
957
+ return void 0;
958
+ }
959
+ throw new ArcaInputError(
960
+ "exchangeRate is required unless sameCurrencyForeignCancellation is S for a foreign-currency voucher.",
961
+ {
962
+ code: "ARCA_INPUT_MISSING_FIELD",
963
+ field: "exchangeRate",
964
+ expected: "a positive exchange rate unless sameCurrencyForeignCancellation is S"
965
+ }
966
+ );
967
+ }
968
+ return serializeArcaExchangeRate(exchangeRate, "exchangeRate");
969
+ }
970
+ function normalizeWsfeDateInput(value, fieldName) {
971
+ if (typeof value !== "string") {
972
+ throw new ArcaInputError(
973
+ `Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
974
+ {
975
+ code: "ARCA_INPUT_INVALID_DATE",
976
+ field: fieldName,
977
+ expected: "a YYYY-MM-DD or YYYYMMDD calendar date string"
978
+ }
979
+ );
980
+ }
981
+ const normalizedValue = value.trim();
982
+ const afipMatch = normalizedValue.match(/^(\d{4})(\d{2})(\d{2})$/);
983
+ if (afipMatch) {
984
+ const [, year, month, day] = afipMatch;
985
+ assertValidCalendarDate(year, month, day, fieldName);
986
+ return normalizedValue;
987
+ }
988
+ const isoMatch = normalizedValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
989
+ if (isoMatch) {
990
+ const [, year, month, day] = isoMatch;
991
+ assertValidCalendarDate(year, month, day, fieldName);
992
+ return `${year}${month}${day}`;
993
+ }
994
+ throw new ArcaInputError(
995
+ `Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
996
+ {
997
+ code: "ARCA_INPUT_INVALID_DATE",
998
+ field: fieldName,
999
+ expected: "a YYYY-MM-DD or YYYYMMDD calendar date string"
1000
+ }
1001
+ );
1002
+ }
1003
+ function assertValidCalendarDate(yearInput, monthInput, dayInput, fieldName) {
1004
+ const year = Number(yearInput);
1005
+ const month = Number(monthInput);
1006
+ const day = Number(dayInput);
1007
+ const candidate = new Date(Date.UTC(year, month - 1, day));
1008
+ if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() !== month - 1 || candidate.getUTCDate() !== day) {
1009
+ throw new ArcaInputError(
1010
+ `Invalid WSFE ${fieldName}: received a non-existent calendar date`,
1011
+ {
1012
+ code: "ARCA_INPUT_INVALID_DATE",
1013
+ field: fieldName,
1014
+ expected: "an existing calendar date"
1015
+ }
1016
+ );
1017
+ }
1018
+ }
1019
+ function mapWsfeSalesPoint(raw) {
1020
+ const record = raw;
1021
+ return {
1022
+ number: Number(record.Nro ?? 0),
1023
+ ...record.EmisionTipo === void 0 ? {} : { emissionType: String(record.EmisionTipo) },
1024
+ ...record.Bloqueado === void 0 ? {} : { blocked: String(record.Bloqueado) },
1025
+ ...record.FchBaja === void 0 ? {} : { deletedSince: String(record.FchBaja) }
1026
+ };
1027
+ }
1028
+ function mapWsfeCatalogEntry(raw) {
1029
+ const record = raw;
1030
+ return {
1031
+ id: Number(record.Id ?? 0),
1032
+ description: String(record.Desc ?? "")
1033
+ };
1034
+ }
1035
+ function mapWsfeActivityType(raw) {
1036
+ const record = raw;
1037
+ return {
1038
+ id: Number(record.Id ?? 0),
1039
+ description: String(record.Desc ?? ""),
1040
+ order: Number(record.Orden ?? 0)
1041
+ };
1042
+ }
1043
+ function mapWsfeReceiverVatCondition(raw) {
1044
+ const record = raw;
1045
+ return {
1046
+ id: Number(record.Id ?? 0),
1047
+ description: String(record.Desc ?? ""),
1048
+ voucherClass: String(record.Cmp_Clase ?? "")
1049
+ };
1050
+ }
1051
+ function mapWsfeCurrencyType(raw) {
1052
+ const record = raw;
1053
+ return {
1054
+ id: String(record.Id ?? ""),
1055
+ description: String(record.Desc ?? ""),
1056
+ validFrom: String(record.FchDesde ?? ""),
1057
+ validTo: String(record.FchHasta ?? "")
1058
+ };
1059
+ }
1060
+ function mapWsfeServerStatus(raw) {
1061
+ return {
1062
+ appServer: String(raw.AppServer ?? ""),
1063
+ dbServer: String(raw.DbServer ?? ""),
1064
+ authServer: String(raw.AuthServer ?? "")
1065
+ };
1066
+ }
1067
+ function mapWsfeQuotation(raw) {
1068
+ return {
1069
+ currencyId: String(raw.MonId ?? ""),
1070
+ rate: Number(raw.MonCotiz ?? 0),
1071
+ date: String(raw.FchCotiz ?? "")
1072
+ };
1073
+ }
1074
+ function mapWsfeVoucherInfo(raw) {
1075
+ const voucher = {
1076
+ voucherNumber: Number(raw.CbteDesde ?? raw.CbteHasta ?? 0),
1077
+ raw
1078
+ };
1079
+ assignWsfeValue(voucher, "voucherDate", normalizeWsfeString(raw.CbteFch));
1080
+ assignWsfeValue(voucher, "salesPoint", normalizeWsfeNumber(raw.PtoVta));
1081
+ assignWsfeValue(voucher, "voucherType", normalizeWsfeNumber(raw.CbteTipo));
1082
+ assignWsfeValue(voucher, "concept", normalizeWsfeNumber(raw.Concepto));
1083
+ assignWsfeValue(voucher, "documentType", normalizeWsfeNumber(raw.DocTipo));
1084
+ assignWsfeValue(voucher, "documentNumber", normalizeWsfeString(raw.DocNro));
1085
+ assignWsfeValue(
1086
+ voucher,
1087
+ "receiverVatConditionId",
1088
+ normalizeWsfeNumber(raw.CondicionIVAReceptorId)
1089
+ );
1090
+ assignWsfeValue(voucher, "totalAmount", normalizeWsfeNumber(raw.ImpTotal));
1091
+ assignWsfeValue(
1092
+ voucher,
1093
+ "nonTaxableAmount",
1094
+ normalizeWsfeNumber(raw.ImpTotConc)
1095
+ );
1096
+ assignWsfeValue(voucher, "netAmount", normalizeWsfeNumber(raw.ImpNeto));
1097
+ assignWsfeValue(voucher, "exemptAmount", normalizeWsfeNumber(raw.ImpOpEx));
1098
+ assignWsfeValue(voucher, "taxAmount", normalizeWsfeNumber(raw.ImpTrib));
1099
+ assignWsfeValue(voucher, "vatAmount", normalizeWsfeNumber(raw.ImpIVA));
1100
+ assignWsfeValue(voucher, "currencyId", normalizeWsfeString(raw.MonId));
1101
+ assignWsfeValue(voucher, "exchangeRate", normalizeWsfeNumber(raw.MonCotiz));
1102
+ assignWsfeValue(voucher, "result", normalizeWsfeString(raw.Resultado));
1103
+ assignWsfeValue(
1104
+ voucher,
1105
+ "cae",
1106
+ normalizeWsfeString(raw.CodAutorizacion ?? raw.CAE)
1107
+ );
1108
+ assignWsfeValue(
1109
+ voucher,
1110
+ "caeExpiry",
1111
+ normalizeWsfeString(raw.FchVto ?? raw.CAEFchVto)
1112
+ );
1113
+ return voucher;
1114
+ }
1115
+ function createWsfeAuth(representedTaxId, token, sign) {
1116
+ return {
1117
+ Token: token,
1118
+ Sign: sign,
1119
+ Cuit: Number.parseInt(String(representedTaxId), 10)
1120
+ };
1121
+ }
1122
+ function unwrapWsfeOperationEnvelope(operation, response) {
1123
+ const operationResponse = response[`${operation}Response`];
1124
+ const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
1125
+ return result;
1126
+ }
1127
+ function throwForWsfeOperationErrors(operation, result) {
1128
+ const errors = extractWsfeGlobalIssues(result, operation);
1129
+ if (errors.length > 0) {
1130
+ throw createWsfeServiceError(operation, errors);
1131
+ }
1132
+ }
1133
+ function normalizeWsfeDetailResponse(result) {
1134
+ const detailResponse = result.FeDetResp;
1135
+ const rawDetail = detailResponse?.FECAEDetResponse;
1136
+ if (Array.isArray(rawDetail)) {
1137
+ return rawDetail[0] ?? {};
1138
+ }
1139
+ return rawDetail ?? {};
1140
+ }
1141
+ function classifyWsfeAuthorization(result, voucherNumber) {
1142
+ const operation = "FECAESolicitar";
1143
+ const header = toWsfeRecord(result.FeCabResp) ?? {};
1144
+ const detail = normalizeWsfeDetailResponse(result);
1145
+ const headerResult = normalizeWsfeResult(header.Resultado);
1146
+ const detailResult = normalizeWsfeResult(detail.Resultado);
1147
+ const resultCode = detailResult ?? headerResult;
1148
+ const resultLevel = getWsfeResultLevel(headerResult, detailResult);
1149
+ const cae = normalizeWsfeString(detail.CAE);
1150
+ const caeExpiry = normalizeWsfeString(detail.CAEFchVto);
1151
+ const errors = extractWsfeGlobalIssues(result, operation, "header");
1152
+ const observations = extractWsfeObservations(
1153
+ detail,
1154
+ detailResult === "R" ? "business" : "observation"
1155
+ );
1156
+ const hasInfrastructureError = errors.some(
1157
+ (issue) => issue.category === "infrastructure"
1158
+ );
1159
+ const base = {
1160
+ service: "wsfe",
1161
+ operation,
1162
+ results: createWsfeResults(headerResult, detailResult),
1163
+ errors,
1164
+ observations,
1165
+ raw: result
1166
+ };
1167
+ const context = {
1168
+ base,
1169
+ headerResult,
1170
+ detailResult,
1171
+ resultCode,
1172
+ resultLevel,
1173
+ cae,
1174
+ caeExpiry
1175
+ };
1176
+ if (hasContradictoryWsfeResults(context)) {
1177
+ return createWsfeStructuredIndeterminate(context, "contradictory_response");
1178
+ }
1179
+ const authenticationError = classifyArcaAuthenticationIssues(errors, {
1180
+ service: "wsfe",
1181
+ operation
1182
+ });
1183
+ if (authenticationError && detailResult === void 0 && headerResult !== "A" && headerResult !== "O" && !cae) {
1184
+ return {
1185
+ ...createWsfeStructuredIndeterminate(context, "authentication_rejected"),
1186
+ authentication: createArcaAuthenticationEvidence(authenticationError)
1187
+ };
1188
+ }
1189
+ if (hasInfrastructureError) {
1190
+ return createWsfeStructuredIndeterminate(context, "incomplete_response");
1191
+ }
1192
+ if (isAuthorizedWsfeContext(context)) {
1193
+ return {
1194
+ ...base,
1195
+ kind: "authorized",
1196
+ result: "A",
1197
+ resultLevel: "detail",
1198
+ cae: context.cae,
1199
+ caeExpiry: context.caeExpiry,
1200
+ voucherNumber
1201
+ };
1202
+ }
1203
+ if (isRejectedWsfeDetailContext(context)) {
1204
+ return {
1205
+ ...base,
1206
+ kind: "rejected",
1207
+ result: "R",
1208
+ resultLevel: "detail"
1209
+ };
1210
+ }
1211
+ if (isRejectedWsfeHeaderContext(context)) {
1212
+ return {
1213
+ ...base,
1214
+ kind: "rejected",
1215
+ result: "R",
1216
+ resultLevel: "header"
1217
+ };
1218
+ }
1219
+ return createWsfeStructuredIndeterminate(
1220
+ context,
1221
+ hasWsfeCaeContradiction(context) ? "contradictory_response" : "incomplete_response"
1222
+ );
1223
+ }
1224
+ function getWsfeResultLevel(headerResult, detailResult) {
1225
+ if (detailResult) {
1226
+ return "detail";
1227
+ }
1228
+ return headerResult ? "header" : void 0;
1229
+ }
1230
+ function hasContradictoryWsfeResults(context) {
1231
+ return Boolean(
1232
+ context.headerResult && context.detailResult && context.headerResult !== context.detailResult
1233
+ );
1234
+ }
1235
+ function isAuthorizedWsfeContext(context) {
1236
+ return Boolean(
1237
+ context.detailResult === "A" && context.headerResult !== "R" && context.base.errors.length === 0 && context.cae && context.caeExpiry
1238
+ );
1239
+ }
1240
+ function isRejectedWsfeDetailContext(context) {
1241
+ return context.detailResult === "R" && context.headerResult !== "A" && !context.cae;
1242
+ }
1243
+ function isRejectedWsfeHeaderContext(context) {
1244
+ return context.headerResult === "R" && context.detailResult === void 0 && !context.cae && context.base.errors.length > 0 && context.base.errors.every((issue) => issue.category === "business");
1245
+ }
1246
+ function hasWsfeCaeContradiction(context) {
1247
+ return (context.resultCode === "A" || context.resultCode === "R") && Boolean(context.cae);
1248
+ }
1249
+ function createWsfeStructuredIndeterminate(context, reason) {
1250
+ const outcome = {
1251
+ ...context.base,
1252
+ kind: "indeterminate",
1253
+ reason
1254
+ };
1255
+ assignWsfeValue(outcome, "result", context.resultCode);
1256
+ assignWsfeValue(outcome, "resultLevel", context.resultLevel);
1257
+ assignWsfeValue(outcome, "cae", context.cae);
1258
+ assignWsfeValue(outcome, "caeExpiry", context.caeExpiry);
1259
+ return outcome;
1260
+ }
1261
+ function createWsfeResults(headerResult, detailResult) {
1262
+ const results = {};
1263
+ assignWsfeValue(results, "header", headerResult);
1264
+ assignWsfeValue(results, "detail", detailResult);
1265
+ return results;
1266
+ }
1267
+ function createWsfeIndeterminateOutcome(error) {
1268
+ const authenticationError = classifyArcaAuthenticationError(error, {
1269
+ service: "wsfe",
1270
+ operation: "FECAESolicitar"
1271
+ });
1272
+ return {
1273
+ kind: "indeterminate",
1274
+ service: "wsfe",
1275
+ operation: "FECAESolicitar",
1276
+ results: {},
1277
+ reason: authenticationError ? "authentication_rejected" : getArcaIndeterminateReason(error),
1278
+ ...authenticationError ? {
1279
+ authentication: createArcaAuthenticationEvidence(authenticationError)
1280
+ } : {},
1281
+ errors: [],
1282
+ observations: []
1283
+ };
1284
+ }
1285
+ function getArcaIndeterminateReason(error) {
1286
+ if (error instanceof ArcaTransportError) {
1287
+ return "transport_error";
1288
+ }
1289
+ if (error instanceof ArcaSoapFaultError) {
1290
+ return "soap_fault";
1291
+ }
1292
+ if (error instanceof ArcaInvalidSoapResponseError) {
1293
+ return "invalid_response";
1294
+ }
1295
+ return "unexpected_error";
1296
+ }
1297
+ function createWsfeOutcomeError(outcome) {
1298
+ if (outcome.kind === "indeterminate" && outcome.authentication) {
1299
+ return createArcaAuthenticationErrorFromEvidence(outcome.authentication, {
1300
+ service: "wsfe",
1301
+ operation: outcome.operation
1302
+ });
1303
+ }
1304
+ const issues = [...outcome.errors, ...outcome.observations];
1305
+ const firstIssue = issues[0];
1306
+ const message = firstIssue ? formatWsfeIssue(firstIssue) : outcome.kind === "rejected" ? "WSFE rejected the voucher authorization" : outcome.result === "A" ? "WSFE did not return CAE authorization data" : "WSFE did not return conclusive voucher authorization data";
1307
+ return new ArcaServiceError(message, {
1308
+ service: "wsfe",
1309
+ operation: outcome.operation,
1310
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1311
+ ...outcome.result === void 0 ? {} : { result: outcome.result },
1312
+ ...outcome.resultLevel === void 0 ? {} : { resultLevel: outcome.resultLevel },
1313
+ results: outcome.results,
1314
+ ...outcome.kind === "indeterminate" && outcome.cae ? { cae: outcome.cae } : {},
1315
+ issues
1316
+ });
1317
+ }
1318
+ function createWsfeServiceError(operation, issues) {
1319
+ const authenticationError = classifyArcaAuthenticationIssues(issues, {
1320
+ service: "wsfe",
1321
+ operation
1322
+ });
1323
+ if (authenticationError) {
1324
+ return authenticationError;
1325
+ }
1326
+ const firstIssue = issues[0];
1327
+ return new ArcaServiceError(
1328
+ firstIssue ? formatWsfeIssue(firstIssue) : "WSFE returned a service error",
1329
+ {
1330
+ service: "wsfe",
1331
+ operation,
1332
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1333
+ issues
1334
+ }
1335
+ );
1336
+ }
1337
+ function extractWsfeGlobalIssues(result, operation, resultLevel) {
1338
+ const errorsContainer = toWsfeRecord(result.Errors);
1339
+ return normalizeWsfeIssueEntries(errorsContainer?.Err).map((entry) => ({
1340
+ service: "wsfe",
1341
+ operation,
1342
+ source: "error",
1343
+ category: operation === "FECAESolicitar" && WSFE_AUTHORIZATION_INFRASTRUCTURE_CODES.has(entry.code ?? "") ? "infrastructure" : operation === "FECAESolicitar" ? "business" : "unknown",
1344
+ ...entry.code === void 0 ? {} : { code: entry.code },
1345
+ message: entry.message,
1346
+ ...resultLevel === void 0 ? {} : { resultLevel }
1347
+ }));
1348
+ }
1349
+ function extractWsfeObservations(detail, category) {
1350
+ const observationsContainer = toWsfeRecord(detail.Observaciones);
1351
+ return normalizeWsfeIssueEntries(observationsContainer?.Obs).map((entry) => ({
1352
+ service: "wsfe",
1353
+ operation: "FECAESolicitar",
1354
+ source: "observation",
1355
+ category,
1356
+ ...entry.code === void 0 ? {} : { code: entry.code },
1357
+ message: entry.message,
1358
+ resultLevel: "detail"
1359
+ }));
1360
+ }
1361
+ function normalizeWsfeIssueEntries(rawErrors) {
1362
+ const entries = Array.isArray(rawErrors) ? rawErrors : rawErrors ? [rawErrors] : [];
1363
+ return entries.map((entry) => entry).map((entry) => {
1364
+ const code = entry.Code ?? entry.code;
1365
+ const message = entry.Msg ?? entry.msg ?? "Unknown WSFE error";
1366
+ return {
1367
+ ...code === void 0 ? {} : { code: String(code) },
1368
+ message: String(message)
1369
+ };
1370
+ });
1371
+ }
1372
+ function formatWsfeIssue(issue) {
1373
+ return issue.code ? `(${issue.code}) ${issue.message}` : issue.message;
1374
+ }
1375
+ function normalizeWsfeResult(value) {
1376
+ if (typeof value !== "string") {
1377
+ return void 0;
1378
+ }
1379
+ const normalized = value.trim().toUpperCase();
1380
+ return normalized || void 0;
1381
+ }
1382
+ function normalizeWsfeString(value) {
1383
+ if (value === void 0 || value === null) {
1384
+ return void 0;
1385
+ }
1386
+ const normalized = String(value).trim();
1387
+ return normalized || void 0;
1388
+ }
1389
+ function normalizeWsfeNumber(value) {
1390
+ if (value === void 0 || value === null || value === "") {
1391
+ return void 0;
1392
+ }
1393
+ const normalized = Number(value);
1394
+ return Number.isFinite(normalized) ? normalized : void 0;
1395
+ }
1396
+ function assignWsfeValue(target, key, value) {
1397
+ if (value !== void 0) {
1398
+ target[key] = value;
1399
+ }
1400
+ }
1401
+ function toWsfeRecord(value) {
1402
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1403
+ }
1404
+ var WSFE_AUTHORIZATION_INFRASTRUCTURE_CODES = /* @__PURE__ */ new Set([
1405
+ "500",
1406
+ "501",
1407
+ "502",
1408
+ "600",
1409
+ "601"
1410
+ ]);
1411
+ function getWsfeResultEntries(result, key) {
1412
+ const rawEntries = result.ResultGet?.[key];
1413
+ if (!rawEntries) {
1414
+ return [];
1415
+ }
1416
+ return (Array.isArray(rawEntries) ? rawEntries : [rawEntries]).map(
1417
+ (entry) => entry
1418
+ );
1419
+ }
1420
+
1421
+ // src/services/wsfe-builders.ts
1422
+ var VAT_RATE_IDS = {
1423
+ 0: ARCA_VAT_RATES.IVA_0,
1424
+ 2.5: ARCA_VAT_RATES.IVA_2_5,
1425
+ 5: ARCA_VAT_RATES.IVA_5,
1426
+ 10.5: ARCA_VAT_RATES.IVA_10_5,
1427
+ 21: ARCA_VAT_RATES.IVA_21,
1428
+ 27: ARCA_VAT_RATES.IVA_27
1429
+ };
1430
+ function buildFacturaB(input) {
1431
+ const taxableMinorUnits = assertArcaMinorUnits(
1432
+ input.taxableAmount,
1433
+ "taxableAmount"
1434
+ );
1435
+ if (taxableMinorUnits === 0n) {
1436
+ throw new ArcaInputError(
1437
+ "taxableAmount must be greater than zero for Factura B.",
1438
+ {
1439
+ code: "ARCA_INPUT_INVALID_AMOUNT",
1440
+ field: "taxableAmount",
1441
+ expected: "a positive safe integer in currency minor units"
1442
+ }
1443
+ );
1444
+ }
1445
+ const vatMinorUnits = calculateVatMinorUnits(
1446
+ taxableMinorUnits,
1447
+ input.vatRate,
1448
+ "vatRate"
1449
+ );
1450
+ const totalMinorUnits = taxableMinorUnits + vatMinorUnits;
1451
+ const vatRateId = VAT_RATE_IDS[input.vatRate];
1452
+ if (vatRateId === void 0) {
1453
+ throw new ArcaInputError("vatRate is not a supported VAT rate.", {
1454
+ code: "ARCA_INPUT_INVALID_VALUE",
1455
+ field: "vatRate",
1456
+ expected: "one of 0, 2.5, 5, 10.5, 21, or 27"
1457
+ });
1458
+ }
1459
+ if (input.vatRate !== 0 && vatMinorUnits === 0n) {
1460
+ throw new ArcaInputError(
1461
+ "taxableAmount is too small to produce VAT at the selected positive vatRate.",
1462
+ {
1463
+ code: "ARCA_INPUT_INVALID_AMOUNT",
1464
+ field: "taxableAmount",
1465
+ expected: "an amount that rounds to at least one currency minor unit of VAT for a positive vatRate"
1466
+ }
1467
+ );
1468
+ }
1469
+ const vatRates = [
1470
+ Object.freeze({
1471
+ id: vatRateId,
1472
+ baseAmount: arcaMinorUnitsToNumber(taxableMinorUnits, "taxableAmount"),
1473
+ amount: arcaMinorUnitsToNumber(vatMinorUnits, "vatAmount")
1474
+ })
1475
+ ];
1476
+ Object.freeze(vatRates);
1477
+ return Object.freeze({
1478
+ ...buildCommonExactInput(input),
1479
+ voucherType: ARCA_VOUCHER_TYPES.FACTURA_B,
1480
+ totalAmount: arcaMinorUnitsToNumber(totalMinorUnits, "totalAmount"),
1481
+ nonTaxableAmount: 0,
1482
+ netAmount: arcaMinorUnitsToNumber(taxableMinorUnits, "taxableAmount"),
1483
+ exemptAmount: 0,
1484
+ taxAmount: 0,
1485
+ vatAmount: arcaMinorUnitsToNumber(vatMinorUnits, "vatAmount"),
1486
+ vatRates
1487
+ });
1488
+ }
1489
+ function buildFacturaC(input) {
1490
+ const amountMinorUnits = assertArcaMinorUnits(input.amount, "amount");
1491
+ const amount = arcaMinorUnitsToNumber(amountMinorUnits, "amount");
1492
+ return Object.freeze({
1493
+ ...buildCommonExactInput(input),
1494
+ voucherType: ARCA_VOUCHER_TYPES.FACTURA_C,
1495
+ totalAmount: amount,
1496
+ nonTaxableAmount: 0,
1497
+ // ARCA defines ImpNeto as the subtotal for class C vouchers.
1498
+ netAmount: amount,
1499
+ exemptAmount: 0,
1500
+ taxAmount: 0,
1501
+ vatAmount: 0
1502
+ });
1503
+ }
1504
+ function buildCommonExactInput(input) {
1505
+ return {
1506
+ salesPoint: input.salesPoint,
1507
+ concept: input.concept,
1508
+ documentType: input.documentType,
1509
+ documentNumber: input.documentNumber,
1510
+ receiverVatConditionId: input.receiverVatConditionId,
1511
+ voucherDate: input.voucherDate,
1512
+ ...normalizeBuilderCurrency(input),
1513
+ ...input.serviceStartDate === void 0 ? {} : { serviceStartDate: input.serviceStartDate },
1514
+ ...input.serviceEndDate === void 0 ? {} : { serviceEndDate: input.serviceEndDate },
1515
+ ...input.paymentDueDate === void 0 ? {} : { paymentDueDate: input.paymentDueDate }
1516
+ };
1517
+ }
1518
+ function normalizeBuilderCurrency(input) {
1519
+ const unsafeInput = input;
1520
+ const currency = unsafeInput.currency ?? "ARS";
1521
+ if (unsafeInput.sameCurrencyForeignCancellation !== void 0 && typeof unsafeInput.sameCurrencyForeignCancellation !== "boolean") {
1522
+ throw new ArcaInputError(
1523
+ "sameCurrencyForeignCancellation must be a boolean when provided.",
1524
+ {
1525
+ code: "ARCA_INPUT_INVALID_VALUE",
1526
+ field: "sameCurrencyForeignCancellation",
1527
+ expected: "true, false, or omitted"
1528
+ }
1529
+ );
1530
+ }
1531
+ if (currency === "ARS") {
1532
+ if (unsafeInput.exchangeRate !== void 0) {
1533
+ throw new ArcaInputError(
1534
+ "exchangeRate must be omitted when currency is ARS.",
1535
+ {
1536
+ code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
1537
+ field: "exchangeRate",
1538
+ expected: "omitted when currency is ARS"
1539
+ }
1540
+ );
1541
+ }
1542
+ if (unsafeInput.sameCurrencyForeignCancellation !== void 0) {
1543
+ throw new ArcaInputError(
1544
+ "sameCurrencyForeignCancellation applies only when currency is USD.",
1545
+ {
1546
+ code: "ARCA_INPUT_INVALID_VALUE",
1547
+ field: "sameCurrencyForeignCancellation",
1548
+ expected: "omitted when currency is ARS"
1549
+ }
1550
+ );
1551
+ }
1552
+ return {
1553
+ currencyId: ARCA_CURRENCY_IDS.ARS,
1554
+ exchangeRate: "1"
1555
+ };
1556
+ }
1557
+ if (currency !== "USD") {
1558
+ throw new ArcaInputError("currency is not supported by this builder.", {
1559
+ code: "ARCA_INPUT_INVALID_VALUE",
1560
+ field: "currency",
1561
+ expected: "ARS or USD"
1562
+ });
1563
+ }
1564
+ if (unsafeInput.sameCurrencyForeignCancellation === true) {
1565
+ if (unsafeInput.exchangeRate !== void 0) {
1566
+ throw new ArcaInputError(
1567
+ "exchangeRate must be omitted for same-currency foreign cancellation.",
1568
+ {
1569
+ code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
1570
+ field: "exchangeRate",
1571
+ expected: "omitted when sameCurrencyForeignCancellation is true"
1572
+ }
1573
+ );
1574
+ }
1575
+ return {
1576
+ currencyId: ARCA_CURRENCY_IDS.USD,
1577
+ sameCurrencyForeignCancellation: "S"
1578
+ };
1579
+ }
1580
+ if (typeof unsafeInput.exchangeRate !== "string") {
1581
+ throw new ArcaInputError("exchangeRate is required for USD invoices.", {
1582
+ code: "ARCA_INPUT_MISSING_FIELD",
1583
+ field: "exchangeRate",
1584
+ expected: "a decimal string unless sameCurrencyForeignCancellation is true"
1585
+ });
1586
+ }
1587
+ return {
1588
+ currencyId: ARCA_CURRENCY_IDS.USD,
1589
+ exchangeRate: serializeArcaExchangeRate(
1590
+ unsafeInput.exchangeRate,
1591
+ "exchangeRate"
1592
+ ),
1593
+ ...unsafeInput.sameCurrencyForeignCancellation === false ? { sameCurrencyForeignCancellation: "N" } : {}
1594
+ };
1595
+ }
1596
+
1597
+ export {
1598
+ createWsfeService,
1599
+ buildFacturaB,
1600
+ buildFacturaC
1601
+ };
1602
+ //# sourceMappingURL=chunk-OHHXHYLV.mjs.map