facturas 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -68,10 +68,24 @@ var ArcaInvalidSoapResponseError = class extends ArcaError {
68
68
  var ArcaServiceError = class extends ArcaError {
69
69
  name = "ArcaServiceError";
70
70
  serviceCode;
71
+ service;
72
+ operation;
73
+ result;
74
+ resultLevel;
75
+ results;
76
+ cae;
77
+ issues;
71
78
  detail;
72
79
  constructor(message, options) {
73
80
  super(message, "ARCA_SERVICE_ERROR", options);
74
81
  this.serviceCode = options?.serviceCode;
82
+ this.service = options?.service;
83
+ this.operation = options?.operation;
84
+ this.result = options?.result;
85
+ this.resultLevel = options?.resultLevel;
86
+ this.results = options?.results;
87
+ this.cae = options?.cae;
88
+ this.issues = options?.issues;
75
89
  this.detail = options?.detail;
76
90
  }
77
91
  };
@@ -86,9 +100,10 @@ var ARCA_ENV_VARIABLES = {
86
100
  };
87
101
  var PRIVATE_KEY_PEM_PREFIXES = [
88
102
  "-----BEGIN PRIVATE KEY-----",
89
- "-----BEGIN RSA PRIVATE KEY-----",
90
- "-----BEGIN ENCRYPTED PRIVATE KEY-----"
103
+ "-----BEGIN RSA PRIVATE KEY-----"
91
104
  ];
105
+ var ENCRYPTED_PRIVATE_KEY_PEM_PREFIX = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
106
+ var LEGACY_ENCRYPTED_RSA_PRIVATE_KEY_PATTERN = /^-----BEGIN RSA PRIVATE KEY-----[\s\S]*^Proc-Type:\s*4,\s*ENCRYPTED\s*$/m;
92
107
  var VALID_ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
93
108
  var DEFAULT_ARCA_TIMEOUT_MS = 3e4;
94
109
  var DEFAULT_ARCA_RETRIES = 0;
@@ -119,6 +134,11 @@ function assertArcaClientConfig(config) {
119
134
  const timeout = normalized.timeout ?? DEFAULT_ARCA_TIMEOUT_MS;
120
135
  const retries = normalized.retries ?? DEFAULT_ARCA_RETRIES;
121
136
  const retryDelay = normalized.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS;
137
+ if (normalized.privateKeyPem.startsWith(ENCRYPTED_PRIVATE_KEY_PEM_PREFIX) || LEGACY_ENCRYPTED_RSA_PRIVATE_KEY_PATTERN.test(normalized.privateKeyPem)) {
138
+ throw new ArcaConfigurationError(
139
+ "Encrypted private keys are not supported. Provide an unencrypted PKCS#8 or RSA private key PEM."
140
+ );
141
+ }
122
142
  if (!/^\d{11}$/.test(normalized.taxId)) {
123
143
  invalidFields.push("taxId");
124
144
  }
@@ -432,7 +452,7 @@ async function executePadronOperation(options, service, operation, body) {
432
452
 
433
453
  // src/services/wsfe.ts
434
454
  function createWsfeService(options) {
435
- async function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
455
+ async function executeWsfeAuthenticatedRawOperation(operation, input, body = {}, retries) {
436
456
  const auth = await options.auth.login("wsfe", {
437
457
  representedTaxId: input.representedTaxId,
438
458
  forceRefresh: input.forceRefresh
@@ -440,6 +460,7 @@ function createWsfeService(options) {
440
460
  const response = await options.soap.execute({
441
461
  service: "wsfe",
442
462
  operation,
463
+ ...retries === void 0 ? {} : { retries },
443
464
  body: {
444
465
  Auth: createWsfeAuth(
445
466
  input.representedTaxId ?? options.config.taxId,
@@ -449,7 +470,16 @@ function createWsfeService(options) {
449
470
  ...body
450
471
  }
451
472
  });
452
- return unwrapWsfeOperationResult(operation, response.result);
473
+ return unwrapWsfeOperationEnvelope(operation, response.result);
474
+ }
475
+ async function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
476
+ const result = await executeWsfeAuthenticatedRawOperation(
477
+ operation,
478
+ input,
479
+ body
480
+ );
481
+ throwForWsfeOperationErrors(operation, result);
482
+ return result;
453
483
  }
454
484
  async function executeWsfeOperation(operation, body = {}) {
455
485
  const response = await options.soap.execute({
@@ -457,7 +487,9 @@ function createWsfeService(options) {
457
487
  operation,
458
488
  body
459
489
  });
460
- return unwrapWsfeOperationResult(operation, response.result);
490
+ const result = unwrapWsfeOperationEnvelope(operation, response.result);
491
+ throwForWsfeOperationErrors(operation, result);
492
+ return result;
461
493
  }
462
494
  async function getNextVoucherNumber({
463
495
  representedTaxId,
@@ -499,55 +531,148 @@ function createWsfeService(options) {
499
531
  forceRefresh
500
532
  });
501
533
  }
534
+ function authorizeVoucherOutcome({
535
+ representedTaxId,
536
+ data,
537
+ voucherNumber,
538
+ forceRefresh
539
+ }) {
540
+ const normalizedInput = normalizeWsfeVoucherInput(data);
541
+ return executeWsfeAuthorization({
542
+ representedTaxId,
543
+ data: normalizedInput,
544
+ voucherNumber,
545
+ forceRefresh
546
+ }).then(({ outcome }) => outcome);
547
+ }
502
548
  async function authorizeNormalizedVoucher({
503
549
  representedTaxId,
504
550
  data: normalizedInput,
505
551
  voucherNumber,
506
552
  forceRefresh
507
553
  }) {
508
- const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
509
- const auth = await options.auth.login("wsfe", {
554
+ const execution = await executeWsfeAuthorization({
510
555
  representedTaxId,
556
+ data: normalizedInput,
557
+ voucherNumber,
511
558
  forceRefresh
512
559
  });
513
- const response = await options.soap.execute({
514
- service: "wsfe",
515
- operation: "FECAESolicitar",
516
- body: {
517
- Auth: createWsfeAuth(
518
- representedTaxId ?? options.config.taxId,
519
- auth.token,
520
- auth.sign
521
- ),
522
- FeCAEReq: {
523
- FeCabReq: {
524
- CantReg: 1,
525
- PtoVta: normalizedInput.salesPoint,
526
- CbteTipo: normalizedInput.voucherType
527
- },
528
- FeDetReq: {
529
- FECAEDetRequest: requestData
560
+ if (execution.error) {
561
+ throw execution.error;
562
+ }
563
+ if (execution.outcome.kind !== "authorized") {
564
+ throw createWsfeOutcomeError(execution.outcome);
565
+ }
566
+ const { cae, caeExpiry, raw } = execution.outcome;
567
+ if (!(caeExpiry && raw)) {
568
+ throw new ArcaServiceError("WSFE did not return CAE authorization data", {
569
+ service: "wsfe",
570
+ operation: "FECAESolicitar",
571
+ result: execution.outcome.result,
572
+ resultLevel: execution.outcome.resultLevel,
573
+ results: execution.outcome.results,
574
+ cae,
575
+ issues: [
576
+ ...execution.outcome.errors,
577
+ ...execution.outcome.observations
578
+ ],
579
+ detail: raw
580
+ });
581
+ }
582
+ return {
583
+ cae,
584
+ caeExpiry,
585
+ voucherNumber,
586
+ raw
587
+ };
588
+ }
589
+ async function executeWsfeAuthorization({
590
+ representedTaxId,
591
+ data: normalizedInput,
592
+ voucherNumber,
593
+ forceRefresh
594
+ }) {
595
+ const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
596
+ try {
597
+ const result = await executeWsfeAuthenticatedRawOperation(
598
+ "FECAESolicitar",
599
+ { representedTaxId, forceRefresh },
600
+ {
601
+ FeCAEReq: {
602
+ FeCabReq: {
603
+ CantReg: 1,
604
+ PtoVta: normalizedInput.salesPoint,
605
+ CbteTipo: normalizedInput.voucherType
606
+ },
607
+ FeDetReq: {
608
+ FECAEDetRequest: requestData
609
+ }
530
610
  }
611
+ },
612
+ 0
613
+ );
614
+ return {
615
+ outcome: classifyWsfeAuthorization(result, voucherNumber)
616
+ };
617
+ } catch (error) {
618
+ return {
619
+ outcome: createWsfeIndeterminateOutcome(error),
620
+ error
621
+ };
622
+ }
623
+ }
624
+ async function lookupVoucher({
625
+ representedTaxId,
626
+ number,
627
+ salesPoint,
628
+ voucherType,
629
+ forceRefresh
630
+ }) {
631
+ const operation = "FECompConsultar";
632
+ const result = await executeWsfeAuthenticatedRawOperation(
633
+ operation,
634
+ { representedTaxId, forceRefresh },
635
+ {
636
+ FeCompConsReq: {
637
+ CbteNro: number,
638
+ PtoVta: salesPoint,
639
+ CbteTipo: voucherType
531
640
  }
532
641
  }
533
- });
534
- const result = unwrapWsfeOperationResult("FECAESolicitar", response.result);
535
- const detailResponse = normalizeWsfeDetailResponse(result);
536
- const cae = detailResponse.CAE;
537
- const caeExpiry = detailResponse.CAEFchVto;
538
- if (typeof cae !== "string" || typeof caeExpiry !== "string") {
539
- throw new ArcaServiceError("WSFE did not return CAE authorization data", {
642
+ );
643
+ const errors = extractWsfeGlobalIssues(result, operation);
644
+ if (errors.length > 0 && errors.every((issue) => issue.code === "602")) {
645
+ return {
646
+ kind: "not_found",
647
+ service: "wsfe",
648
+ operation,
649
+ errors,
650
+ observations: [],
651
+ raw: result
652
+ };
653
+ }
654
+ if (errors.length > 0) {
655
+ throw createWsfeServiceError(operation, result, errors);
656
+ }
657
+ const raw = toWsfeRecord(result.ResultGet);
658
+ if (!raw) {
659
+ throw new ArcaServiceError("WSFE did not return the consulted voucher", {
660
+ service: "wsfe",
661
+ operation,
540
662
  detail: result
541
663
  });
542
664
  }
543
665
  return {
544
- cae,
545
- caeExpiry: String(caeExpiry),
546
- voucherNumber,
666
+ kind: "found",
667
+ service: "wsfe",
668
+ operation,
669
+ voucher: mapWsfeVoucherInfo(raw),
670
+ observations: [],
547
671
  raw: result
548
672
  };
549
673
  }
550
674
  return {
675
+ authorizeVoucherOutcome,
551
676
  authorizeVoucher,
552
677
  async createNextVoucher({ representedTaxId, data, forceRefresh }) {
553
678
  const normalizedInput = normalizeWsfeVoucherInput(data);
@@ -659,42 +784,14 @@ function createWsfeService(options) {
659
784
  const raw = result.ResultGet ?? {};
660
785
  return mapWsfeQuotation(raw);
661
786
  },
662
- async getVoucherInfo({
663
- representedTaxId,
664
- number,
665
- salesPoint,
666
- voucherType,
667
- forceRefresh
668
- }) {
669
- const result = await executeWsfeAuthenticatedOperation(
670
- "FECompConsultar",
671
- {
672
- representedTaxId,
673
- forceRefresh
674
- },
675
- {
676
- FeCompConsReq: {
677
- CbteNro: number,
678
- PtoVta: salesPoint,
679
- CbteTipo: voucherType
680
- }
681
- }
682
- );
683
- const raw = result.ResultGet ?? null;
684
- if (!raw) {
685
- return null;
686
- }
687
- return mapWsfeVoucherInfo(raw);
688
- }
787
+ async getVoucherInfo(input) {
788
+ const lookup = await lookupVoucher(input);
789
+ return lookup.kind === "found" ? lookup.voucher : null;
790
+ },
791
+ lookupVoucher
689
792
  };
690
793
  }
691
794
  function mapWsfeVoucherInput(input, voucherNumber) {
692
- const sendsSameForeignCurrencyCancellation = input.currencyId !== "PES" && input.sameCurrencyForeignCancellation === "S";
693
- if (input.exchangeRate === void 0 && !sendsSameForeignCurrencyCancellation) {
694
- throw new ArcaInputError(
695
- "exchangeRate is required unless sameCurrencyForeignCancellation is S for a foreign-currency voucher."
696
- );
697
- }
698
795
  const data = {
699
796
  Concepto: input.concept,
700
797
  DocTipo: input.documentType,
@@ -712,7 +809,7 @@ function mapWsfeVoucherInput(input, voucherNumber) {
712
809
  PtoVta: input.salesPoint,
713
810
  CbteTipo: input.voucherType
714
811
  };
715
- if (!sendsSameForeignCurrencyCancellation) {
812
+ if (input.exchangeRate !== void 0) {
716
813
  data.MonCotiz = input.exchangeRate;
717
814
  }
718
815
  if (input.receiverVatConditionId !== void 0) {
@@ -796,6 +893,7 @@ function mapWsfeVoucherInput(input, voucherNumber) {
796
893
  function normalizeWsfeVoucherInput(input) {
797
894
  const {
798
895
  voucherDate,
896
+ exchangeRate,
799
897
  serviceStartDate,
800
898
  serviceEndDate,
801
899
  paymentDueDate,
@@ -803,9 +901,11 @@ function normalizeWsfeVoucherInput(input) {
803
901
  associatedPeriod,
804
902
  ...rest
805
903
  } = input;
904
+ const normalizedExchangeRate = normalizeWsfeExchangeRate(input, exchangeRate);
806
905
  return {
807
906
  ...rest,
808
907
  voucherDate: normalizeWsfeDateInput(voucherDate, "voucherDate"),
908
+ ...normalizedExchangeRate === void 0 ? {} : { exchangeRate: normalizedExchangeRate },
809
909
  ...serviceStartDate === void 0 ? {} : {
810
910
  serviceStartDate: normalizeWsfeDateInput(
811
911
  serviceStartDate,
@@ -852,6 +952,30 @@ function normalizeWsfeVoucherInput(input) {
852
952
  }
853
953
  };
854
954
  }
955
+ function normalizeWsfeExchangeRate(input, exchangeRate) {
956
+ if (input.currencyId === "PES") {
957
+ if (exchangeRate !== void 0 && exchangeRate !== 1) {
958
+ throw new ArcaInputError(
959
+ "exchangeRate must be 1 when currencyId is PES."
960
+ );
961
+ }
962
+ return 1;
963
+ }
964
+ if (exchangeRate === void 0) {
965
+ if (input.sameCurrencyForeignCancellation === "S") {
966
+ return void 0;
967
+ }
968
+ throw new ArcaInputError(
969
+ "exchangeRate is required unless sameCurrencyForeignCancellation is S for a foreign-currency voucher."
970
+ );
971
+ }
972
+ if (!(Number.isFinite(exchangeRate) && exchangeRate > 0)) {
973
+ throw new ArcaInputError(
974
+ "exchangeRate must be a positive finite number for a foreign-currency voucher."
975
+ );
976
+ }
977
+ return exchangeRate;
978
+ }
855
979
  function normalizeWsfeDateInput(value, fieldName) {
856
980
  if (typeof value !== "string") {
857
981
  throw new ArcaInputError(
@@ -951,17 +1075,45 @@ function mapWsfeQuotation(raw) {
951
1075
  };
952
1076
  }
953
1077
  function mapWsfeVoucherInfo(raw) {
954
- return {
1078
+ const voucher = {
955
1079
  voucherNumber: Number(raw.CbteDesde ?? raw.CbteHasta ?? 0),
956
- ...raw.CbteFch === void 0 ? {} : { voucherDate: String(raw.CbteFch) },
957
- ...raw.PtoVta === void 0 ? {} : { salesPoint: Number(raw.PtoVta) },
958
- ...raw.CbteTipo === void 0 ? {} : { voucherType: Number(raw.CbteTipo) },
959
- ...raw.ImpTotal === void 0 ? {} : { totalAmount: Number(raw.ImpTotal) },
960
- ...raw.Resultado === void 0 ? {} : { result: String(raw.Resultado) },
961
- ...raw.CAE === void 0 ? {} : { cae: String(raw.CAE) },
962
- ...raw.CAEFchVto === void 0 ? {} : { caeExpiry: String(raw.CAEFchVto) },
963
1080
  raw
964
1081
  };
1082
+ assignWsfeValue(voucher, "voucherDate", normalizeWsfeString(raw.CbteFch));
1083
+ assignWsfeValue(voucher, "salesPoint", normalizeWsfeNumber(raw.PtoVta));
1084
+ assignWsfeValue(voucher, "voucherType", normalizeWsfeNumber(raw.CbteTipo));
1085
+ assignWsfeValue(voucher, "concept", normalizeWsfeNumber(raw.Concepto));
1086
+ assignWsfeValue(voucher, "documentType", normalizeWsfeNumber(raw.DocTipo));
1087
+ assignWsfeValue(voucher, "documentNumber", normalizeWsfeString(raw.DocNro));
1088
+ assignWsfeValue(
1089
+ voucher,
1090
+ "receiverVatConditionId",
1091
+ normalizeWsfeNumber(raw.CondicionIVAReceptorId)
1092
+ );
1093
+ assignWsfeValue(voucher, "totalAmount", normalizeWsfeNumber(raw.ImpTotal));
1094
+ assignWsfeValue(
1095
+ voucher,
1096
+ "nonTaxableAmount",
1097
+ normalizeWsfeNumber(raw.ImpTotConc)
1098
+ );
1099
+ assignWsfeValue(voucher, "netAmount", normalizeWsfeNumber(raw.ImpNeto));
1100
+ assignWsfeValue(voucher, "exemptAmount", normalizeWsfeNumber(raw.ImpOpEx));
1101
+ assignWsfeValue(voucher, "taxAmount", normalizeWsfeNumber(raw.ImpTrib));
1102
+ assignWsfeValue(voucher, "vatAmount", normalizeWsfeNumber(raw.ImpIVA));
1103
+ assignWsfeValue(voucher, "currencyId", normalizeWsfeString(raw.MonId));
1104
+ assignWsfeValue(voucher, "exchangeRate", normalizeWsfeNumber(raw.MonCotiz));
1105
+ assignWsfeValue(voucher, "result", normalizeWsfeString(raw.Resultado));
1106
+ assignWsfeValue(
1107
+ voucher,
1108
+ "cae",
1109
+ normalizeWsfeString(raw.CodAutorizacion ?? raw.CAE)
1110
+ );
1111
+ assignWsfeValue(
1112
+ voucher,
1113
+ "caeExpiry",
1114
+ normalizeWsfeString(raw.FchVto ?? raw.CAEFchVto)
1115
+ );
1116
+ return voucher;
965
1117
  }
966
1118
  function createWsfeAuth(representedTaxId, token, sign) {
967
1119
  return {
@@ -970,47 +1122,16 @@ function createWsfeAuth(representedTaxId, token, sign) {
970
1122
  Cuit: Number.parseInt(String(representedTaxId), 10)
971
1123
  };
972
1124
  }
973
- function unwrapWsfeOperationResult(operation, response) {
1125
+ function unwrapWsfeOperationEnvelope(operation, response) {
974
1126
  const operationResponse = response[`${operation}Response`];
975
1127
  const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
976
- if (operation === "FECAESolicitar") {
977
- const detailResponse = normalizeWsfeDetailResponse(result);
978
- const resultCode = detailResponse.Resultado;
979
- if (resultCode && resultCode !== "A") {
980
- const observationsContainer = detailResponse.Observaciones;
981
- const observations = normalizeWsfeErrors(observationsContainer?.Obs);
982
- if (observations.length > 0) {
983
- const firstObservation = observations[0];
984
- if (!firstObservation) {
985
- throw new ArcaServiceError(
986
- "WSFE returned an empty observation list",
987
- {
988
- detail: result
989
- }
990
- );
991
- }
992
- throw new ArcaServiceError(firstObservation.message, {
993
- serviceCode: firstObservation.code,
994
- detail: result
995
- });
996
- }
997
- }
998
- }
999
- const errorsContainer = result.Errors;
1000
- const errors = normalizeWsfeErrors(errorsContainer?.Err);
1128
+ return result;
1129
+ }
1130
+ function throwForWsfeOperationErrors(operation, result) {
1131
+ const errors = extractWsfeGlobalIssues(result, operation);
1001
1132
  if (errors.length > 0) {
1002
- const firstError = errors[0];
1003
- if (!firstError) {
1004
- throw new ArcaServiceError("WSFE returned an empty error list", {
1005
- detail: result
1006
- });
1007
- }
1008
- throw new ArcaServiceError(firstError.message, {
1009
- serviceCode: firstError.code,
1010
- detail: result
1011
- });
1133
+ throw createWsfeServiceError(operation, result, errors);
1012
1134
  }
1013
- return result;
1014
1135
  }
1015
1136
  function normalizeWsfeDetailResponse(result) {
1016
1137
  const detailResponse = result.FeDetResp;
@@ -1020,17 +1141,248 @@ function normalizeWsfeDetailResponse(result) {
1020
1141
  }
1021
1142
  return rawDetail ?? {};
1022
1143
  }
1023
- function normalizeWsfeErrors(rawErrors) {
1144
+ function classifyWsfeAuthorization(result, voucherNumber) {
1145
+ const operation = "FECAESolicitar";
1146
+ const header = toWsfeRecord(result.FeCabResp) ?? {};
1147
+ const detail = normalizeWsfeDetailResponse(result);
1148
+ const headerResult = normalizeWsfeResult(header.Resultado);
1149
+ const detailResult = normalizeWsfeResult(detail.Resultado);
1150
+ const resultCode = detailResult ?? headerResult;
1151
+ const resultLevel = getWsfeResultLevel(headerResult, detailResult);
1152
+ const cae = normalizeWsfeString(detail.CAE);
1153
+ const caeExpiry = normalizeWsfeString(detail.CAEFchVto);
1154
+ const errors = extractWsfeGlobalIssues(result, operation, "header");
1155
+ const observations = extractWsfeObservations(
1156
+ detail,
1157
+ detailResult === "R" ? "business" : "observation"
1158
+ );
1159
+ const hasInfrastructureError = errors.some(
1160
+ (issue) => issue.category === "infrastructure"
1161
+ );
1162
+ const base = {
1163
+ service: "wsfe",
1164
+ operation,
1165
+ results: createWsfeResults(headerResult, detailResult),
1166
+ errors,
1167
+ observations,
1168
+ raw: result
1169
+ };
1170
+ const context = {
1171
+ base,
1172
+ headerResult,
1173
+ detailResult,
1174
+ resultCode,
1175
+ resultLevel,
1176
+ cae,
1177
+ caeExpiry
1178
+ };
1179
+ if (hasContradictoryWsfeResults(context)) {
1180
+ return createWsfeStructuredIndeterminate(context, "contradictory_response");
1181
+ }
1182
+ if (hasInfrastructureError) {
1183
+ return createWsfeStructuredIndeterminate(context, "incomplete_response");
1184
+ }
1185
+ if (isAuthorizedWsfeContext(context)) {
1186
+ return {
1187
+ ...base,
1188
+ kind: "authorized",
1189
+ result: "A",
1190
+ resultLevel: "detail",
1191
+ cae: context.cae,
1192
+ caeExpiry: context.caeExpiry,
1193
+ voucherNumber
1194
+ };
1195
+ }
1196
+ if (isRejectedWsfeDetailContext(context)) {
1197
+ return {
1198
+ ...base,
1199
+ kind: "rejected",
1200
+ result: "R",
1201
+ resultLevel: "detail"
1202
+ };
1203
+ }
1204
+ if (isRejectedWsfeHeaderContext(context)) {
1205
+ return {
1206
+ ...base,
1207
+ kind: "rejected",
1208
+ result: "R",
1209
+ resultLevel: "header"
1210
+ };
1211
+ }
1212
+ return createWsfeStructuredIndeterminate(
1213
+ context,
1214
+ hasWsfeCaeContradiction(context) ? "contradictory_response" : "incomplete_response"
1215
+ );
1216
+ }
1217
+ function getWsfeResultLevel(headerResult, detailResult) {
1218
+ if (detailResult) {
1219
+ return "detail";
1220
+ }
1221
+ return headerResult ? "header" : void 0;
1222
+ }
1223
+ function hasContradictoryWsfeResults(context) {
1224
+ return Boolean(
1225
+ context.headerResult && context.detailResult && context.headerResult !== context.detailResult
1226
+ );
1227
+ }
1228
+ function isAuthorizedWsfeContext(context) {
1229
+ return Boolean(
1230
+ context.detailResult === "A" && context.headerResult !== "R" && context.base.errors.length === 0 && context.cae && context.caeExpiry
1231
+ );
1232
+ }
1233
+ function isRejectedWsfeDetailContext(context) {
1234
+ return context.detailResult === "R" && context.headerResult !== "A" && !context.cae;
1235
+ }
1236
+ function isRejectedWsfeHeaderContext(context) {
1237
+ return context.headerResult === "R" && context.detailResult === void 0 && !context.cae && context.base.errors.length > 0 && context.base.errors.every((issue) => issue.category === "business");
1238
+ }
1239
+ function hasWsfeCaeContradiction(context) {
1240
+ return (context.resultCode === "A" || context.resultCode === "R") && Boolean(context.cae);
1241
+ }
1242
+ function createWsfeStructuredIndeterminate(context, reason) {
1243
+ const outcome = {
1244
+ ...context.base,
1245
+ kind: "indeterminate",
1246
+ reason
1247
+ };
1248
+ assignWsfeValue(outcome, "result", context.resultCode);
1249
+ assignWsfeValue(outcome, "resultLevel", context.resultLevel);
1250
+ assignWsfeValue(outcome, "cae", context.cae);
1251
+ assignWsfeValue(outcome, "caeExpiry", context.caeExpiry);
1252
+ return outcome;
1253
+ }
1254
+ function createWsfeResults(headerResult, detailResult) {
1255
+ const results = {};
1256
+ assignWsfeValue(results, "header", headerResult);
1257
+ assignWsfeValue(results, "detail", detailResult);
1258
+ return results;
1259
+ }
1260
+ function createWsfeIndeterminateOutcome(error) {
1261
+ return {
1262
+ kind: "indeterminate",
1263
+ service: "wsfe",
1264
+ operation: "FECAESolicitar",
1265
+ results: {},
1266
+ reason: getArcaIndeterminateReason(error),
1267
+ errors: [],
1268
+ observations: []
1269
+ };
1270
+ }
1271
+ function getArcaIndeterminateReason(error) {
1272
+ if (error instanceof ArcaTransportError) {
1273
+ return "transport_error";
1274
+ }
1275
+ if (error instanceof ArcaSoapFaultError) {
1276
+ return "soap_fault";
1277
+ }
1278
+ if (error instanceof ArcaInvalidSoapResponseError) {
1279
+ return "invalid_response";
1280
+ }
1281
+ return "unexpected_error";
1282
+ }
1283
+ function createWsfeOutcomeError(outcome) {
1284
+ const issues = [...outcome.errors, ...outcome.observations];
1285
+ const firstIssue = issues[0];
1286
+ 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";
1287
+ return new ArcaServiceError(message, {
1288
+ service: "wsfe",
1289
+ operation: outcome.operation,
1290
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1291
+ ...outcome.result === void 0 ? {} : { result: outcome.result },
1292
+ ...outcome.resultLevel === void 0 ? {} : { resultLevel: outcome.resultLevel },
1293
+ results: outcome.results,
1294
+ ...outcome.kind === "indeterminate" && outcome.cae ? { cae: outcome.cae } : {},
1295
+ issues,
1296
+ detail: outcome.raw
1297
+ });
1298
+ }
1299
+ function createWsfeServiceError(operation, result, issues) {
1300
+ const firstIssue = issues[0];
1301
+ return new ArcaServiceError(
1302
+ firstIssue ? formatWsfeIssue(firstIssue) : "WSFE returned a service error",
1303
+ {
1304
+ service: "wsfe",
1305
+ operation,
1306
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1307
+ issues,
1308
+ detail: result
1309
+ }
1310
+ );
1311
+ }
1312
+ function extractWsfeGlobalIssues(result, operation, resultLevel) {
1313
+ const errorsContainer = toWsfeRecord(result.Errors);
1314
+ return normalizeWsfeIssueEntries(errorsContainer?.Err).map((entry) => ({
1315
+ service: "wsfe",
1316
+ operation,
1317
+ source: "error",
1318
+ category: operation === "FECAESolicitar" && WSFE_AUTHORIZATION_INFRASTRUCTURE_CODES.has(entry.code ?? "") ? "infrastructure" : operation === "FECAESolicitar" ? "business" : "unknown",
1319
+ ...entry.code === void 0 ? {} : { code: entry.code },
1320
+ message: entry.message,
1321
+ ...resultLevel === void 0 ? {} : { resultLevel }
1322
+ }));
1323
+ }
1324
+ function extractWsfeObservations(detail, category) {
1325
+ const observationsContainer = toWsfeRecord(detail.Observaciones);
1326
+ return normalizeWsfeIssueEntries(observationsContainer?.Obs).map((entry) => ({
1327
+ service: "wsfe",
1328
+ operation: "FECAESolicitar",
1329
+ source: "observation",
1330
+ category,
1331
+ ...entry.code === void 0 ? {} : { code: entry.code },
1332
+ message: entry.message,
1333
+ resultLevel: "detail"
1334
+ }));
1335
+ }
1336
+ function normalizeWsfeIssueEntries(rawErrors) {
1024
1337
  const entries = Array.isArray(rawErrors) ? rawErrors : rawErrors ? [rawErrors] : [];
1025
1338
  return entries.map((entry) => entry).map((entry) => {
1026
- const code = entry.Code ?? entry.code ?? "N/A";
1339
+ const code = entry.Code ?? entry.code;
1027
1340
  const message = entry.Msg ?? entry.msg ?? "Unknown WSFE error";
1028
1341
  return {
1029
- code: String(code),
1030
- message: `(${String(code)}) ${String(message)}`
1342
+ ...code === void 0 ? {} : { code: String(code) },
1343
+ message: String(message)
1031
1344
  };
1032
1345
  });
1033
1346
  }
1347
+ function formatWsfeIssue(issue) {
1348
+ return issue.code ? `(${issue.code}) ${issue.message}` : issue.message;
1349
+ }
1350
+ function normalizeWsfeResult(value) {
1351
+ if (typeof value !== "string") {
1352
+ return void 0;
1353
+ }
1354
+ const normalized = value.trim().toUpperCase();
1355
+ return normalized || void 0;
1356
+ }
1357
+ function normalizeWsfeString(value) {
1358
+ if (value === void 0 || value === null) {
1359
+ return void 0;
1360
+ }
1361
+ const normalized = String(value).trim();
1362
+ return normalized || void 0;
1363
+ }
1364
+ function normalizeWsfeNumber(value) {
1365
+ if (value === void 0 || value === null || value === "") {
1366
+ return void 0;
1367
+ }
1368
+ const normalized = Number(value);
1369
+ return Number.isFinite(normalized) ? normalized : void 0;
1370
+ }
1371
+ function assignWsfeValue(target, key, value) {
1372
+ if (value !== void 0) {
1373
+ target[key] = value;
1374
+ }
1375
+ }
1376
+ function toWsfeRecord(value) {
1377
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1378
+ }
1379
+ var WSFE_AUTHORIZATION_INFRASTRUCTURE_CODES = /* @__PURE__ */ new Set([
1380
+ "500",
1381
+ "501",
1382
+ "502",
1383
+ "600",
1384
+ "601"
1385
+ ]);
1034
1386
  function getWsfeResultEntries(result, key) {
1035
1387
  const rawEntries = result.ResultGet?.[key];
1036
1388
  if (!rawEntries) {
@@ -1043,145 +1395,227 @@ function getWsfeResultEntries(result, key) {
1043
1395
 
1044
1396
  // src/services/wsmtxca.ts
1045
1397
  function createWsmtxcaService(options) {
1046
- return {
1047
- async authorizeVoucher({ representedTaxId, data, forceRefresh }) {
1048
- const auth = await options.auth.login("wsmtxca", {
1049
- representedTaxId,
1050
- forceRefresh
1051
- });
1052
- const response = await options.soap.execute({
1053
- service: "wsmtxca",
1054
- operation: "autorizarComprobante",
1055
- bodyElementName: "autorizarComprobanteRequest",
1056
- bodyElementNamespaceMode: "prefix",
1057
- body: {
1058
- authRequest: createWsmtxcaAuth(
1059
- representedTaxId ?? options.config.taxId,
1060
- auth.token,
1061
- auth.sign
1062
- ),
1063
- ...data
1064
- }
1065
- });
1066
- const raw = unwrapWsmtxcaOperationResponse(
1067
- response.result,
1068
- "autorizarComprobante"
1069
- );
1070
- const authorizationPayload = extractWsmtxcaAuthorizationPayload(raw);
1071
- const messages = extractWsmtxcaMessages(raw);
1072
- const resultado = raw.resultado ?? authorizationPayload.resultado;
1073
- const caeValue = authorizationPayload.CAE ?? authorizationPayload.codigoAutorizacion ?? raw.codigoAutorizacion;
1074
- if (resultado === "R" || caeValue == null) {
1075
- throw new ArcaServiceError(
1076
- messages.join(" | ") || "WSMTXCA rejected the voucher authorization",
1077
- { detail: raw }
1078
- );
1398
+ async function executeWsmtxcaAuthenticatedOperation(operation, input, body = {}, retries) {
1399
+ const auth = await options.auth.login("wsmtxca", {
1400
+ representedTaxId: input.representedTaxId,
1401
+ forceRefresh: input.forceRefresh
1402
+ });
1403
+ const response = await options.soap.execute({
1404
+ service: "wsmtxca",
1405
+ operation,
1406
+ ...retries === void 0 ? {} : { retries },
1407
+ bodyElementName: `${operation}Request`,
1408
+ bodyElementNamespaceMode: "prefix",
1409
+ body: {
1410
+ ...body,
1411
+ authRequest: createWsmtxcaAuth(
1412
+ input.representedTaxId ?? options.config.taxId,
1413
+ auth.token,
1414
+ auth.sign
1415
+ )
1079
1416
  }
1417
+ });
1418
+ return unwrapWsmtxcaOperationResponse(response.result, operation);
1419
+ }
1420
+ async function executeWsmtxcaAuthorization({
1421
+ representedTaxId,
1422
+ data,
1423
+ forceRefresh
1424
+ }) {
1425
+ if (Object.hasOwn(data, "authRequest")) {
1426
+ throw new ArcaInputError(
1427
+ 'WSMTXCA authorization data cannot include the reserved top-level field "authRequest".'
1428
+ );
1429
+ }
1430
+ try {
1431
+ const raw = await executeWsmtxcaAuthenticatedOperation(
1432
+ "autorizarComprobante",
1433
+ { representedTaxId, forceRefresh },
1434
+ data,
1435
+ 0
1436
+ );
1437
+ return { outcome: classifyWsmtxcaAuthorization(raw) };
1438
+ } catch (error) {
1080
1439
  return {
1081
- cae: String(caeValue),
1082
- caeExpiry: normalizeWsmtxcaResponseDate(
1083
- authorizationPayload.fechaVencimientoCAE ?? authorizationPayload.fechaVencimiento ?? raw.fechaVencimiento
1084
- ),
1085
- voucherNumber: parseWsmtxcaVoucherNumber(
1086
- authorizationPayload.numeroComprobante ?? raw.numeroComprobante,
1087
- "WSMTXCA did not return the authorized voucher number",
1088
- raw
1089
- ),
1090
- messages,
1091
- raw
1440
+ outcome: createWsmtxcaIndeterminateOutcome(error),
1441
+ error
1092
1442
  };
1093
- },
1094
- async getLastAuthorizedVoucher({
1095
- representedTaxId,
1096
- voucherType,
1097
- salesPoint,
1098
- forceRefresh
1099
- }) {
1100
- const auth = await options.auth.login("wsmtxca", {
1101
- representedTaxId,
1102
- forceRefresh
1103
- });
1104
- const response = await options.soap.execute({
1105
- service: "wsmtxca",
1106
- operation: "consultarUltimoComprobanteAutorizado",
1107
- bodyElementName: "consultarUltimoComprobanteAutorizadoRequest",
1108
- bodyElementNamespaceMode: "prefix",
1109
- body: {
1110
- authRequest: createWsmtxcaAuth(
1111
- representedTaxId ?? options.config.taxId,
1112
- auth.token,
1113
- auth.sign
1114
- ),
1115
- consultaUltimoComprobanteAutorizadoRequest: {
1116
- codigoTipoComprobante: voucherType,
1117
- numeroPuntoVenta: salesPoint
1118
- }
1443
+ }
1444
+ }
1445
+ async function authorizeVoucherOutcome(input) {
1446
+ return (await executeWsmtxcaAuthorization(input)).outcome;
1447
+ }
1448
+ async function authorizeVoucher(input) {
1449
+ const execution = await executeWsmtxcaAuthorization(input);
1450
+ if (execution.error) {
1451
+ throw execution.error;
1452
+ }
1453
+ if (execution.outcome.kind !== "authorized") {
1454
+ throw createWsmtxcaOutcomeError(execution.outcome);
1455
+ }
1456
+ const { outcome } = execution;
1457
+ return {
1458
+ cae: outcome.cae,
1459
+ ...outcome.caeExpiry === void 0 ? {} : { caeExpiry: outcome.caeExpiry },
1460
+ voucherNumber: outcome.voucherNumber,
1461
+ messages: formatWsmtxcaIssues([
1462
+ ...outcome.errors,
1463
+ ...outcome.observations
1464
+ ]),
1465
+ raw: outcome.raw ?? {}
1466
+ };
1467
+ }
1468
+ async function getLastAuthorizedVoucher({
1469
+ representedTaxId,
1470
+ voucherType,
1471
+ salesPoint,
1472
+ forceRefresh
1473
+ }) {
1474
+ const operation = "consultarUltimoComprobanteAutorizado";
1475
+ const raw = await executeWsmtxcaAuthenticatedOperation(
1476
+ operation,
1477
+ { representedTaxId, forceRefresh },
1478
+ {
1479
+ consultaUltimoComprobanteAutorizadoRequest: {
1480
+ codigoTipoComprobante: voucherType,
1481
+ numeroPuntoVenta: salesPoint
1119
1482
  }
1120
- });
1121
- const raw = unwrapWsmtxcaOperationResponse(
1122
- response.result,
1123
- "consultarUltimoComprobanteAutorizado"
1124
- );
1483
+ }
1484
+ );
1485
+ const errors = extractWsmtxcaIssues(raw, operation, "error");
1486
+ if (errors.length > 0 && errors.every((issue) => issue.code === "1502")) {
1487
+ return { voucherNumber: 0, raw };
1488
+ }
1489
+ if (errors.length > 0) {
1490
+ throw createWsmtxcaServiceError(operation, raw, errors);
1491
+ }
1492
+ return {
1493
+ voucherNumber: parseWsmtxcaVoucherNumber(
1494
+ raw.numeroComprobante ?? raw.cbteNro ?? raw.nroComprobante,
1495
+ "WSMTXCA did not return the last authorized voucher number",
1496
+ raw,
1497
+ true
1498
+ ),
1499
+ raw
1500
+ };
1501
+ }
1502
+ async function getSalesPoints({
1503
+ representedTaxId,
1504
+ forceRefresh
1505
+ }) {
1506
+ const raw = await executeWsmtxcaAuthenticatedOperation(
1507
+ "consultarPuntosVenta",
1508
+ { representedTaxId, forceRefresh }
1509
+ );
1510
+ const rawSalesPoints = toRecord(raw.arrayPuntosVenta)?.puntoVenta;
1511
+ const entries = Array.isArray(rawSalesPoints) ? rawSalesPoints : rawSalesPoints ? [rawSalesPoints] : [];
1512
+ const salesPoints = entries.flatMap((entry) => {
1513
+ const record = toRecord(entry);
1514
+ const number = parseOptionalPositiveInteger(record?.numeroPuntoVenta);
1515
+ if (number === void 0) {
1516
+ return [];
1517
+ }
1518
+ const deletedAt = normalizeWsmtxcaResponseDate(record?.fechaBaja);
1519
+ return [
1520
+ {
1521
+ number,
1522
+ blocked: String(record?.bloqueado ?? "N").toUpperCase() === "S",
1523
+ ...deletedAt === void 0 ? {} : { deletedAt }
1524
+ }
1525
+ ];
1526
+ });
1527
+ return { salesPoints, raw };
1528
+ }
1529
+ async function lookupVoucher({
1530
+ representedTaxId,
1531
+ voucherType,
1532
+ salesPoint,
1533
+ voucherNumber,
1534
+ forceRefresh
1535
+ }) {
1536
+ const operation = "consultarComprobante";
1537
+ const raw = await executeWsmtxcaAuthenticatedOperation(
1538
+ operation,
1539
+ { representedTaxId, forceRefresh },
1540
+ {
1541
+ consultaComprobanteRequest: {
1542
+ codigoTipoComprobante: voucherType,
1543
+ numeroPuntoVenta: salesPoint,
1544
+ numeroComprobante: voucherNumber
1545
+ }
1546
+ }
1547
+ );
1548
+ const errors = extractWsmtxcaIssues(raw, operation, "error");
1549
+ const observations = extractWsmtxcaIssues(raw, operation, "observation");
1550
+ if (errors.length > 0 && errors.every((issue) => issue.code === "1503")) {
1125
1551
  return {
1126
- voucherNumber: parseWsmtxcaVoucherNumber(
1127
- raw.numeroComprobante ?? raw.cbteNro ?? raw.nroComprobante,
1128
- extractWsmtxcaMessages(raw).join(" | ") || "WSMTXCA did not return the last authorized voucher number",
1129
- raw
1130
- ),
1552
+ kind: "not_found",
1553
+ service: "wsmtxca",
1554
+ operation,
1555
+ errors,
1556
+ observations,
1131
1557
  raw
1132
1558
  };
1133
- },
1134
- async getVoucher({
1135
- representedTaxId,
1136
- voucherType,
1137
- salesPoint,
1138
- voucherNumber,
1139
- forceRefresh
1140
- }) {
1141
- const auth = await options.auth.login("wsmtxca", {
1142
- representedTaxId,
1143
- forceRefresh
1144
- });
1145
- const response = await options.soap.execute({
1146
- service: "wsmtxca",
1147
- operation: "consultarComprobante",
1148
- bodyElementName: "consultarComprobanteRequest",
1149
- bodyElementNamespaceMode: "prefix",
1150
- body: {
1151
- authRequest: createWsmtxcaAuth(
1152
- representedTaxId ?? options.config.taxId,
1153
- auth.token,
1154
- auth.sign
1155
- ),
1156
- consultaComprobanteRequest: {
1157
- codigoTipoComprobante: voucherType,
1158
- numeroPuntoVenta: salesPoint,
1159
- numeroComprobante: voucherNumber
1160
- }
1559
+ }
1560
+ if (errors.length > 0) {
1561
+ throw createWsmtxcaServiceError(operation, raw, errors);
1562
+ }
1563
+ const voucher = extractWsmtxcaVoucherPayload(raw);
1564
+ if (voucher === raw && !toRecord(raw.comprobante)) {
1565
+ throw new ArcaServiceError(
1566
+ "WSMTXCA did not return the voucher issue date",
1567
+ {
1568
+ service: "wsmtxca",
1569
+ operation,
1570
+ issues: observations,
1571
+ detail: raw
1161
1572
  }
1162
- });
1163
- const raw = unwrapWsmtxcaOperationResponse(
1164
- response.result,
1165
- "consultarComprobante"
1166
1573
  );
1167
- const voucher = extractWsmtxcaVoucherPayload(raw);
1168
- const messages = extractWsmtxcaMessages(raw);
1169
- const invoiceDate = normalizeWsmtxcaResponseDate(
1170
- voucher.fechaEmision ?? voucher.fecha ?? voucher.CbteFch
1574
+ }
1575
+ return {
1576
+ kind: "found",
1577
+ service: "wsmtxca",
1578
+ operation,
1579
+ voucher: mapWsmtxcaVoucherInfo(voucher),
1580
+ observations,
1581
+ raw
1582
+ };
1583
+ }
1584
+ async function getVoucher(input) {
1585
+ const lookup = await lookupVoucher(input);
1586
+ if (lookup.kind === "not_found") {
1587
+ throw createWsmtxcaServiceError(
1588
+ lookup.operation,
1589
+ lookup.raw,
1590
+ lookup.errors
1591
+ );
1592
+ }
1593
+ const invoiceDate = lookup.voucher.invoiceDate;
1594
+ if (!invoiceDate) {
1595
+ throw new ArcaServiceError(
1596
+ formatWsmtxcaIssues(lookup.observations)[0] ?? "WSMTXCA did not return the voucher issue date",
1597
+ {
1598
+ service: "wsmtxca",
1599
+ operation: lookup.operation,
1600
+ issues: lookup.observations,
1601
+ detail: lookup.raw
1602
+ }
1171
1603
  );
1172
- if (!invoiceDate) {
1173
- throw new ArcaServiceError(
1174
- messages[0] ?? "WSMTXCA did not return the voucher issue date",
1175
- { detail: raw }
1176
- );
1177
- }
1178
- return {
1179
- invoiceDate,
1180
- voucher,
1181
- messages,
1182
- raw
1183
- };
1184
1604
  }
1605
+ return {
1606
+ invoiceDate,
1607
+ voucher: lookup.voucher.raw,
1608
+ messages: formatWsmtxcaIssues(lookup.observations),
1609
+ raw: lookup.raw
1610
+ };
1611
+ }
1612
+ return {
1613
+ authorizeVoucherOutcome,
1614
+ authorizeVoucher,
1615
+ getLastAuthorizedVoucher,
1616
+ getSalesPoints,
1617
+ lookupVoucher,
1618
+ getVoucher
1185
1619
  };
1186
1620
  }
1187
1621
  function createWsmtxcaAuth(representedTaxId, token, sign) {
@@ -1192,7 +1626,7 @@ function createWsmtxcaAuth(representedTaxId, token, sign) {
1192
1626
  };
1193
1627
  }
1194
1628
  function toRecord(value) {
1195
- return value && typeof value === "object" ? value : void 0;
1629
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1196
1630
  }
1197
1631
  function unwrapWsmtxcaOperationResponse(response, operation) {
1198
1632
  const responseRecord = toRecord(response) ?? {};
@@ -1202,6 +1636,9 @@ function unwrapWsmtxcaOperationResponse(response, operation) {
1202
1636
  if (operation === "consultarComprobante") {
1203
1637
  return toRecord(responseRecord.consultarComprobanteResponse) ?? toRecord(responseRecord.consultaComprobanteResponse) ?? toRecord(responseRecord.consultarComprobanteResult) ?? responseRecord;
1204
1638
  }
1639
+ if (operation === "consultarPuntosVenta") {
1640
+ return toRecord(responseRecord.consultarPuntosVentaResponse) ?? toRecord(responseRecord.consultarPuntosVentaResult) ?? responseRecord;
1641
+ }
1205
1642
  return toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultaUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResult) ?? responseRecord;
1206
1643
  }
1207
1644
  function extractWsmtxcaAuthorizationPayload(raw) {
@@ -1210,42 +1647,291 @@ function extractWsmtxcaAuthorizationPayload(raw) {
1210
1647
  function extractWsmtxcaVoucherPayload(raw) {
1211
1648
  return toRecord(raw.comprobanteResponse) ?? toRecord(raw.comprobante) ?? toRecord(raw.cmp) ?? raw;
1212
1649
  }
1213
- function extractWsmtxcaMessages(raw) {
1214
- const rawErrors = raw.arrayErrores;
1215
- const rawObservations = raw.arrayObservaciones;
1216
- const toEntries = (value) => {
1217
- if (!value) {
1218
- return [];
1219
- }
1220
- if (Array.isArray(value)) {
1221
- return value;
1650
+ function classifyWsmtxcaAuthorization(raw) {
1651
+ const operation = "autorizarComprobante";
1652
+ const payload = extractWsmtxcaAuthorizationPayload(raw);
1653
+ const result = normalizeWsmtxcaResult(raw.resultado ?? payload.resultado);
1654
+ const cae = normalizeWsmtxcaString(
1655
+ payload.CAE ?? payload.codigoAutorizacion ?? raw.codigoAutorizacion
1656
+ );
1657
+ const caeExpiry = normalizeWsmtxcaResponseDate(
1658
+ payload.fechaVencimientoCAE ?? payload.fechaVencimiento ?? raw.fechaVencimiento
1659
+ );
1660
+ const voucherNumber = parseOptionalPositiveInteger(
1661
+ payload.numeroComprobante ?? raw.numeroComprobante
1662
+ );
1663
+ const errors = extractWsmtxcaIssues(raw, operation, "error");
1664
+ const observations = extractWsmtxcaIssues(raw, operation, "observation");
1665
+ const base = {
1666
+ service: "wsmtxca",
1667
+ operation,
1668
+ results: createWsmtxcaResults(result),
1669
+ errors,
1670
+ observations,
1671
+ raw
1672
+ };
1673
+ if ((result === "A" || result === "O") && cae && voucherNumber !== void 0 && errors.length === 0) {
1674
+ return {
1675
+ ...base,
1676
+ kind: "authorized",
1677
+ result,
1678
+ resultLevel: "operation",
1679
+ cae,
1680
+ ...caeExpiry === void 0 ? {} : { caeExpiry },
1681
+ voucherNumber
1682
+ };
1683
+ }
1684
+ if (result === "R" && !cae && errors.length > 0) {
1685
+ return {
1686
+ ...base,
1687
+ kind: "rejected",
1688
+ result: "R",
1689
+ resultLevel: "operation"
1690
+ };
1691
+ }
1692
+ const outcome = {
1693
+ ...base,
1694
+ kind: "indeterminate",
1695
+ reason: result === "R" && Boolean(cae) || (result === "A" || result === "O") && Boolean(errors.length) ? "contradictory_response" : "incomplete_response",
1696
+ ...result === void 0 ? {} : { result },
1697
+ ...result === void 0 ? {} : { resultLevel: "operation" }
1698
+ };
1699
+ assignWsmtxcaValue(outcome, "cae", cae);
1700
+ assignWsmtxcaValue(outcome, "caeExpiry", caeExpiry);
1701
+ assignWsmtxcaValue(outcome, "voucherNumber", voucherNumber);
1702
+ return outcome;
1703
+ }
1704
+ function createWsmtxcaIndeterminateOutcome(error) {
1705
+ return {
1706
+ kind: "indeterminate",
1707
+ service: "wsmtxca",
1708
+ operation: "autorizarComprobante",
1709
+ results: {},
1710
+ reason: getWsmtxcaIndeterminateReason(error),
1711
+ errors: [],
1712
+ observations: []
1713
+ };
1714
+ }
1715
+ function getWsmtxcaIndeterminateReason(error) {
1716
+ if (error instanceof ArcaTransportError) {
1717
+ return "transport_error";
1718
+ }
1719
+ if (error instanceof ArcaSoapFaultError) {
1720
+ return "soap_fault";
1721
+ }
1722
+ if (error instanceof ArcaInvalidSoapResponseError) {
1723
+ return "invalid_response";
1724
+ }
1725
+ return "unexpected_error";
1726
+ }
1727
+ function createWsmtxcaOutcomeError(outcome) {
1728
+ const issues = [...outcome.errors, ...outcome.observations];
1729
+ const messages = formatWsmtxcaIssues(issues);
1730
+ const firstIssue = issues[0];
1731
+ return new ArcaServiceError(
1732
+ messages.join(" | ") || (outcome.kind === "rejected" ? "WSMTXCA rejected the voucher authorization" : "WSMTXCA did not return conclusive voucher authorization data"),
1733
+ {
1734
+ service: "wsmtxca",
1735
+ operation: outcome.operation,
1736
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1737
+ ...outcome.result === void 0 ? {} : { result: outcome.result },
1738
+ ...outcome.resultLevel === void 0 ? {} : { resultLevel: outcome.resultLevel },
1739
+ results: outcome.results,
1740
+ ...outcome.kind === "indeterminate" && outcome.cae ? { cae: outcome.cae } : {},
1741
+ issues,
1742
+ detail: outcome.raw
1222
1743
  }
1223
- if (typeof value === "object") {
1224
- return [value];
1744
+ );
1745
+ }
1746
+ function createWsmtxcaResults(operationResult) {
1747
+ const results = {};
1748
+ assignWsmtxcaValue(results, "operation", operationResult);
1749
+ return results;
1750
+ }
1751
+ function createWsmtxcaServiceError(operation, raw, issues) {
1752
+ const firstIssue = issues[0];
1753
+ return new ArcaServiceError(
1754
+ formatWsmtxcaIssues(issues).join(" | ") || "WSMTXCA returned a service error",
1755
+ {
1756
+ service: "wsmtxca",
1757
+ operation,
1758
+ ...firstIssue?.code === void 0 ? {} : { serviceCode: firstIssue.code },
1759
+ issues,
1760
+ detail: raw
1225
1761
  }
1226
- return [];
1227
- };
1228
- const errors = toEntries(rawErrors?.codigoDescripcion).map((entry) => {
1229
- const code = entry.codigo == null ? "N/A" : String(entry.codigo);
1230
- const description = entry.descripcion == null ? "Unknown WSMTXCA error" : String(entry.descripcion);
1231
- return `Error ${code}: ${description}`;
1762
+ );
1763
+ }
1764
+ function extractWsmtxcaIssues(raw, operation, source) {
1765
+ const container = toRecord(
1766
+ source === "error" ? raw.arrayErrores : raw.arrayObservaciones
1767
+ );
1768
+ return normalizeWsmtxcaIssueEntries(container?.codigoDescripcion).map(
1769
+ (entry) => ({
1770
+ service: "wsmtxca",
1771
+ operation,
1772
+ source,
1773
+ category: source === "observation" ? "observation" : operation === "autorizarComprobante" ? "business" : "unknown",
1774
+ ...entry.code === void 0 ? {} : { code: entry.code },
1775
+ message: entry.message,
1776
+ ...operation === "autorizarComprobante" ? { resultLevel: "operation" } : {}
1777
+ })
1778
+ );
1779
+ }
1780
+ function normalizeWsmtxcaIssueEntries(value) {
1781
+ const entries = Array.isArray(value) ? value : value ? [value] : [];
1782
+ return entries.map((entry) => {
1783
+ const record = toRecord(entry) ?? {};
1784
+ const code = record.codigo;
1785
+ const description = record.descripcion;
1786
+ return {
1787
+ ...code === void 0 || code === null ? {} : { code: String(code) },
1788
+ message: description === void 0 || description === null ? "Unknown WSMTXCA issue" : String(description)
1789
+ };
1232
1790
  });
1233
- const observations = toEntries(rawObservations?.codigoDescripcion).map(
1234
- (entry) => {
1235
- const code = entry.codigo == null ? "N/A" : String(entry.codigo);
1236
- const description = entry.descripcion == null ? "" : String(entry.descripcion);
1237
- return `Obs ${code}: ${description}`.trim();
1238
- }
1791
+ }
1792
+ function formatWsmtxcaIssues(issues) {
1793
+ return issues.map((issue) => {
1794
+ const prefix = issue.source === "error" ? "Error" : "Obs";
1795
+ return `${prefix}${issue.code ? ` ${issue.code}` : ""}: ${issue.message}`;
1796
+ });
1797
+ }
1798
+ function mapWsmtxcaVoucherInfo(raw) {
1799
+ const voucher = { raw };
1800
+ const invoiceDate = normalizeWsmtxcaResponseDate(
1801
+ raw.fechaEmision ?? raw.fecha ?? raw.CbteFch
1802
+ );
1803
+ const cae = normalizeWsmtxcaString(raw.codigoAutorizacion ?? raw.CAE);
1804
+ const caeExpiry = normalizeWsmtxcaResponseDate(
1805
+ raw.fechaVencimiento ?? raw.fechaVencimientoCAE
1806
+ );
1807
+ const vatAmount = sumWsmtxcaVatAmounts(raw.arraySubtotalesIVA);
1808
+ assignWsmtxcaValue(
1809
+ voucher,
1810
+ "voucherNumber",
1811
+ parseOptionalPositiveInteger(raw.numeroComprobante)
1812
+ );
1813
+ assignWsmtxcaValue(voucher, "invoiceDate", invoiceDate);
1814
+ assignWsmtxcaValue(
1815
+ voucher,
1816
+ "salesPoint",
1817
+ parseOptionalPositiveInteger(raw.numeroPuntoVenta)
1818
+ );
1819
+ assignWsmtxcaValue(
1820
+ voucher,
1821
+ "voucherType",
1822
+ parseOptionalPositiveInteger(raw.codigoTipoComprobante)
1823
+ );
1824
+ assignWsmtxcaValue(
1825
+ voucher,
1826
+ "concept",
1827
+ parseOptionalNumber(raw.codigoConcepto)
1828
+ );
1829
+ assignWsmtxcaValue(
1830
+ voucher,
1831
+ "documentType",
1832
+ parseOptionalNumber(raw.codigoTipoDocumento)
1833
+ );
1834
+ assignWsmtxcaValue(
1835
+ voucher,
1836
+ "documentNumber",
1837
+ normalizeWsmtxcaString(raw.numeroDocumento)
1838
+ );
1839
+ assignWsmtxcaValue(
1840
+ voucher,
1841
+ "receiverVatConditionId",
1842
+ parseOptionalNumber(raw.condicionIVAReceptor)
1843
+ );
1844
+ assignWsmtxcaValue(
1845
+ voucher,
1846
+ "totalAmount",
1847
+ parseOptionalNumber(raw.importeTotal)
1848
+ );
1849
+ assignWsmtxcaValue(
1850
+ voucher,
1851
+ "subtotalAmount",
1852
+ parseOptionalNumber(raw.importeSubtotal)
1853
+ );
1854
+ assignWsmtxcaValue(
1855
+ voucher,
1856
+ "taxableAmount",
1857
+ parseOptionalNumber(raw.importeGravado)
1858
+ );
1859
+ assignWsmtxcaValue(
1860
+ voucher,
1861
+ "nonTaxableAmount",
1862
+ parseOptionalNumber(raw.importeNoGravado)
1239
1863
  );
1240
- return [...errors, ...observations];
1864
+ assignWsmtxcaValue(
1865
+ voucher,
1866
+ "exemptAmount",
1867
+ parseOptionalNumber(raw.importeExento)
1868
+ );
1869
+ assignWsmtxcaValue(
1870
+ voucher,
1871
+ "taxAmount",
1872
+ parseOptionalNumber(raw.importeOtrosTributos)
1873
+ );
1874
+ assignWsmtxcaValue(voucher, "vatAmount", vatAmount);
1875
+ assignWsmtxcaValue(
1876
+ voucher,
1877
+ "currencyId",
1878
+ normalizeWsmtxcaString(raw.codigoMoneda)
1879
+ );
1880
+ assignWsmtxcaValue(
1881
+ voucher,
1882
+ "exchangeRate",
1883
+ parseOptionalNumber(raw.cotizacionMoneda)
1884
+ );
1885
+ assignWsmtxcaValue(voucher, "cae", cae);
1886
+ assignWsmtxcaValue(voucher, "caeExpiry", caeExpiry);
1887
+ return voucher;
1241
1888
  }
1242
- function parseWsmtxcaVoucherNumber(value, message, detail) {
1889
+ function sumWsmtxcaVatAmounts(value) {
1890
+ const subtotals = toRecord(value)?.subtotalIVA;
1891
+ const entries = Array.isArray(subtotals) ? subtotals : subtotals ? [subtotals] : [];
1892
+ const amounts = entries.map((entry) => parseOptionalNumber(toRecord(entry)?.importe)).filter((amount) => amount !== void 0);
1893
+ return amounts.length > 0 ? amounts.reduce((total, amount) => total + amount, 0) : void 0;
1894
+ }
1895
+ function parseWsmtxcaVoucherNumber(value, message, detail, allowZero = false) {
1243
1896
  const parsed = Number.parseInt(String(value ?? ""), 10);
1244
- if (!Number.isFinite(parsed) || parsed <= 0) {
1245
- throw new ArcaServiceError(message, { detail });
1897
+ if (!Number.isFinite(parsed) || (allowZero ? parsed < 0 : parsed <= 0)) {
1898
+ throw new ArcaServiceError(message, {
1899
+ service: "wsmtxca",
1900
+ detail
1901
+ });
1246
1902
  }
1247
1903
  return parsed;
1248
1904
  }
1905
+ function parseOptionalPositiveInteger(value) {
1906
+ const parsed = Number.parseInt(String(value ?? ""), 10);
1907
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1908
+ }
1909
+ function parseOptionalNumber(value) {
1910
+ if (value === void 0 || value === null || value === "") {
1911
+ return void 0;
1912
+ }
1913
+ const parsed = Number(value);
1914
+ return Number.isFinite(parsed) ? parsed : void 0;
1915
+ }
1916
+ function normalizeWsmtxcaResult(value) {
1917
+ if (typeof value !== "string") {
1918
+ return void 0;
1919
+ }
1920
+ const normalized = value.trim().toUpperCase();
1921
+ return normalized || void 0;
1922
+ }
1923
+ function normalizeWsmtxcaString(value) {
1924
+ if (value === void 0 || value === null) {
1925
+ return void 0;
1926
+ }
1927
+ const normalized = String(value).trim();
1928
+ return normalized || void 0;
1929
+ }
1930
+ function assignWsmtxcaValue(target, key, value) {
1931
+ if (value !== void 0) {
1932
+ target[key] = value;
1933
+ }
1934
+ }
1249
1935
  function normalizeWsmtxcaResponseDate(value) {
1250
1936
  if (typeof value === "number" && Number.isInteger(value)) {
1251
1937
  return formatCompactDateToIso(value);
@@ -1638,7 +2324,7 @@ function createSoapTransport(options) {
1638
2324
  soapAction: serviceConfig.soapVersion === "1.1" ? soapAction : void 0,
1639
2325
  useLegacyTlsSecurityLevel0: options.config.environment === "production" && serviceConfig.useLegacyTlsSecurityLevel0 === true,
1640
2326
  timeout: options.config.timeout,
1641
- retries: options.config.retries,
2327
+ retries: request.retries ?? options.config.retries,
1642
2328
  retryDelay: options.config.retryDelay,
1643
2329
  logger: options.logger,
1644
2330
  service: request.service,
@@ -1751,93 +2437,129 @@ function isWsaaCredentialValid(credentials) {
1751
2437
  // src/wsaa/index.ts
1752
2438
  function createWsaaAuthModule(options) {
1753
2439
  const cache = /* @__PURE__ */ new Map();
1754
- const inFlight = /* @__PURE__ */ new Map();
1755
- return {
1756
- async login(service, authOptions = {}) {
1757
- const sessionKey = buildWsaaSessionKey(options.config, service);
1758
- const cacheKey = serializeWsaaSessionKey(sessionKey);
1759
- const running = inFlight.get(cacheKey);
1760
- if (running) {
1761
- return running;
2440
+ const ordinaryInFlight = /* @__PURE__ */ new Map();
2441
+ const forcedInFlight = /* @__PURE__ */ new Map();
2442
+ function trackLogin(target, cacheKey, login) {
2443
+ const promise = login();
2444
+ target.set(cacheKey, promise);
2445
+ const cleanup = () => {
2446
+ if (target.get(cacheKey) === promise) {
2447
+ target.delete(cacheKey);
1762
2448
  }
1763
- const loginPromise = (async () => {
1764
- const reuse = await getReusableCredentials({
1765
- config: options.config,
1766
- cache,
1767
- cacheKey,
1768
- sessionKey,
1769
- logger: options.logger,
1770
- service,
1771
- allowStore: !authOptions.forceRefresh,
1772
- allowCache: !authOptions.forceRefresh
1773
- });
1774
- if (reuse) {
1775
- return reuse;
1776
- }
1777
- const refresh = () => refreshWsaaCredentials({
2449
+ };
2450
+ promise.then(cleanup, cleanup);
2451
+ return promise;
2452
+ }
2453
+ async function requestOrReuseWsaaCredentials(service, sessionKey, cacheKey, forceRefresh) {
2454
+ const reuse = await getReusableCredentials({
2455
+ config: options.config,
2456
+ cache,
2457
+ cacheKey,
2458
+ sessionKey,
2459
+ logger: options.logger,
2460
+ service,
2461
+ allowStore: !forceRefresh,
2462
+ allowCache: !forceRefresh
2463
+ });
2464
+ if (reuse) {
2465
+ return reuse;
2466
+ }
2467
+ const refresh = () => refreshWsaaCredentials({
2468
+ config: options.config,
2469
+ cache,
2470
+ cacheKey,
2471
+ sessionKey,
2472
+ logger: options.logger,
2473
+ service,
2474
+ forceRefresh
2475
+ });
2476
+ if (options.config.wsaaSessionStore?.withLock) {
2477
+ return await withWsaaSessionStoreLock(
2478
+ options.config,
2479
+ sessionKey,
2480
+ service,
2481
+ refresh
2482
+ );
2483
+ }
2484
+ return await refresh();
2485
+ }
2486
+ async function performLogin(service, sessionKey, cacheKey, forceRefresh) {
2487
+ try {
2488
+ return await requestOrReuseWsaaCredentials(
2489
+ service,
2490
+ sessionKey,
2491
+ cacheKey,
2492
+ forceRefresh
2493
+ );
2494
+ } catch (error) {
2495
+ if (error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
2496
+ const recovered = await getReusableCredentials({
1778
2497
  config: options.config,
1779
2498
  cache,
1780
2499
  cacheKey,
1781
2500
  sessionKey,
1782
2501
  logger: options.logger,
1783
2502
  service,
1784
- forceRefresh: authOptions.forceRefresh === true
2503
+ allowStore: true,
2504
+ allowCache: true
1785
2505
  });
1786
- if (options.config.wsaaSessionStore?.withLock) {
1787
- return await withWsaaSessionStoreLock(
1788
- options.config,
1789
- sessionKey,
1790
- service,
1791
- refresh
2506
+ if (recovered) {
2507
+ options.logger?.warn(
2508
+ "Recovered WSAA coe.alreadyAuthenticated fault",
2509
+ {
2510
+ service,
2511
+ faultCode: error.faultCode
2512
+ }
1792
2513
  );
2514
+ return recovered;
1793
2515
  }
1794
- return await refresh();
1795
- })();
1796
- inFlight.set(cacheKey, loginPromise);
1797
- try {
1798
- return await loginPromise;
1799
- } catch (error) {
1800
- if (error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
1801
- const recovered = await getReusableCredentials({
1802
- config: options.config,
1803
- cache,
1804
- cacheKey,
1805
- sessionKey,
1806
- logger: options.logger,
1807
- service,
1808
- allowStore: true,
1809
- allowCache: true
1810
- });
1811
- if (recovered) {
1812
- options.logger?.warn(
1813
- "Recovered WSAA coe.alreadyAuthenticated fault",
1814
- {
1815
- service,
1816
- faultCode: error.faultCode
1817
- }
1818
- );
1819
- return recovered;
1820
- }
1821
- if (!options.config.wsaaSessionStore) {
1822
- throw new ArcaConfigurationError(
1823
- "WSAA login failed because another process likely owns a valid TA. Configure a durable wsaaSessionStore for multi-process or serverless deployments.",
1824
- { cause: error }
1825
- );
1826
- }
2516
+ if (!options.config.wsaaSessionStore) {
2517
+ throw new ArcaConfigurationError(
2518
+ "WSAA login failed because another process likely owns a valid TA. Configure a durable wsaaSessionStore for multi-process or serverless deployments.",
2519
+ { cause: error }
2520
+ );
1827
2521
  }
1828
- if (error instanceof ArcaSoapFaultError) {
1829
- options.logger?.error("WSAA SOAP fault response", {
1830
- service,
1831
- operation: "loginCms",
1832
- url: ARCA_WSAA_CONFIG.endpoint[options.config.environment],
1833
- faultCode: error.faultCode,
1834
- error
1835
- });
2522
+ }
2523
+ if (error instanceof ArcaSoapFaultError) {
2524
+ options.logger?.error("WSAA SOAP fault response", {
2525
+ service,
2526
+ operation: "loginCms",
2527
+ url: ARCA_WSAA_CONFIG.endpoint[options.config.environment],
2528
+ faultCode: error.faultCode,
2529
+ error
2530
+ });
2531
+ }
2532
+ throw error;
2533
+ }
2534
+ }
2535
+ return {
2536
+ login(service, authOptions = {}) {
2537
+ const sessionKey = buildWsaaSessionKey(options.config, service);
2538
+ const cacheKey = serializeWsaaSessionKey(sessionKey);
2539
+ if (authOptions.forceRefresh) {
2540
+ const runningForced2 = forcedInFlight.get(cacheKey);
2541
+ if (runningForced2) {
2542
+ return runningForced2;
1836
2543
  }
1837
- throw error;
1838
- } finally {
1839
- inFlight.delete(cacheKey);
2544
+ const runningOrdinary2 = ordinaryInFlight.get(cacheKey);
2545
+ return trackLogin(forcedInFlight, cacheKey, async () => {
2546
+ await runningOrdinary2?.catch(() => void 0);
2547
+ return await performLogin(service, sessionKey, cacheKey, true);
2548
+ });
1840
2549
  }
2550
+ const runningOrdinary = ordinaryInFlight.get(cacheKey);
2551
+ if (runningOrdinary) {
2552
+ return runningOrdinary;
2553
+ }
2554
+ const runningForced = forcedInFlight.get(cacheKey);
2555
+ if (runningForced) {
2556
+ return runningForced;
2557
+ }
2558
+ return trackLogin(
2559
+ ordinaryInFlight,
2560
+ cacheKey,
2561
+ () => performLogin(service, sessionKey, cacheKey, false)
2562
+ );
1841
2563
  }
1842
2564
  };
1843
2565
  }