facturas 0.4.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.
package/dist/index.js ADDED
@@ -0,0 +1,1757 @@
1
+ // src/errors.ts
2
+ var ArcaError = class extends Error {
3
+ code;
4
+ name = "ArcaError";
5
+ constructor(message, code = "ARCA_ERROR", options) {
6
+ super(message, options);
7
+ this.code = code;
8
+ }
9
+ };
10
+ var ArcaConfigurationError = class extends ArcaError {
11
+ name = "ArcaConfigurationError";
12
+ constructor(message, options) {
13
+ super(message, "ARCA_CONFIGURATION_ERROR", options);
14
+ }
15
+ };
16
+ var ArcaInputError = class extends ArcaError {
17
+ name = "ArcaInputError";
18
+ detail;
19
+ constructor(message, options) {
20
+ super(message, "ARCA_INPUT_ERROR", options);
21
+ this.detail = options?.detail;
22
+ }
23
+ };
24
+ var ArcaTransportError = class extends ArcaError {
25
+ name = "ArcaTransportError";
26
+ statusCode;
27
+ responseBody;
28
+ constructor(message, options) {
29
+ super(message, "ARCA_TRANSPORT_ERROR", options);
30
+ this.statusCode = options?.statusCode;
31
+ this.responseBody = options?.responseBody;
32
+ }
33
+ };
34
+ var ArcaSoapFaultError = class extends ArcaError {
35
+ name = "ArcaSoapFaultError";
36
+ faultCode;
37
+ detail;
38
+ constructor(message, options) {
39
+ super(message, "ARCA_SOAP_FAULT", options);
40
+ this.faultCode = options?.faultCode;
41
+ this.detail = options?.detail;
42
+ }
43
+ };
44
+ var ArcaServiceError = class extends ArcaError {
45
+ name = "ArcaServiceError";
46
+ serviceCode;
47
+ detail;
48
+ constructor(message, options) {
49
+ super(message, "ARCA_SERVICE_ERROR", options);
50
+ this.serviceCode = options?.serviceCode;
51
+ this.detail = options?.detail;
52
+ }
53
+ };
54
+
55
+ // src/config.ts
56
+ var ARCA_ENVIRONMENTS = ["production", "test"];
57
+ var ARCA_ENV_VARIABLES = {
58
+ taxId: "ARCA_TAX_ID",
59
+ certificatePem: "ARCA_CERTIFICATE_PEM",
60
+ privateKeyPem: "ARCA_PRIVATE_KEY_PEM",
61
+ environment: "ARCA_ENVIRONMENT"
62
+ };
63
+ var PRIVATE_KEY_PEM_PREFIXES = [
64
+ "-----BEGIN PRIVATE KEY-----",
65
+ "-----BEGIN RSA PRIVATE KEY-----",
66
+ "-----BEGIN ENCRYPTED PRIVATE KEY-----"
67
+ ];
68
+ var VALID_ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
69
+ var DEFAULT_ARCA_TIMEOUT_MS = 3e4;
70
+ var DEFAULT_ARCA_RETRIES = 0;
71
+ var DEFAULT_ARCA_RETRY_DELAY_MS = 500;
72
+ function resolveArcaEnvironment(production) {
73
+ return production ? "production" : "test";
74
+ }
75
+ function createArcaClientConfigFromEnv(options = {}) {
76
+ const env = options.env ?? process.env;
77
+ const variableNames = {
78
+ ...ARCA_ENV_VARIABLES,
79
+ ...options.variableNames
80
+ };
81
+ const environmentInput = readEnv(env, variableNames.environment);
82
+ const environmentValue = normalizeEnvironmentValue(environmentInput);
83
+ const config = {
84
+ taxId: readEnv(env, variableNames.taxId) ?? "",
85
+ certificatePem: readEnv(env, variableNames.certificatePem) ?? "",
86
+ privateKeyPem: readEnv(env, variableNames.privateKeyPem) ?? "",
87
+ environment: environmentValue ?? environmentInput ?? options.defaultEnvironment ?? "test"
88
+ };
89
+ assertArcaClientConfig(config);
90
+ return normalizeArcaClientConfig(config);
91
+ }
92
+ function assertArcaClientConfig(config) {
93
+ const invalidFields = [];
94
+ const normalized = normalizeArcaClientConfig(config);
95
+ const timeout = normalized.timeout ?? DEFAULT_ARCA_TIMEOUT_MS;
96
+ const retries = normalized.retries ?? DEFAULT_ARCA_RETRIES;
97
+ const retryDelay = normalized.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS;
98
+ if (!/^\d{11}$/.test(normalized.taxId)) {
99
+ invalidFields.push("taxId");
100
+ }
101
+ if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
102
+ invalidFields.push("certificatePem");
103
+ }
104
+ if (!PRIVATE_KEY_PEM_PREFIXES.some(
105
+ (prefix) => normalized.privateKeyPem.startsWith(prefix)
106
+ )) {
107
+ invalidFields.push("privateKeyPem");
108
+ }
109
+ if (!ARCA_ENVIRONMENTS.includes(normalized.environment)) {
110
+ invalidFields.push("environment");
111
+ }
112
+ if (!Number.isFinite(timeout) || timeout <= 0) {
113
+ invalidFields.push("timeout");
114
+ }
115
+ if (!Number.isInteger(retries) || retries < 0) {
116
+ invalidFields.push("retries");
117
+ }
118
+ if (!Number.isFinite(retryDelay) || retryDelay < 0) {
119
+ invalidFields.push("retryDelay");
120
+ }
121
+ const loggerLevel = normalized.logger?.level;
122
+ if (loggerLevel !== void 0 && !VALID_ARCA_LOG_LEVELS.includes(loggerLevel)) {
123
+ invalidFields.push("logger.level");
124
+ }
125
+ if (normalized.logger?.log !== void 0 && typeof normalized.logger.log !== "function") {
126
+ invalidFields.push("logger.log");
127
+ }
128
+ if (invalidFields.length > 0) {
129
+ throw new ArcaConfigurationError(
130
+ `Missing or invalid ARCA client config fields: ${invalidFields.join(", ")}`
131
+ );
132
+ }
133
+ }
134
+ var ARCA_WSAA_CONFIG = {
135
+ namespace: "http://wsaa.view.sua.dvadac.desein.afip.gov",
136
+ endpoint: {
137
+ production: "https://wsaa.afip.gov.ar/ws/services/LoginCms",
138
+ test: "https://wsaahomo.afip.gov.ar/ws/services/LoginCms"
139
+ },
140
+ soapVersion: "1.1",
141
+ soapActionBase: "",
142
+ usesEmptySoapAction: true
143
+ };
144
+ var ARCA_SERVICE_CONFIG = {
145
+ wsaa: ARCA_WSAA_CONFIG,
146
+ wsfe: {
147
+ namespace: "http://ar.gov.afip.dif.FEV1/",
148
+ endpoint: {
149
+ production: "https://servicios1.afip.gov.ar/wsfev1/service.asmx",
150
+ test: "https://wswhomo.afip.gov.ar/wsfev1/service.asmx"
151
+ },
152
+ soapVersion: "1.2",
153
+ soapActionBase: "http://ar.gov.afip.dif.FEV1/",
154
+ useLegacyTlsSecurityLevel0: true
155
+ },
156
+ wsmtxca: {
157
+ namespace: "http://impl.service.wsmtxca.afip.gov.ar/service/",
158
+ endpoint: {
159
+ production: "https://serviciosjava.afip.gov.ar/wsmtxca/services/MTXCAService",
160
+ test: "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService"
161
+ },
162
+ soapVersion: "1.1",
163
+ soapActionBase: "http://impl.service.wsmtxca.afip.gov.ar/service/"
164
+ },
165
+ "padron-a5": {
166
+ namespace: "http://a5.soap.ws.server.puc.sr/",
167
+ endpoint: {
168
+ production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5",
169
+ test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5"
170
+ },
171
+ soapVersion: "1.1",
172
+ soapActionBase: "",
173
+ usesEmptySoapAction: true
174
+ },
175
+ "padron-a13": {
176
+ namespace: "http://a13.soap.ws.server.puc.sr/",
177
+ endpoint: {
178
+ production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA13",
179
+ test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA13"
180
+ },
181
+ soapVersion: "1.1",
182
+ soapActionBase: "",
183
+ usesEmptySoapAction: true
184
+ }
185
+ };
186
+ function getArcaServiceConfig(service) {
187
+ const serviceConfig = ARCA_SERVICE_CONFIG[service];
188
+ if (!serviceConfig) {
189
+ throw new ArcaConfigurationError(
190
+ `Unsupported ARCA service configuration: ${service}`
191
+ );
192
+ }
193
+ return serviceConfig;
194
+ }
195
+ function normalizeArcaClientConfig(config) {
196
+ const normalizedEnvironment = normalizeEnvironmentValue(String(config.environment)) ?? config.environment;
197
+ const normalizedLoggerLevel = normalizeLogLevelValue(config.logger?.level);
198
+ return {
199
+ taxId: config.taxId.trim(),
200
+ certificatePem: config.certificatePem.trim(),
201
+ privateKeyPem: config.privateKeyPem.trim(),
202
+ environment: normalizedEnvironment,
203
+ timeout: config.timeout ?? DEFAULT_ARCA_TIMEOUT_MS,
204
+ retries: config.retries ?? DEFAULT_ARCA_RETRIES,
205
+ retryDelay: config.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS,
206
+ ...config.logger === void 0 ? {} : {
207
+ logger: {
208
+ ...config.logger,
209
+ ...normalizedLoggerLevel === void 0 ? {} : { level: normalizedLoggerLevel }
210
+ }
211
+ }
212
+ };
213
+ }
214
+ function normalizeEnvironmentValue(value) {
215
+ if (!value) {
216
+ return void 0;
217
+ }
218
+ const normalized = value.trim().toLowerCase();
219
+ if (ARCA_ENVIRONMENTS.includes(normalized)) {
220
+ return normalized;
221
+ }
222
+ return void 0;
223
+ }
224
+ function readEnv(env, variableName) {
225
+ return env[variableName]?.trim() || void 0;
226
+ }
227
+ function normalizeLogLevelValue(value) {
228
+ if (!value) {
229
+ return void 0;
230
+ }
231
+ const normalized = value.trim().toLowerCase();
232
+ if (VALID_ARCA_LOG_LEVELS.includes(normalized)) {
233
+ return normalized;
234
+ }
235
+ return value;
236
+ }
237
+
238
+ // src/internal/logger.ts
239
+ var ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
240
+ function createArcaLogger(config) {
241
+ const disabled = config?.disabled ?? false;
242
+ const level = resolveArcaLogLevel(config?.level);
243
+ const sink = config?.log ?? defaultArcaLog;
244
+ const log = (messageLevel, message, ...args) => {
245
+ if (disabled || !shouldLog(level, messageLevel)) {
246
+ return;
247
+ }
248
+ sink(messageLevel, message, ...args);
249
+ };
250
+ return {
251
+ disabled,
252
+ level,
253
+ log,
254
+ debug(message, ...args) {
255
+ log("debug", message, ...args);
256
+ },
257
+ info(message, ...args) {
258
+ log("info", message, ...args);
259
+ },
260
+ warn(message, ...args) {
261
+ log("warn", message, ...args);
262
+ },
263
+ error(message, ...args) {
264
+ log("error", message, ...args);
265
+ }
266
+ };
267
+ }
268
+ function resolveArcaLogLevel(level) {
269
+ if (isArcaLogLevel(level)) {
270
+ return level;
271
+ }
272
+ const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
273
+ if (isArcaLogLevel(envLevel)) {
274
+ return envLevel;
275
+ }
276
+ return "warn";
277
+ }
278
+ function shouldLog(threshold, messageLevel) {
279
+ return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
280
+ }
281
+ function isArcaLogLevel(value) {
282
+ return ARCA_LOG_LEVELS.includes(value);
283
+ }
284
+ function defaultArcaLog(level, message, ...args) {
285
+ const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
286
+ method(message, ...args);
287
+ }
288
+
289
+ // src/services/padron.ts
290
+ function createPadronService(options) {
291
+ return {
292
+ async getTaxpayerDetails(taxId) {
293
+ const raw = await executePadronOperation(
294
+ options,
295
+ "padron-a5",
296
+ "getPersona_v2",
297
+ {
298
+ idPersona: Number.parseInt(String(taxId), 10)
299
+ }
300
+ );
301
+ if (!raw) {
302
+ return null;
303
+ }
304
+ const record = raw;
305
+ const datosGenerales = record.datosGenerales;
306
+ return {
307
+ taxId: String(record.idPersona ?? ""),
308
+ ...record.tipoPersona === void 0 ? {} : { personType: String(record.tipoPersona) },
309
+ ...datosGenerales ? { name: extractPadronName(datosGenerales) } : {},
310
+ raw: record
311
+ };
312
+ },
313
+ async getTaxIdByDocument(documentNumber) {
314
+ const raw = await executePadronOperation(
315
+ options,
316
+ "padron-a13",
317
+ "getIdPersonaListByDocumento",
318
+ {
319
+ documento: String(documentNumber)
320
+ }
321
+ );
322
+ if (!raw) {
323
+ return null;
324
+ }
325
+ const record = raw;
326
+ const idPersona = record.idPersona;
327
+ const taxIds = Array.isArray(idPersona) ? idPersona.map(String) : idPersona === void 0 ? [] : [String(idPersona)];
328
+ return {
329
+ taxIds,
330
+ raw: record
331
+ };
332
+ }
333
+ };
334
+ }
335
+ function extractPadronName(datosGenerales) {
336
+ if (typeof datosGenerales.razonSocial === "string") {
337
+ return datosGenerales.razonSocial;
338
+ }
339
+ const nombre = datosGenerales.nombre;
340
+ const apellido = datosGenerales.apellido;
341
+ if (typeof apellido === "string" && typeof nombre === "string") {
342
+ return `${apellido} ${nombre}`.trim();
343
+ }
344
+ if (typeof apellido === "string") {
345
+ return apellido;
346
+ }
347
+ if (typeof nombre === "string") {
348
+ return nombre;
349
+ }
350
+ return void 0;
351
+ }
352
+ async function executePadronOperation(options, service, operation, body) {
353
+ const auth = await options.auth.login(
354
+ service === "padron-a5" ? "ws_sr_constancia_inscripcion" : "ws_sr_padron_a13"
355
+ );
356
+ try {
357
+ const response = await options.soap.execute({
358
+ service,
359
+ operation,
360
+ bodyElementNamespaceMode: "prefix",
361
+ body: {
362
+ token: auth.token,
363
+ sign: auth.sign,
364
+ cuitRepresentada: Number.parseInt(options.config.taxId, 10),
365
+ ...body
366
+ }
367
+ });
368
+ const operationResponse = response.result;
369
+ if (operation === "getPersona_v2") {
370
+ return operationResponse.personaReturn ?? null;
371
+ }
372
+ if (operation === "getIdPersonaListByDocumento") {
373
+ return operationResponse.idPersonaListReturn ?? null;
374
+ }
375
+ return operationResponse.return ?? null;
376
+ } catch (error) {
377
+ if (error instanceof ArcaSoapFaultError && // Public Padron A5/A13 WSDLs expose only a generic validation fault, so
378
+ // there is no documented not-found-specific fault code to match here.
379
+ // Keep the current message fallback, but treat it as fragile.
380
+ error.message.toLowerCase().includes("no existe")) {
381
+ return null;
382
+ }
383
+ throw error;
384
+ }
385
+ }
386
+
387
+ // src/services/wsfe.ts
388
+ function createWsfeService(options) {
389
+ async function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
390
+ const auth = await options.auth.login("wsfe", {
391
+ representedTaxId: input.representedTaxId,
392
+ forceRefresh: input.forceAuthRefresh
393
+ });
394
+ const response = await options.soap.execute({
395
+ service: "wsfe",
396
+ operation,
397
+ body: {
398
+ Auth: createWsfeAuth(
399
+ input.representedTaxId ?? options.config.taxId,
400
+ auth.token,
401
+ auth.sign
402
+ ),
403
+ ...body
404
+ }
405
+ });
406
+ return unwrapWsfeOperationResult(operation, response.result);
407
+ }
408
+ async function executeWsfeOperation(operation, body = {}) {
409
+ const response = await options.soap.execute({
410
+ service: "wsfe",
411
+ operation,
412
+ body
413
+ });
414
+ return unwrapWsfeOperationResult(operation, response.result);
415
+ }
416
+ async function getNextVoucherNumber({
417
+ representedTaxId,
418
+ salesPoint,
419
+ voucherType,
420
+ forceAuthRefresh
421
+ }) {
422
+ const result = await executeWsfeAuthenticatedOperation(
423
+ "FECompUltimoAutorizado",
424
+ {
425
+ representedTaxId,
426
+ forceAuthRefresh
427
+ },
428
+ {
429
+ PtoVta: salesPoint,
430
+ CbteTipo: voucherType
431
+ }
432
+ );
433
+ return Number(result.CbteNro ?? 0) + 1;
434
+ }
435
+ async function getWsfeCatalog(operation, resultKey, input) {
436
+ const result = await executeWsfeAuthenticatedOperation(operation, {
437
+ representedTaxId: input.representedTaxId,
438
+ forceAuthRefresh: input.forceAuthRefresh
439
+ });
440
+ return getWsfeResultEntries(result, resultKey).map(mapWsfeCatalogEntry);
441
+ }
442
+ return {
443
+ async createNextVoucher({ representedTaxId, data }) {
444
+ const normalizedInput = normalizeWsfeVoucherInput(data);
445
+ const voucherNumber = await getNextVoucherNumber({
446
+ representedTaxId,
447
+ salesPoint: normalizedInput.salesPoint,
448
+ voucherType: normalizedInput.voucherType
449
+ });
450
+ const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
451
+ const auth = await options.auth.login("wsfe", { representedTaxId });
452
+ const response = await options.soap.execute({
453
+ service: "wsfe",
454
+ operation: "FECAESolicitar",
455
+ body: {
456
+ Auth: createWsfeAuth(
457
+ representedTaxId ?? options.config.taxId,
458
+ auth.token,
459
+ auth.sign
460
+ ),
461
+ FeCAEReq: {
462
+ FeCabReq: {
463
+ CantReg: 1,
464
+ PtoVta: normalizedInput.salesPoint,
465
+ CbteTipo: normalizedInput.voucherType
466
+ },
467
+ FeDetReq: {
468
+ FECAEDetRequest: requestData
469
+ }
470
+ }
471
+ }
472
+ });
473
+ const result = unwrapWsfeOperationResult(
474
+ "FECAESolicitar",
475
+ response.result
476
+ );
477
+ const detailResponse = normalizeWsfeDetailResponse(result);
478
+ const cae = detailResponse.CAE;
479
+ const caeExpiry = detailResponse.CAEFchVto;
480
+ if (typeof cae !== "string" || typeof caeExpiry !== "string") {
481
+ throw new ArcaServiceError(
482
+ "WSFE did not return CAE authorization data",
483
+ { detail: result }
484
+ );
485
+ }
486
+ return {
487
+ cae,
488
+ caeExpiry: String(caeExpiry),
489
+ voucherNumber,
490
+ raw: result
491
+ };
492
+ },
493
+ getNextVoucherNumber,
494
+ getLastVoucher(input) {
495
+ return getNextVoucherNumber(input);
496
+ },
497
+ async getSalesPoints({ representedTaxId, forceAuthRefresh }) {
498
+ const result = await executeWsfeAuthenticatedOperation(
499
+ "FEParamGetPtosVenta",
500
+ {
501
+ representedTaxId,
502
+ forceAuthRefresh
503
+ }
504
+ );
505
+ const rawPoints = result.ResultGet?.PtoVenta;
506
+ if (!rawPoints) {
507
+ return [];
508
+ }
509
+ const entries = Array.isArray(rawPoints) ? rawPoints : [rawPoints];
510
+ return entries.map(mapWsfeSalesPoint);
511
+ },
512
+ getVoucherTypes(input) {
513
+ return getWsfeCatalog("FEParamGetTiposCbte", "CbteTipo", input);
514
+ },
515
+ getDocumentTypes(input) {
516
+ return getWsfeCatalog("FEParamGetTiposDoc", "DocTipo", input);
517
+ },
518
+ getConceptTypes(input) {
519
+ return getWsfeCatalog("FEParamGetTiposConcepto", "ConceptoTipo", input);
520
+ },
521
+ async getCurrencyTypes({ representedTaxId, forceAuthRefresh }) {
522
+ const result = await executeWsfeAuthenticatedOperation(
523
+ "FEParamGetTiposMonedas",
524
+ {
525
+ representedTaxId,
526
+ forceAuthRefresh
527
+ }
528
+ );
529
+ return getWsfeResultEntries(result, "Moneda").map(mapWsfeCurrencyType);
530
+ },
531
+ getVatRates(input) {
532
+ return getWsfeCatalog("FEParamGetTiposIva", "IvaTipo", input);
533
+ },
534
+ getTaxTypes(input) {
535
+ return getWsfeCatalog("FEParamGetTiposTributos", "TributoTipo", input);
536
+ },
537
+ getOptionalTypes(input) {
538
+ return getWsfeCatalog("FEParamGetTiposOpcional", "OpcionalTipo", input);
539
+ },
540
+ async getActivities({ representedTaxId, forceAuthRefresh }) {
541
+ const result = await executeWsfeAuthenticatedOperation(
542
+ "FEParamGetActividades",
543
+ {
544
+ representedTaxId,
545
+ forceAuthRefresh
546
+ }
547
+ );
548
+ return getWsfeResultEntries(result, "ActividadesTipo").map(
549
+ mapWsfeActivityType
550
+ );
551
+ },
552
+ async getReceiverVatConditions({
553
+ representedTaxId,
554
+ voucherClass,
555
+ forceAuthRefresh
556
+ }) {
557
+ const result = await executeWsfeAuthenticatedOperation(
558
+ "FEParamGetCondicionIvaReceptor",
559
+ {
560
+ representedTaxId,
561
+ forceAuthRefresh
562
+ },
563
+ {
564
+ ...voucherClass === void 0 ? {} : { ClaseCmp: voucherClass }
565
+ }
566
+ );
567
+ return getWsfeResultEntries(result, "CondicionIvaReceptor").map(
568
+ mapWsfeReceiverVatCondition
569
+ );
570
+ },
571
+ async getServerStatus() {
572
+ const result = await executeWsfeOperation("FEDummy");
573
+ return mapWsfeServerStatus(result);
574
+ },
575
+ async getQuotation({ currencyId, representedTaxId, forceAuthRefresh }) {
576
+ const result = await executeWsfeAuthenticatedOperation(
577
+ "FEParamGetCotizacion",
578
+ {
579
+ representedTaxId,
580
+ forceAuthRefresh
581
+ },
582
+ {
583
+ MonId: currencyId
584
+ }
585
+ );
586
+ const raw = result.ResultGet ?? {};
587
+ return mapWsfeQuotation(raw);
588
+ },
589
+ async getVoucherInfo({
590
+ representedTaxId,
591
+ number,
592
+ salesPoint,
593
+ voucherType
594
+ }) {
595
+ const result = await executeWsfeAuthenticatedOperation(
596
+ "FECompConsultar",
597
+ {
598
+ representedTaxId
599
+ },
600
+ {
601
+ FeCompConsReq: {
602
+ CbteNro: number,
603
+ PtoVta: salesPoint,
604
+ CbteTipo: voucherType
605
+ }
606
+ }
607
+ );
608
+ const raw = result.ResultGet ?? null;
609
+ if (!raw) {
610
+ return null;
611
+ }
612
+ return mapWsfeVoucherInfo(raw);
613
+ }
614
+ };
615
+ }
616
+ function mapWsfeVoucherInput(input, voucherNumber) {
617
+ const data = {
618
+ Concepto: input.concept,
619
+ DocTipo: input.documentType,
620
+ DocNro: input.documentNumber,
621
+ CbteDesde: voucherNumber,
622
+ CbteHasta: voucherNumber,
623
+ CbteFch: input.voucherDate,
624
+ ImpTotal: input.totalAmount,
625
+ ImpTotConc: input.nonTaxableAmount,
626
+ ImpNeto: input.netAmount,
627
+ ImpOpEx: input.exemptAmount,
628
+ ImpTrib: input.taxAmount,
629
+ ImpIVA: input.vatAmount,
630
+ MonId: input.currencyId,
631
+ MonCotiz: input.exchangeRate,
632
+ PtoVta: input.salesPoint,
633
+ CbteTipo: input.voucherType
634
+ };
635
+ if (input.receiverVatConditionId !== void 0) {
636
+ data.CondicionIVAReceptorId = input.receiverVatConditionId;
637
+ }
638
+ if (input.sameCurrencyForeignCancellation !== void 0) {
639
+ data.CanMisMonExt = input.sameCurrencyForeignCancellation;
640
+ }
641
+ if (input.serviceStartDate !== void 0) {
642
+ data.FchServDesde = input.serviceStartDate;
643
+ }
644
+ if (input.serviceEndDate !== void 0) {
645
+ data.FchServHasta = input.serviceEndDate;
646
+ }
647
+ if (input.paymentDueDate !== void 0) {
648
+ data.FchVtoPago = input.paymentDueDate;
649
+ }
650
+ if (input.associatedVouchers) {
651
+ data.CbtesAsoc = {
652
+ CbteAsoc: input.associatedVouchers.map((v) => ({
653
+ Tipo: v.type,
654
+ PtoVta: v.salesPoint,
655
+ Nro: v.number,
656
+ ...v.taxId === void 0 ? {} : { Cuit: v.taxId },
657
+ ...v.voucherDate === void 0 ? {} : { CbteFch: v.voucherDate }
658
+ }))
659
+ };
660
+ }
661
+ if (input.associatedPeriod) {
662
+ data.PeriodoAsoc = {
663
+ FchDesde: input.associatedPeriod.startDate,
664
+ FchHasta: input.associatedPeriod.endDate
665
+ };
666
+ }
667
+ if (input.taxes) {
668
+ data.Tributos = {
669
+ Tributo: input.taxes.map((t) => ({
670
+ Id: t.id,
671
+ ...t.description === void 0 ? {} : { Desc: t.description },
672
+ BaseImp: t.baseAmount,
673
+ Alic: t.rate,
674
+ Importe: t.amount
675
+ }))
676
+ };
677
+ }
678
+ if (input.vatRates) {
679
+ data.Iva = {
680
+ AlicIva: input.vatRates.map((v) => ({
681
+ Id: v.id,
682
+ BaseImp: v.baseAmount,
683
+ Importe: v.amount
684
+ }))
685
+ };
686
+ }
687
+ if (input.optionalFields) {
688
+ data.Opcionales = {
689
+ Opcional: input.optionalFields.map((o) => ({
690
+ Id: o.id,
691
+ Valor: o.value
692
+ }))
693
+ };
694
+ }
695
+ if (input.buyers) {
696
+ data.Compradores = {
697
+ Comprador: input.buyers.map((b) => ({
698
+ DocTipo: b.documentType,
699
+ DocNro: b.documentNumber,
700
+ Porcentaje: b.percentage
701
+ }))
702
+ };
703
+ }
704
+ if (input.activities) {
705
+ data.Actividades = {
706
+ Actividad: input.activities.map((a) => ({
707
+ Id: a.id
708
+ }))
709
+ };
710
+ }
711
+ return data;
712
+ }
713
+ function normalizeWsfeVoucherInput(input) {
714
+ const {
715
+ voucherDate,
716
+ serviceStartDate,
717
+ serviceEndDate,
718
+ paymentDueDate,
719
+ associatedVouchers,
720
+ associatedPeriod,
721
+ ...rest
722
+ } = input;
723
+ return {
724
+ ...rest,
725
+ voucherDate: normalizeWsfeDateInput(voucherDate, "voucherDate"),
726
+ ...serviceStartDate === void 0 ? {} : {
727
+ serviceStartDate: normalizeWsfeDateInput(
728
+ serviceStartDate,
729
+ "serviceStartDate"
730
+ )
731
+ },
732
+ ...serviceEndDate === void 0 ? {} : {
733
+ serviceEndDate: normalizeWsfeDateInput(
734
+ serviceEndDate,
735
+ "serviceEndDate"
736
+ )
737
+ },
738
+ ...paymentDueDate === void 0 ? {} : {
739
+ paymentDueDate: normalizeWsfeDateInput(
740
+ paymentDueDate,
741
+ "paymentDueDate"
742
+ )
743
+ },
744
+ ...associatedVouchers === void 0 ? {} : {
745
+ associatedVouchers: associatedVouchers.map((voucher, index) => {
746
+ const { voucherDate: associatedVoucherDate, ...associatedRest } = voucher;
747
+ return {
748
+ ...associatedRest,
749
+ ...associatedVoucherDate === void 0 ? {} : {
750
+ voucherDate: normalizeWsfeDateInput(
751
+ associatedVoucherDate,
752
+ `associatedVouchers[${index}].voucherDate`
753
+ )
754
+ }
755
+ };
756
+ })
757
+ },
758
+ ...associatedPeriod === void 0 ? {} : {
759
+ associatedPeriod: {
760
+ startDate: normalizeWsfeDateInput(
761
+ associatedPeriod.startDate,
762
+ "associatedPeriod.startDate"
763
+ ),
764
+ endDate: normalizeWsfeDateInput(
765
+ associatedPeriod.endDate,
766
+ "associatedPeriod.endDate"
767
+ )
768
+ }
769
+ }
770
+ };
771
+ }
772
+ function normalizeWsfeDateInput(value, fieldName) {
773
+ if (typeof value !== "string") {
774
+ throw new ArcaInputError(
775
+ `Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
776
+ {
777
+ detail: { field: fieldName, value }
778
+ }
779
+ );
780
+ }
781
+ const normalizedValue = value.trim();
782
+ const afipMatch = normalizedValue.match(/^(\d{4})(\d{2})(\d{2})$/);
783
+ if (afipMatch) {
784
+ const [, year, month, day] = afipMatch;
785
+ assertValidCalendarDate(year, month, day, fieldName, normalizedValue);
786
+ return normalizedValue;
787
+ }
788
+ const isoMatch = normalizedValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
789
+ if (isoMatch) {
790
+ const [, year, month, day] = isoMatch;
791
+ assertValidCalendarDate(year, month, day, fieldName, normalizedValue);
792
+ return `${year}${month}${day}`;
793
+ }
794
+ throw new ArcaInputError(
795
+ `Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
796
+ {
797
+ detail: { field: fieldName, value: normalizedValue }
798
+ }
799
+ );
800
+ }
801
+ function assertValidCalendarDate(yearInput, monthInput, dayInput, fieldName, value) {
802
+ const year = Number(yearInput);
803
+ const month = Number(monthInput);
804
+ const day = Number(dayInput);
805
+ const candidate = new Date(Date.UTC(year, month - 1, day));
806
+ if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() !== month - 1 || candidate.getUTCDate() !== day) {
807
+ throw new ArcaInputError(
808
+ `Invalid WSFE ${fieldName}: received a non-existent calendar date`,
809
+ {
810
+ detail: { field: fieldName, value }
811
+ }
812
+ );
813
+ }
814
+ }
815
+ function mapWsfeSalesPoint(raw) {
816
+ const record = raw;
817
+ return {
818
+ number: Number(record.Nro ?? 0),
819
+ ...record.EmisionTipo === void 0 ? {} : { emissionType: String(record.EmisionTipo) },
820
+ ...record.Bloqueado === void 0 ? {} : { blocked: String(record.Bloqueado) },
821
+ ...record.FchBaja === void 0 ? {} : { deletedSince: String(record.FchBaja) }
822
+ };
823
+ }
824
+ function mapWsfeCatalogEntry(raw) {
825
+ const record = raw;
826
+ return {
827
+ id: Number(record.Id ?? 0),
828
+ description: String(record.Desc ?? "")
829
+ };
830
+ }
831
+ function mapWsfeActivityType(raw) {
832
+ const record = raw;
833
+ return {
834
+ id: Number(record.Id ?? 0),
835
+ description: String(record.Desc ?? ""),
836
+ order: Number(record.Orden ?? 0)
837
+ };
838
+ }
839
+ function mapWsfeReceiverVatCondition(raw) {
840
+ const record = raw;
841
+ return {
842
+ id: Number(record.Id ?? 0),
843
+ description: String(record.Desc ?? ""),
844
+ voucherClass: String(record.Cmp_Clase ?? "")
845
+ };
846
+ }
847
+ function mapWsfeCurrencyType(raw) {
848
+ const record = raw;
849
+ return {
850
+ id: String(record.Id ?? ""),
851
+ description: String(record.Desc ?? ""),
852
+ validFrom: String(record.FchDesde ?? ""),
853
+ validTo: String(record.FchHasta ?? "")
854
+ };
855
+ }
856
+ function mapWsfeServerStatus(raw) {
857
+ return {
858
+ appServer: String(raw.AppServer ?? ""),
859
+ dbServer: String(raw.DbServer ?? ""),
860
+ authServer: String(raw.AuthServer ?? "")
861
+ };
862
+ }
863
+ function mapWsfeQuotation(raw) {
864
+ return {
865
+ currencyId: String(raw.MonId ?? ""),
866
+ rate: Number(raw.MonCotiz ?? 0),
867
+ date: String(raw.FchCotiz ?? "")
868
+ };
869
+ }
870
+ function mapWsfeVoucherInfo(raw) {
871
+ return {
872
+ voucherNumber: Number(raw.CbteDesde ?? raw.CbteHasta ?? 0),
873
+ ...raw.CbteFch === void 0 ? {} : { voucherDate: String(raw.CbteFch) },
874
+ ...raw.PtoVta === void 0 ? {} : { salesPoint: Number(raw.PtoVta) },
875
+ ...raw.CbteTipo === void 0 ? {} : { voucherType: Number(raw.CbteTipo) },
876
+ ...raw.ImpTotal === void 0 ? {} : { totalAmount: Number(raw.ImpTotal) },
877
+ ...raw.Resultado === void 0 ? {} : { result: String(raw.Resultado) },
878
+ ...raw.CAE === void 0 ? {} : { cae: String(raw.CAE) },
879
+ ...raw.CAEFchVto === void 0 ? {} : { caeExpiry: String(raw.CAEFchVto) },
880
+ raw
881
+ };
882
+ }
883
+ function createWsfeAuth(representedTaxId, token, sign) {
884
+ return {
885
+ Token: token,
886
+ Sign: sign,
887
+ Cuit: Number.parseInt(String(representedTaxId), 10)
888
+ };
889
+ }
890
+ function unwrapWsfeOperationResult(operation, response) {
891
+ const operationResponse = response[`${operation}Response`];
892
+ const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
893
+ if (operation === "FECAESolicitar") {
894
+ const detailResponse = normalizeWsfeDetailResponse(result);
895
+ const resultCode = detailResponse.Resultado;
896
+ if (resultCode && resultCode !== "A") {
897
+ const observationsContainer = detailResponse.Observaciones;
898
+ const observations = normalizeWsfeErrors(observationsContainer?.Obs);
899
+ if (observations.length > 0) {
900
+ const firstObservation = observations[0];
901
+ if (!firstObservation) {
902
+ throw new ArcaServiceError(
903
+ "WSFE returned an empty observation list",
904
+ {
905
+ detail: result
906
+ }
907
+ );
908
+ }
909
+ throw new ArcaServiceError(firstObservation.message, {
910
+ serviceCode: firstObservation.code,
911
+ detail: result
912
+ });
913
+ }
914
+ }
915
+ }
916
+ const errorsContainer = result.Errors;
917
+ const errors = normalizeWsfeErrors(errorsContainer?.Err);
918
+ if (errors.length > 0) {
919
+ const firstError = errors[0];
920
+ if (!firstError) {
921
+ throw new ArcaServiceError("WSFE returned an empty error list", {
922
+ detail: result
923
+ });
924
+ }
925
+ throw new ArcaServiceError(firstError.message, {
926
+ serviceCode: firstError.code,
927
+ detail: result
928
+ });
929
+ }
930
+ return result;
931
+ }
932
+ function normalizeWsfeDetailResponse(result) {
933
+ const detailResponse = result.FeDetResp;
934
+ const rawDetail = detailResponse?.FECAEDetResponse;
935
+ if (Array.isArray(rawDetail)) {
936
+ return rawDetail[0] ?? {};
937
+ }
938
+ return rawDetail ?? {};
939
+ }
940
+ function normalizeWsfeErrors(rawErrors) {
941
+ const entries = Array.isArray(rawErrors) ? rawErrors : rawErrors ? [rawErrors] : [];
942
+ return entries.map((entry) => entry).map((entry) => {
943
+ const code = entry.Code ?? entry.code ?? "N/A";
944
+ const message = entry.Msg ?? entry.msg ?? "Unknown WSFE error";
945
+ return {
946
+ code: String(code),
947
+ message: `(${String(code)}) ${String(message)}`
948
+ };
949
+ });
950
+ }
951
+ function getWsfeResultEntries(result, key) {
952
+ const rawEntries = result.ResultGet?.[key];
953
+ if (!rawEntries) {
954
+ return [];
955
+ }
956
+ return (Array.isArray(rawEntries) ? rawEntries : [rawEntries]).map(
957
+ (entry) => entry
958
+ );
959
+ }
960
+
961
+ // src/services/wsmtxca.ts
962
+ function createWsmtxcaService(options) {
963
+ return {
964
+ async authorizeVoucher({ representedTaxId, data }) {
965
+ const auth = await options.auth.login("wsmtxca", { representedTaxId });
966
+ const response = await options.soap.execute({
967
+ service: "wsmtxca",
968
+ operation: "autorizarComprobante",
969
+ bodyElementName: "autorizarComprobanteRequest",
970
+ bodyElementNamespaceMode: "prefix",
971
+ body: {
972
+ authRequest: createWsmtxcaAuth(
973
+ representedTaxId ?? options.config.taxId,
974
+ auth.token,
975
+ auth.sign
976
+ ),
977
+ ...data
978
+ }
979
+ });
980
+ const raw = unwrapWsmtxcaOperationResponse(
981
+ response.result,
982
+ "autorizarComprobante"
983
+ );
984
+ const authorizationPayload = extractWsmtxcaAuthorizationPayload(raw);
985
+ const messages = extractWsmtxcaMessages(raw);
986
+ const resultado = raw.resultado ?? authorizationPayload.resultado;
987
+ const caeValue = authorizationPayload.CAE ?? authorizationPayload.codigoAutorizacion ?? raw.codigoAutorizacion;
988
+ if (resultado === "R" || caeValue == null) {
989
+ throw new ArcaServiceError(
990
+ messages.join(" | ") || "WSMTXCA rejected the voucher authorization",
991
+ { detail: raw }
992
+ );
993
+ }
994
+ return {
995
+ cae: String(caeValue),
996
+ caeExpiry: normalizeWsmtxcaResponseDate(
997
+ authorizationPayload.fechaVencimientoCAE ?? authorizationPayload.fechaVencimiento ?? raw.fechaVencimiento
998
+ ),
999
+ voucherNumber: parseWsmtxcaVoucherNumber(
1000
+ authorizationPayload.numeroComprobante ?? raw.numeroComprobante,
1001
+ "WSMTXCA did not return the authorized voucher number",
1002
+ raw
1003
+ ),
1004
+ messages,
1005
+ raw
1006
+ };
1007
+ },
1008
+ async getLastAuthorizedVoucher({
1009
+ representedTaxId,
1010
+ voucherType,
1011
+ salesPoint
1012
+ }) {
1013
+ const auth = await options.auth.login("wsmtxca", { representedTaxId });
1014
+ const response = await options.soap.execute({
1015
+ service: "wsmtxca",
1016
+ operation: "consultarUltimoComprobanteAutorizado",
1017
+ bodyElementName: "consultarUltimoComprobanteAutorizadoRequest",
1018
+ bodyElementNamespaceMode: "prefix",
1019
+ body: {
1020
+ authRequest: createWsmtxcaAuth(
1021
+ representedTaxId ?? options.config.taxId,
1022
+ auth.token,
1023
+ auth.sign
1024
+ ),
1025
+ consultaUltimoComprobanteAutorizadoRequest: {
1026
+ codigoTipoComprobante: voucherType,
1027
+ numeroPuntoVenta: salesPoint
1028
+ }
1029
+ }
1030
+ });
1031
+ const raw = unwrapWsmtxcaOperationResponse(
1032
+ response.result,
1033
+ "consultarUltimoComprobanteAutorizado"
1034
+ );
1035
+ return {
1036
+ voucherNumber: parseWsmtxcaVoucherNumber(
1037
+ raw.numeroComprobante ?? raw.cbteNro ?? raw.nroComprobante,
1038
+ extractWsmtxcaMessages(raw).join(" | ") || "WSMTXCA did not return the last authorized voucher number",
1039
+ raw
1040
+ ),
1041
+ raw
1042
+ };
1043
+ },
1044
+ async getVoucher({
1045
+ representedTaxId,
1046
+ voucherType,
1047
+ salesPoint,
1048
+ voucherNumber
1049
+ }) {
1050
+ const auth = await options.auth.login("wsmtxca", { representedTaxId });
1051
+ const response = await options.soap.execute({
1052
+ service: "wsmtxca",
1053
+ operation: "consultarComprobante",
1054
+ bodyElementName: "consultarComprobanteRequest",
1055
+ bodyElementNamespaceMode: "prefix",
1056
+ body: {
1057
+ authRequest: createWsmtxcaAuth(
1058
+ representedTaxId ?? options.config.taxId,
1059
+ auth.token,
1060
+ auth.sign
1061
+ ),
1062
+ consultaComprobanteRequest: {
1063
+ codigoTipoComprobante: voucherType,
1064
+ numeroPuntoVenta: salesPoint,
1065
+ numeroComprobante: voucherNumber
1066
+ }
1067
+ }
1068
+ });
1069
+ const raw = unwrapWsmtxcaOperationResponse(
1070
+ response.result,
1071
+ "consultarComprobante"
1072
+ );
1073
+ const voucher = extractWsmtxcaVoucherPayload(raw);
1074
+ const messages = extractWsmtxcaMessages(raw);
1075
+ const invoiceDate = normalizeWsmtxcaResponseDate(
1076
+ voucher.fechaEmision ?? voucher.fecha ?? voucher.CbteFch
1077
+ );
1078
+ if (!invoiceDate) {
1079
+ throw new ArcaServiceError(
1080
+ messages[0] ?? "WSMTXCA did not return the voucher issue date",
1081
+ { detail: raw }
1082
+ );
1083
+ }
1084
+ return {
1085
+ invoiceDate,
1086
+ voucher,
1087
+ messages,
1088
+ raw
1089
+ };
1090
+ }
1091
+ };
1092
+ }
1093
+ function createWsmtxcaAuth(representedTaxId, token, sign) {
1094
+ return {
1095
+ token,
1096
+ sign,
1097
+ cuitRepresentada: Number.parseInt(String(representedTaxId), 10)
1098
+ };
1099
+ }
1100
+ function toRecord(value) {
1101
+ return value && typeof value === "object" ? value : void 0;
1102
+ }
1103
+ function unwrapWsmtxcaOperationResponse(response, operation) {
1104
+ const responseRecord = toRecord(response) ?? {};
1105
+ if (operation === "autorizarComprobante") {
1106
+ return toRecord(responseRecord.autorizarComprobanteResponse) ?? toRecord(responseRecord.autorizarComprobanteResult) ?? toRecord(responseRecord.comprobanteCAEResponse) ?? toRecord(responseRecord.comprobanteCAEReponse) ?? responseRecord;
1107
+ }
1108
+ if (operation === "consultarComprobante") {
1109
+ return toRecord(responseRecord.consultarComprobanteResponse) ?? toRecord(responseRecord.consultaComprobanteResponse) ?? toRecord(responseRecord.consultarComprobanteResult) ?? responseRecord;
1110
+ }
1111
+ return toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultaUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResult) ?? responseRecord;
1112
+ }
1113
+ function extractWsmtxcaAuthorizationPayload(raw) {
1114
+ return toRecord(raw.comprobanteResponse) ?? toRecord(raw.comprobanteCAEResponse) ?? toRecord(raw.comprobanteCAEReponse) ?? raw;
1115
+ }
1116
+ function extractWsmtxcaVoucherPayload(raw) {
1117
+ return toRecord(raw.comprobanteResponse) ?? toRecord(raw.comprobante) ?? toRecord(raw.cmp) ?? raw;
1118
+ }
1119
+ function extractWsmtxcaMessages(raw) {
1120
+ const rawErrors = raw.arrayErrores;
1121
+ const rawObservations = raw.arrayObservaciones;
1122
+ const toEntries = (value) => {
1123
+ if (!value) {
1124
+ return [];
1125
+ }
1126
+ if (Array.isArray(value)) {
1127
+ return value;
1128
+ }
1129
+ if (typeof value === "object") {
1130
+ return [value];
1131
+ }
1132
+ return [];
1133
+ };
1134
+ const errors = toEntries(rawErrors?.codigoDescripcion).map((entry) => {
1135
+ const code = entry.codigo == null ? "N/A" : String(entry.codigo);
1136
+ const description = entry.descripcion == null ? "Unknown WSMTXCA error" : String(entry.descripcion);
1137
+ return `Error ${code}: ${description}`;
1138
+ });
1139
+ const observations = toEntries(rawObservations?.codigoDescripcion).map(
1140
+ (entry) => {
1141
+ const code = entry.codigo == null ? "N/A" : String(entry.codigo);
1142
+ const description = entry.descripcion == null ? "" : String(entry.descripcion);
1143
+ return `Obs ${code}: ${description}`.trim();
1144
+ }
1145
+ );
1146
+ return [...errors, ...observations];
1147
+ }
1148
+ function parseWsmtxcaVoucherNumber(value, message, detail) {
1149
+ const parsed = Number.parseInt(String(value ?? ""), 10);
1150
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1151
+ throw new ArcaServiceError(message, { detail });
1152
+ }
1153
+ return parsed;
1154
+ }
1155
+ function normalizeWsmtxcaResponseDate(value) {
1156
+ if (typeof value === "number" && Number.isInteger(value)) {
1157
+ return formatCompactDateToIso(value);
1158
+ }
1159
+ if (typeof value !== "string") {
1160
+ return void 0;
1161
+ }
1162
+ const trimmed = value.trim();
1163
+ if (!trimmed) {
1164
+ return void 0;
1165
+ }
1166
+ if (/^\d{8}$/.test(trimmed)) {
1167
+ return formatCompactDateToIso(Number.parseInt(trimmed, 10));
1168
+ }
1169
+ if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) {
1170
+ return trimmed.slice(0, 10);
1171
+ }
1172
+ return void 0;
1173
+ }
1174
+ function formatCompactDateToIso(dateValue) {
1175
+ if (!dateValue) {
1176
+ return void 0;
1177
+ }
1178
+ const raw = String(dateValue);
1179
+ if (raw.length !== 8) {
1180
+ return void 0;
1181
+ }
1182
+ return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
1183
+ }
1184
+
1185
+ // src/internal/http.ts
1186
+ import https from "https";
1187
+ var defaultAgent = new https.Agent({
1188
+ keepAlive: true
1189
+ });
1190
+ var legacyTlsAgent = new https.Agent({
1191
+ keepAlive: true,
1192
+ ciphers: "DEFAULT@SECLEVEL=0"
1193
+ });
1194
+ async function postXml({
1195
+ url,
1196
+ body,
1197
+ contentType,
1198
+ soapAction,
1199
+ useLegacyTlsSecurityLevel0 = false,
1200
+ timeout = 3e4,
1201
+ retries = 0,
1202
+ retryDelay = 500,
1203
+ logger,
1204
+ service,
1205
+ operation
1206
+ }) {
1207
+ const totalAttempts = retries + 1;
1208
+ for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
1209
+ try {
1210
+ return await postXmlOnce({
1211
+ url,
1212
+ body,
1213
+ contentType,
1214
+ soapAction,
1215
+ useLegacyTlsSecurityLevel0,
1216
+ timeout
1217
+ });
1218
+ } catch (error) {
1219
+ if (!(error instanceof ArcaTransportError)) {
1220
+ throw error;
1221
+ }
1222
+ if (attempt >= totalAttempts) {
1223
+ logger?.error("ARCA transport request failed", {
1224
+ service,
1225
+ operation,
1226
+ url,
1227
+ attempt,
1228
+ attempts: totalAttempts,
1229
+ error
1230
+ });
1231
+ throw error;
1232
+ }
1233
+ const nextAttempt = attempt + 1;
1234
+ logger?.warn(
1235
+ `Retrying ARCA request after transport failure (attempt ${nextAttempt}/${totalAttempts})`,
1236
+ {
1237
+ service,
1238
+ operation,
1239
+ url,
1240
+ attempt: nextAttempt,
1241
+ attempts: totalAttempts,
1242
+ error
1243
+ }
1244
+ );
1245
+ await delay(retryDelay);
1246
+ }
1247
+ }
1248
+ throw new ArcaTransportError("ARCA HTTP request exhausted retries");
1249
+ }
1250
+ async function postXmlOnce({
1251
+ url,
1252
+ body,
1253
+ contentType,
1254
+ soapAction,
1255
+ useLegacyTlsSecurityLevel0,
1256
+ timeout
1257
+ }) {
1258
+ const endpoint = new URL(url);
1259
+ const requestBody = Buffer.from(body, "utf8");
1260
+ return await new Promise((resolve, reject) => {
1261
+ let settled = false;
1262
+ const settleResolve = (responseBody) => {
1263
+ if (settled) {
1264
+ return;
1265
+ }
1266
+ settled = true;
1267
+ resolve(responseBody);
1268
+ };
1269
+ const settleReject = (error) => {
1270
+ if (settled) {
1271
+ return;
1272
+ }
1273
+ settled = true;
1274
+ reject(error);
1275
+ };
1276
+ const request = https.request(
1277
+ {
1278
+ protocol: endpoint.protocol,
1279
+ hostname: endpoint.hostname,
1280
+ port: endpoint.port || void 0,
1281
+ path: `${endpoint.pathname}${endpoint.search}`,
1282
+ method: "POST",
1283
+ agent: useLegacyTlsSecurityLevel0 ? legacyTlsAgent : defaultAgent,
1284
+ headers: {
1285
+ Accept: "text/xml, application/soap+xml",
1286
+ "Content-Length": requestBody.byteLength,
1287
+ "Content-Type": contentType,
1288
+ ...soapAction === void 0 ? {} : { SOAPAction: `"${soapAction}"` }
1289
+ }
1290
+ },
1291
+ (response) => {
1292
+ const chunks = [];
1293
+ const getResponseBody = () => Buffer.concat(chunks).toString("utf8");
1294
+ response.on("data", (chunk) => {
1295
+ chunks.push(
1296
+ typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk
1297
+ );
1298
+ });
1299
+ response.on("error", (error) => {
1300
+ settleReject(
1301
+ new ArcaTransportError(
1302
+ `ARCA HTTP response stream failed: ${error.message}`,
1303
+ {
1304
+ cause: error,
1305
+ statusCode: response.statusCode,
1306
+ responseBody: getResponseBody()
1307
+ }
1308
+ )
1309
+ );
1310
+ });
1311
+ response.on("aborted", () => {
1312
+ settleReject(
1313
+ new ArcaTransportError("ARCA HTTP response was aborted", {
1314
+ statusCode: response.statusCode,
1315
+ responseBody: getResponseBody()
1316
+ })
1317
+ );
1318
+ });
1319
+ response.on("end", () => {
1320
+ const responseBody = getResponseBody();
1321
+ const statusCode = response.statusCode ?? 500;
1322
+ const responseContentType = Array.isArray(
1323
+ response.headers["content-type"]
1324
+ ) ? response.headers["content-type"].join("; ") : response.headers["content-type"];
1325
+ if (statusCode >= 200 && statusCode < 300) {
1326
+ settleResolve(responseBody);
1327
+ return;
1328
+ }
1329
+ if (isXmlLikeResponse(responseBody, responseContentType)) {
1330
+ settleResolve(responseBody);
1331
+ return;
1332
+ }
1333
+ settleReject(
1334
+ new ArcaTransportError(
1335
+ `ARCA HTTP request failed with status ${statusCode}`,
1336
+ {
1337
+ statusCode,
1338
+ responseBody
1339
+ }
1340
+ )
1341
+ );
1342
+ });
1343
+ }
1344
+ );
1345
+ request.setTimeout(timeout, () => {
1346
+ request.destroy(
1347
+ new Error(`ARCA HTTP request timed out after ${timeout}ms`)
1348
+ );
1349
+ });
1350
+ request.on("error", (error) => {
1351
+ settleReject(
1352
+ new ArcaTransportError(`ARCA HTTP request failed: ${error.message}`, {
1353
+ cause: error
1354
+ })
1355
+ );
1356
+ });
1357
+ request.write(requestBody);
1358
+ request.end();
1359
+ });
1360
+ }
1361
+ function isXmlLikeResponse(body, contentType) {
1362
+ const normalizedContentType = contentType?.toLowerCase() ?? "";
1363
+ if (normalizedContentType.includes("xml") || normalizedContentType.includes("soap")) {
1364
+ return true;
1365
+ }
1366
+ return body.trimStart().startsWith("<");
1367
+ }
1368
+ function delay(ms) {
1369
+ return new Promise((resolve) => {
1370
+ setTimeout(resolve, ms);
1371
+ });
1372
+ }
1373
+
1374
+ // src/internal/xml.ts
1375
+ import { XMLBuilder, XMLParser } from "fast-xml-parser";
1376
+ var xmlBuilder = new XMLBuilder({
1377
+ attributeNamePrefix: "@_",
1378
+ format: false,
1379
+ ignoreAttributes: false,
1380
+ suppressBooleanAttributes: false,
1381
+ suppressEmptyNode: true
1382
+ });
1383
+ var xmlParser = new XMLParser({
1384
+ attributeNamePrefix: "@_",
1385
+ ignoreAttributes: false,
1386
+ parseAttributeValue: false,
1387
+ parseTagValue: false,
1388
+ removeNSPrefix: true,
1389
+ trimValues: true
1390
+ });
1391
+ function buildSoapEnvelope(soapVersion, operation, namespace, body, options) {
1392
+ const prefix = soapVersion === "1.2" ? "soap12" : "soap";
1393
+ const envelopeNamespace = soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
1394
+ const namespaceMode = options?.namespaceMode ?? "default";
1395
+ const operationElementName = namespaceMode === "prefix" ? `tns:${operation}` : operation;
1396
+ const operationNamespaceAttributes = namespaceMode === "prefix" ? { "@_xmlns:tns": namespace } : { "@_xmlns": namespace };
1397
+ const payload = {
1398
+ [`${prefix}:Envelope`]: {
1399
+ "@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
1400
+ "@_xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
1401
+ [`@_xmlns:${prefix}`]: envelopeNamespace,
1402
+ [`${prefix}:Body`]: {
1403
+ [operationElementName]: {
1404
+ ...operationNamespaceAttributes,
1405
+ ...pruneUndefinedDeep(body)
1406
+ }
1407
+ }
1408
+ }
1409
+ };
1410
+ return `<?xml version="1.0" encoding="utf-8"?>${xmlBuilder.build(payload)}`;
1411
+ }
1412
+ function parseSoapBody(xml) {
1413
+ const parsed = xmlParser.parse(xml);
1414
+ const envelope = parsed.Envelope;
1415
+ const body = envelope?.Body;
1416
+ if (!body) {
1417
+ throw new ArcaSoapFaultError("Invalid SOAP response: missing body", {
1418
+ detail: parsed
1419
+ });
1420
+ }
1421
+ const fault = body.Fault;
1422
+ if (fault) {
1423
+ throw createSoapFaultError(fault);
1424
+ }
1425
+ return body;
1426
+ }
1427
+ function getSingleBodyEntry(body) {
1428
+ const entries = Object.entries(body).filter(([key]) => key !== "@_xmlns");
1429
+ if (entries.length !== 1) {
1430
+ throw new ArcaSoapFaultError(
1431
+ `Invalid SOAP response: expected a single body entry, got ${entries.length}`,
1432
+ {
1433
+ detail: body
1434
+ }
1435
+ );
1436
+ }
1437
+ return entries[0];
1438
+ }
1439
+ function parseXmlDocument(xml) {
1440
+ return xmlParser.parse(xml);
1441
+ }
1442
+ function pruneUndefinedDeep(value) {
1443
+ if (Array.isArray(value)) {
1444
+ return value.map((item) => pruneUndefinedDeep(item)).filter((item) => item !== void 0);
1445
+ }
1446
+ if (value && typeof value === "object") {
1447
+ const entries = Object.entries(value).filter(([, nestedValue]) => nestedValue !== void 0).map(([key, nestedValue]) => [key, pruneUndefinedDeep(nestedValue)]);
1448
+ return Object.fromEntries(entries);
1449
+ }
1450
+ return value;
1451
+ }
1452
+ function createSoapFaultError(fault) {
1453
+ const faultCode = typeof fault.faultcode === "string" ? fault.faultcode : getNestedString(fault, ["Code", "Value"]);
1454
+ const message = typeof fault.faultstring === "string" ? fault.faultstring : getNestedString(fault, ["Reason", "Text"]) ?? "ARCA SOAP fault response";
1455
+ return new ArcaSoapFaultError(message, {
1456
+ faultCode: faultCode ?? void 0,
1457
+ detail: fault
1458
+ });
1459
+ }
1460
+ function getNestedString(value, path) {
1461
+ let current = value;
1462
+ for (const key of path) {
1463
+ if (!current || typeof current !== "object") {
1464
+ return null;
1465
+ }
1466
+ current = current[key];
1467
+ }
1468
+ return typeof current === "string" ? current : null;
1469
+ }
1470
+
1471
+ // src/soap/index.ts
1472
+ function createSoapTransport(options) {
1473
+ return {
1474
+ async execute(request) {
1475
+ const serviceConfig = getArcaServiceConfig(request.service);
1476
+ const url = serviceConfig.endpoint[options.config.environment];
1477
+ const soapActionOperation = request.operation;
1478
+ const bodyElementName = request.bodyElementName ?? request.operation;
1479
+ const soapAction = serviceConfig.usesEmptySoapAction ? "" : `${serviceConfig.soapActionBase}${soapActionOperation}`;
1480
+ const contentType = serviceConfig.soapVersion === "1.2" ? `application/soap+xml; charset=utf-8; action="${soapAction}"` : 'text/xml; charset="utf-8"';
1481
+ const xml = buildSoapEnvelope(
1482
+ serviceConfig.soapVersion,
1483
+ bodyElementName,
1484
+ serviceConfig.namespace,
1485
+ request.body,
1486
+ {
1487
+ namespaceMode: request.bodyElementNamespaceMode
1488
+ }
1489
+ );
1490
+ const startedAt = Date.now();
1491
+ options.logger?.debug("Sending ARCA SOAP request", {
1492
+ service: request.service,
1493
+ operation: request.operation,
1494
+ url
1495
+ });
1496
+ try {
1497
+ const responseXml = await postXml({
1498
+ url,
1499
+ body: xml,
1500
+ contentType,
1501
+ soapAction: serviceConfig.soapVersion === "1.1" ? soapAction : void 0,
1502
+ useLegacyTlsSecurityLevel0: options.config.environment === "production" && serviceConfig.useLegacyTlsSecurityLevel0 === true,
1503
+ timeout: options.config.timeout,
1504
+ retries: options.config.retries,
1505
+ retryDelay: options.config.retryDelay,
1506
+ logger: options.logger,
1507
+ service: request.service,
1508
+ operation: request.operation
1509
+ });
1510
+ options.logger?.debug("Received ARCA SOAP response", {
1511
+ service: request.service,
1512
+ operation: request.operation,
1513
+ durationMs: Date.now() - startedAt
1514
+ });
1515
+ const soapBody = parseSoapBody(responseXml);
1516
+ const [, result] = getSingleBodyEntry(soapBody);
1517
+ return {
1518
+ service: request.service,
1519
+ operation: request.operation,
1520
+ raw: responseXml,
1521
+ result
1522
+ };
1523
+ } catch (error) {
1524
+ if (error instanceof ArcaSoapFaultError) {
1525
+ options.logger?.error("ARCA SOAP fault response", {
1526
+ service: request.service,
1527
+ operation: request.operation,
1528
+ url,
1529
+ faultCode: error.faultCode,
1530
+ error
1531
+ });
1532
+ }
1533
+ throw error;
1534
+ }
1535
+ }
1536
+ };
1537
+ }
1538
+
1539
+ // src/wsaa/index.ts
1540
+ import { createHash } from "crypto";
1541
+ import forge from "node-forge";
1542
+ function createWsaaAuthModule(options) {
1543
+ const cache = /* @__PURE__ */ new Map();
1544
+ const inFlight = /* @__PURE__ */ new Map();
1545
+ return {
1546
+ async login(service, authOptions = {}) {
1547
+ const cacheKey = buildWsaaCacheKey(options.config, service);
1548
+ const running = inFlight.get(cacheKey);
1549
+ if (running) {
1550
+ return running;
1551
+ }
1552
+ const loginPromise = (async () => {
1553
+ if (!authOptions.forceRefresh) {
1554
+ const cached = getCachedCredentials(cache, cacheKey);
1555
+ if (cached) {
1556
+ options.logger?.debug("Attempting WSAA login", {
1557
+ service,
1558
+ source: "cached"
1559
+ });
1560
+ return cached;
1561
+ }
1562
+ }
1563
+ options.logger?.debug("Attempting WSAA login", {
1564
+ service,
1565
+ source: "fresh"
1566
+ });
1567
+ const credentials = await requestCredentials(options.config, service, {
1568
+ logger: options.logger
1569
+ });
1570
+ options.logger?.info("WSAA login succeeded", {
1571
+ service,
1572
+ expiresAt: credentials.expiresAt
1573
+ });
1574
+ cache.set(cacheKey, credentials);
1575
+ return credentials;
1576
+ })();
1577
+ inFlight.set(cacheKey, loginPromise);
1578
+ try {
1579
+ return await loginPromise;
1580
+ } catch (error) {
1581
+ if (!authOptions.forceRefresh && error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
1582
+ const cached = getCachedCredentials(cache, cacheKey);
1583
+ if (cached) {
1584
+ options.logger?.warn(
1585
+ "Recovered WSAA coe.alreadyAuthenticated fault",
1586
+ {
1587
+ service,
1588
+ faultCode: error.faultCode
1589
+ }
1590
+ );
1591
+ return cached;
1592
+ }
1593
+ }
1594
+ if (error instanceof ArcaSoapFaultError) {
1595
+ options.logger?.error("WSAA SOAP fault response", {
1596
+ service,
1597
+ operation: "loginCms",
1598
+ url: ARCA_WSAA_CONFIG.endpoint[options.config.environment],
1599
+ faultCode: error.faultCode,
1600
+ error
1601
+ });
1602
+ }
1603
+ throw error;
1604
+ } finally {
1605
+ inFlight.delete(cacheKey);
1606
+ }
1607
+ }
1608
+ };
1609
+ }
1610
+ async function requestCredentials(config, service, options) {
1611
+ const loginTicketRequestXml = buildLoginTicketRequest(service);
1612
+ const signedCms = signLoginTicketRequest(loginTicketRequestXml, {
1613
+ certificatePem: config.certificatePem,
1614
+ privateKeyPem: config.privateKeyPem
1615
+ });
1616
+ const requestXml = buildSoapEnvelope(
1617
+ ARCA_WSAA_CONFIG.soapVersion,
1618
+ "loginCms",
1619
+ ARCA_WSAA_CONFIG.namespace,
1620
+ { in0: signedCms }
1621
+ );
1622
+ const responseXml = await postXml({
1623
+ url: ARCA_WSAA_CONFIG.endpoint[config.environment],
1624
+ body: requestXml,
1625
+ contentType: 'text/xml; charset="utf-8"',
1626
+ soapAction: ARCA_WSAA_CONFIG.soapActionBase,
1627
+ timeout: config.timeout,
1628
+ retries: config.retries,
1629
+ retryDelay: config.retryDelay,
1630
+ logger: options?.logger,
1631
+ service: "wsaa",
1632
+ operation: "loginCms"
1633
+ });
1634
+ const soapBody = parseSoapBody(responseXml);
1635
+ const [, response] = getSingleBodyEntry(soapBody);
1636
+ const loginCmsReturn = response.loginCmsReturn;
1637
+ if (typeof loginCmsReturn !== "string" || loginCmsReturn.trim().length < 1) {
1638
+ throw new ArcaTransportError(
1639
+ "WSAA response did not include loginCmsReturn XML"
1640
+ );
1641
+ }
1642
+ return parseLoginTicketResponse(loginCmsReturn);
1643
+ }
1644
+ function buildWsaaCacheKey(config, service) {
1645
+ return [config.environment, service, getCertificateFingerprint(config)].join(
1646
+ ":"
1647
+ );
1648
+ }
1649
+ function getCertificateFingerprint(config) {
1650
+ return createHash("sha256").update(config.certificatePem).digest("hex");
1651
+ }
1652
+ function isCredentialValid(credentials) {
1653
+ return new Date(credentials.expiresAt).getTime() - Date.now() > 6e4;
1654
+ }
1655
+ function getCachedCredentials(cache, cacheKey) {
1656
+ const localCached = cache.get(cacheKey);
1657
+ if (localCached && isCredentialValid(localCached)) {
1658
+ return localCached;
1659
+ }
1660
+ return null;
1661
+ }
1662
+ function buildLoginTicketRequest(service) {
1663
+ const uniqueId = Math.floor(Date.now() / 1e3);
1664
+ const generationTime = new Date(Date.now() - 5 * 6e4).toISOString().replace(".000Z", "Z");
1665
+ const expirationTime = new Date(Date.now() + 5 * 6e4).toISOString().replace(".000Z", "Z");
1666
+ return `<?xml version="1.0" encoding="UTF-8"?>
1667
+ <loginTicketRequest version="1.0">
1668
+ <header>
1669
+ <uniqueId>${uniqueId}</uniqueId>
1670
+ <generationTime>${generationTime}</generationTime>
1671
+ <expirationTime>${expirationTime}</expirationTime>
1672
+ </header>
1673
+ <service>${service}</service>
1674
+ </loginTicketRequest>`;
1675
+ }
1676
+ function signLoginTicketRequest(loginTicketRequestXml, options) {
1677
+ const certificate = forge.pki.certificateFromPem(options.certificatePem);
1678
+ const privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem);
1679
+ const signedData = forge.pkcs7.createSignedData();
1680
+ signedData.content = forge.util.createBuffer(loginTicketRequestXml, "utf8");
1681
+ signedData.addCertificate(certificate);
1682
+ const authenticatedAttributes = [
1683
+ {
1684
+ type: String(forge.pki.oids.contentType),
1685
+ value: String(forge.pki.oids.data)
1686
+ },
1687
+ {
1688
+ type: String(forge.pki.oids.messageDigest)
1689
+ },
1690
+ {
1691
+ type: String(forge.pki.oids.signingTime),
1692
+ value: /* @__PURE__ */ new Date()
1693
+ }
1694
+ ];
1695
+ const signerOptions = {
1696
+ key: privateKey,
1697
+ certificate,
1698
+ digestAlgorithm: String(forge.pki.oids.sha1),
1699
+ authenticatedAttributes
1700
+ };
1701
+ signedData.addSigner(signerOptions);
1702
+ signedData.sign();
1703
+ const der = forge.asn1.toDer(signedData.toAsn1()).getBytes();
1704
+ return Buffer.from(der, "binary").toString("base64");
1705
+ }
1706
+ function parseLoginTicketResponse(xml) {
1707
+ const parsed = parseXmlDocument(xml);
1708
+ const response = parsed.loginTicketResponse ?? parsed;
1709
+ const header = response.header;
1710
+ const credentials = response.credentials;
1711
+ const token = credentials?.token;
1712
+ const sign = credentials?.sign;
1713
+ const expiresAt = header?.expirationTime;
1714
+ if (typeof token !== "string" || typeof sign !== "string" || typeof expiresAt !== "string") {
1715
+ throw new ArcaTransportError(
1716
+ "Invalid WSAA login ticket response structure"
1717
+ );
1718
+ }
1719
+ return {
1720
+ token,
1721
+ sign,
1722
+ expiresAt
1723
+ };
1724
+ }
1725
+
1726
+ // src/client.ts
1727
+ function createArcaClient(config) {
1728
+ assertArcaClientConfig(config);
1729
+ const normalizedConfig = normalizeArcaClientConfig(config);
1730
+ const logger = createArcaLogger(normalizedConfig.logger);
1731
+ const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
1732
+ const soap = createSoapTransport({ config: normalizedConfig, logger });
1733
+ return {
1734
+ config: normalizedConfig,
1735
+ wsfe: createWsfeService({ config: normalizedConfig, auth, soap }),
1736
+ wsmtxca: createWsmtxcaService({ config: normalizedConfig, auth, soap }),
1737
+ padron: createPadronService({ config: normalizedConfig, auth, soap })
1738
+ };
1739
+ }
1740
+ export {
1741
+ ARCA_ENVIRONMENTS,
1742
+ ARCA_ENV_VARIABLES,
1743
+ ArcaConfigurationError,
1744
+ ArcaError,
1745
+ ArcaInputError,
1746
+ ArcaServiceError,
1747
+ ArcaSoapFaultError,
1748
+ ArcaTransportError,
1749
+ assertArcaClientConfig,
1750
+ createArcaClient,
1751
+ createArcaClientConfigFromEnv,
1752
+ createPadronService,
1753
+ createWsfeService,
1754
+ createWsmtxcaService,
1755
+ resolveArcaEnvironment
1756
+ };
1757
+ //# sourceMappingURL=index.js.map