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