facturas 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +95 -786
  2. package/bin/facturas.mjs +4 -0
  3. package/dist/{chunk-RCM4NAAG.mjs → chunk-5KCTRQJX.mjs} +83 -284
  4. package/dist/chunk-5KCTRQJX.mjs.map +1 -0
  5. package/dist/{chunk-XNBD5NVL.mjs → chunk-ALH5IPSY.mjs} +2 -57
  6. package/dist/chunk-ALH5IPSY.mjs.map +1 -0
  7. package/dist/{chunk-76WU5BVI.mjs → chunk-GX5ERXT6.mjs} +1 -1
  8. package/dist/chunk-GX5ERXT6.mjs.map +1 -0
  9. package/dist/chunk-LOWV7IF6.mjs +3758 -0
  10. package/dist/chunk-LOWV7IF6.mjs.map +1 -0
  11. package/dist/chunk-LR4M2AMG.mjs +169 -0
  12. package/dist/chunk-LR4M2AMG.mjs.map +1 -0
  13. package/dist/{chunk-4QVFXT35.mjs → chunk-UDX6HLAU.mjs} +1 -17
  14. package/dist/{chunk-4QVFXT35.mjs.map → chunk-UDX6HLAU.mjs.map} +1 -1
  15. package/dist/cli.d.ts +49 -0
  16. package/dist/cli.mjs +2316 -0
  17. package/dist/cli.mjs.map +1 -0
  18. package/dist/client-BZxeScuK.d.ts +521 -0
  19. package/dist/constants.d.ts +1 -1
  20. package/dist/constants.mjs +1 -1
  21. package/dist/{wsfe-DF0jAE2k.d.ts → decimal-B0HtrMFY.d.ts} +9 -66
  22. package/dist/errors.d.ts +2 -2
  23. package/dist/{fiscal-evidence-DBzakqBE.d.ts → fiscal-evidence-B1Qe6XMg.d.ts} +2 -2
  24. package/dist/{index-CVGQyyYM.d.ts → index-D8LGFvxV.d.ts} +1 -1
  25. package/dist/index.d.ts +11 -235
  26. package/dist/index.mjs +21 -2315
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/padron.d.ts +2 -2
  29. package/dist/{types-BiznC1-X.d.ts → types-r2nf55gh.d.ts} +1 -1
  30. package/dist/types.d.ts +1 -1
  31. package/dist/wsfe.d.ts +45 -4
  32. package/dist/wsfe.mjs +6 -4
  33. package/dist/wsmtxca.d.ts +4 -20
  34. package/dist/wsmtxca.mjs +2 -2
  35. package/package.json +5 -1
  36. package/dist/chunk-76WU5BVI.mjs.map +0 -1
  37. package/dist/chunk-RCM4NAAG.mjs.map +0 -1
  38. package/dist/chunk-XNBD5NVL.mjs.map +0 -1
package/dist/cli.mjs ADDED
@@ -0,0 +1,2316 @@
1
+ import {
2
+ ARCA_ENVIRONMENTS,
3
+ ARCA_WSAA_CONFIG,
4
+ assertArcaClientConfig,
5
+ createArcaClient,
6
+ createFileStore,
7
+ createWsaaAuthModule,
8
+ createWsaaStoreAdapter,
9
+ discoverArcaClientConfig
10
+ } from "./chunk-LOWV7IF6.mjs";
11
+ import "./chunk-7LEKQ4P7.mjs";
12
+ import "./chunk-5KCTRQJX.mjs";
13
+ import "./chunk-GX5ERXT6.mjs";
14
+ import "./chunk-ALH5IPSY.mjs";
15
+ import "./chunk-UDX6HLAU.mjs";
16
+ import {
17
+ ArcaConfigurationError,
18
+ ArcaServiceError,
19
+ ArcaSoapFaultError,
20
+ ArcaTransportError,
21
+ isArcaAuthenticationError,
22
+ toArcaSafeErrorMetadata
23
+ } from "./chunk-ZOSNASXB.mjs";
24
+
25
+ // src/cli/main.ts
26
+ import { tmpdir } from "os";
27
+ import { join as join2 } from "path";
28
+ import { parseArgs } from "util";
29
+
30
+ // src/cli/cert.ts
31
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
32
+ import { isAbsolute, resolve as resolve2 } from "path";
33
+
34
+ // src/cli/csr.ts
35
+ import forge from "node-forge";
36
+ var RSA_MODULUS_BITS = 2048;
37
+ var RSA_PUBLIC_EXPONENT = 65537;
38
+ var SUBJECT_TAX_ID = /^CUIT\s+(\d{11})$/;
39
+ function buildArcaCsrSubject(subject) {
40
+ return [
41
+ { name: "countryName", value: "AR" },
42
+ { name: "organizationName", value: subject.organization },
43
+ { name: "commonName", value: subject.commonName },
44
+ { name: "serialNumber", value: `CUIT ${subject.taxId}` }
45
+ ];
46
+ }
47
+ function createArcaCsrMaterial(subject) {
48
+ const keyPair = forge.pki.rsa.generateKeyPair({
49
+ bits: RSA_MODULUS_BITS,
50
+ e: RSA_PUBLIC_EXPONENT
51
+ });
52
+ const privateKeyPem = toPkcs8Pem(keyPair.privateKey);
53
+ const request = forge.pki.createCertificationRequest();
54
+ request.publicKey = keyPair.publicKey;
55
+ request.setSubject(buildArcaCsrSubject(subject));
56
+ request.sign(keyPair.privateKey, forge.md.sha256.create());
57
+ return {
58
+ privateKeyPem,
59
+ csrPem: forge.pki.certificationRequestToPem(request)
60
+ };
61
+ }
62
+ function toPkcs8Pem(privateKey) {
63
+ return forge.pki.privateKeyInfoToPem(
64
+ forge.pki.wrapRsaPrivateKey(forge.pki.privateKeyToAsn1(privateKey))
65
+ );
66
+ }
67
+ function readCertificateFacts(certificatePem) {
68
+ const certificate = forge.pki.certificateFromPem(certificatePem);
69
+ const taxId = readSubjectTaxId(certificate);
70
+ return {
71
+ notBefore: certificate.validity.notBefore,
72
+ notAfter: certificate.validity.notAfter,
73
+ ...taxId === void 0 ? {} : { taxId }
74
+ };
75
+ }
76
+ function readCsrTaxId(csrPem) {
77
+ return readSubjectTaxId(forge.pki.certificationRequestFromPem(csrPem));
78
+ }
79
+ function readSubjectTaxId(source) {
80
+ const field = source.subject.getField({ name: "serialNumber" });
81
+ if (typeof field?.value !== "string") {
82
+ return void 0;
83
+ }
84
+ return SUBJECT_TAX_ID.exec(field.value.trim())?.[1];
85
+ }
86
+ function privateKeyMatchesCertificate(certificatePem, privateKeyPem) {
87
+ const certificate = forge.pki.certificateFromPem(certificatePem);
88
+ const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
89
+ const certificateKey = certificate.publicKey;
90
+ if (certificateKey?.n === void 0 || certificateKey.e === void 0) {
91
+ return false;
92
+ }
93
+ return certificateKey.n.compareTo(privateKey.n) === 0 && certificateKey.e.compareTo(privateKey.e) === 0;
94
+ }
95
+
96
+ // src/cli/discover.ts
97
+ import { existsSync, readFileSync } from "fs";
98
+ import { resolve } from "path";
99
+ var DISCOVERY_ORDER = [
100
+ "test",
101
+ "production"
102
+ ];
103
+ function toEnvironment(value) {
104
+ return ARCA_ENVIRONMENTS.includes(value) ? value : void 0;
105
+ }
106
+ function certificateFileName(environment) {
107
+ return `arca-${environment}.crt`;
108
+ }
109
+ function privateKeyFileName(environment) {
110
+ return `arca-${environment}.key`;
111
+ }
112
+ function discoverCredentials(directory, environment) {
113
+ const candidates = (environment === void 0 ? DISCOVERY_ORDER : [environment]).map((candidate) => {
114
+ const certificateFile = certificateFileName(candidate);
115
+ const keyFile = privateKeyFileName(candidate);
116
+ return {
117
+ environment: candidate,
118
+ certificateFile,
119
+ keyFile,
120
+ hasCertificate: existsSync(resolve(directory, certificateFile)),
121
+ hasKey: existsSync(resolve(directory, keyFile))
122
+ };
123
+ });
124
+ const complete = candidates.filter(
125
+ (candidate) => candidate.hasCertificate && candidate.hasKey
126
+ );
127
+ if (complete.length > 1) {
128
+ return {
129
+ kind: "ambiguous",
130
+ files: complete.map((candidate) => candidate.certificateFile)
131
+ };
132
+ }
133
+ const [pair] = complete;
134
+ if (pair !== void 0) {
135
+ return {
136
+ kind: "found",
137
+ credentials: {
138
+ environment: pair.environment,
139
+ certificateFile: pair.certificateFile,
140
+ keyFile: pair.keyFile,
141
+ certificatePem: read(directory, pair.certificateFile),
142
+ privateKeyPem: read(directory, pair.keyFile)
143
+ }
144
+ };
145
+ }
146
+ const half = candidates.find(
147
+ (candidate) => candidate.hasCertificate || candidate.hasKey
148
+ );
149
+ if (half === void 0) {
150
+ return { kind: "none" };
151
+ }
152
+ return half.hasCertificate ? {
153
+ kind: "incomplete",
154
+ present: half.certificateFile,
155
+ missing: half.keyFile,
156
+ missingKind: "key"
157
+ } : {
158
+ kind: "incomplete",
159
+ present: half.keyFile,
160
+ missing: half.certificateFile,
161
+ missingKind: "certificate"
162
+ };
163
+ }
164
+ function read(directory, file) {
165
+ return readFileSync(resolve(directory, file), "utf8").trim();
166
+ }
167
+
168
+ // src/cli/output.ts
169
+ var CLI_EXIT = {
170
+ ok: 0,
171
+ failed: 1,
172
+ usage: 2
173
+ };
174
+ var GREEN = "\x1B[32m";
175
+ var RED = "\x1B[31m";
176
+ var YELLOW = "\x1B[33m";
177
+ var CYAN = "\x1B[36m";
178
+ var BOLD = "\x1B[1m";
179
+ var DIM = "\x1B[2m";
180
+ var RESET = "\x1B[0m";
181
+ var LABEL_WIDTH = 22;
182
+ var INDENT = " ";
183
+ function createPainter(color) {
184
+ const paint = (code) => (text2) => color ? `${code}${text2}${RESET}` : text2;
185
+ return {
186
+ green: paint(GREEN),
187
+ red: paint(RED),
188
+ yellow: paint(YELLOW),
189
+ cyan: paint(CYAN),
190
+ bold: paint(BOLD),
191
+ dim: paint(DIM)
192
+ };
193
+ }
194
+ function shouldUseColor(stream, env, noColorFlag = false) {
195
+ if (noColorFlag || env.NO_COLOR !== void 0) {
196
+ return false;
197
+ }
198
+ if (env.FORCE_COLOR !== void 0) {
199
+ return env.FORCE_COLOR !== "0";
200
+ }
201
+ return stream.isTTY === true;
202
+ }
203
+ function createWriter(stream, options) {
204
+ const painter = createPainter(options.color);
205
+ const mark = (symbol, label, detail) => {
206
+ const body = detail === void 0 ? label : `${label.padEnd(LABEL_WIDTH)} ${detail}`;
207
+ stream.write(`${symbol} ${body}
208
+ `);
209
+ };
210
+ return {
211
+ painter,
212
+ ok(label, detail) {
213
+ mark(painter.green("\u2713"), label, detail);
214
+ },
215
+ fail(label, detail) {
216
+ mark(painter.red("\u2717"), label, detail);
217
+ },
218
+ warn(label, detail) {
219
+ mark(painter.yellow("!"), label, detail);
220
+ },
221
+ note(text2) {
222
+ stream.write(`${INDENT}${text2}
223
+ `);
224
+ },
225
+ line(text2) {
226
+ stream.write(`${text2}
227
+ `);
228
+ },
229
+ dim(text2) {
230
+ stream.write(`${painter.dim(text2)}
231
+ `);
232
+ },
233
+ command(text2) {
234
+ stream.write(`${INDENT}${painter.dim("$")} ${painter.cyan(text2)}
235
+ `);
236
+ },
237
+ blank() {
238
+ stream.write("\n");
239
+ },
240
+ json(value) {
241
+ stream.write(`${JSON.stringify(value, null, 2)}
242
+ `);
243
+ }
244
+ };
245
+ }
246
+
247
+ // src/cli/prompt.ts
248
+ import { createInterface } from "readline/promises";
249
+ function isInteractive(io) {
250
+ return io.stdin.isTTY === true;
251
+ }
252
+ async function ask(io, question) {
253
+ const rl = createInterface({
254
+ input: io.stdin,
255
+ output: io.stdout
256
+ });
257
+ try {
258
+ const answer = await rl.question(question);
259
+ return answer.trim();
260
+ } finally {
261
+ rl.close();
262
+ }
263
+ }
264
+ async function askBlock(io, prompt, endsAt) {
265
+ io.stdout.write(prompt);
266
+ const rl = createInterface({
267
+ input: io.stdin,
268
+ output: io.stdout
269
+ });
270
+ const lines = [];
271
+ let complete = false;
272
+ rl.on("SIGINT", () => rl.close());
273
+ try {
274
+ for await (const raw of rl) {
275
+ const line = raw.trim();
276
+ if (lines.length === 0 && line === "") {
277
+ continue;
278
+ }
279
+ lines.push(line);
280
+ if (endsAt(line)) {
281
+ complete = true;
282
+ break;
283
+ }
284
+ }
285
+ } finally {
286
+ rl.close();
287
+ }
288
+ return complete ? lines : void 0;
289
+ }
290
+
291
+ // src/cli/cert.ts
292
+ var BEGIN = "-----BEGIN CERTIFICATE-----";
293
+ var END = "-----END CERTIFICATE-----";
294
+ var PROMPT = "> ";
295
+ var PASTE_ATTEMPTS = 3;
296
+ var NOT_A_CERTIFICATE = [
297
+ "Eso no parece un certificado PEM. Tiene que ir de",
298
+ `${BEGIN} a ${END}.`
299
+ ];
300
+ async function pasteCertificate(io, writer, request) {
301
+ const interactive = isInteractive(io);
302
+ if (request.intro.length > 0) {
303
+ writer.blank();
304
+ for (const line of request.intro) {
305
+ writer.line(line);
306
+ }
307
+ writer.blank();
308
+ }
309
+ for (let attempt = 1; attempt <= PASTE_ATTEMPTS; attempt += 1) {
310
+ const lines = await askBlock(
311
+ io,
312
+ interactive ? PROMPT : "",
313
+ (line) => line === END
314
+ );
315
+ if (lines === void 0) {
316
+ return "cancelled";
317
+ }
318
+ const pem = toCertificatePem(lines);
319
+ const facts = readFacts(pem);
320
+ if (facts === void 0) {
321
+ for (const line of NOT_A_CERTIFICATE) {
322
+ io.stderr.write(`${line}
323
+ `);
324
+ }
325
+ if (interactive && attempt < PASTE_ATTEMPTS) {
326
+ writer.blank();
327
+ }
328
+ continue;
329
+ }
330
+ const refusal = refuse(pem, facts, request);
331
+ if (refusal !== void 0) {
332
+ io.stderr.write(`${refusal}
333
+ `);
334
+ return "refused";
335
+ }
336
+ writeFileSync(request.certificatePath, pem);
337
+ writer.ok(
338
+ request.certificateName,
339
+ `certificado guardado, vence ${isoDate(facts.notAfter)}`
340
+ );
341
+ return "saved";
342
+ }
343
+ return "exhausted";
344
+ }
345
+ function writeManualHint(writer, certificateName) {
346
+ writer.blank();
347
+ writer.line(
348
+ `Cuando tengas el certificado, guardalo ac\xE1 como ${certificateName},`
349
+ );
350
+ writer.line("o pegalo con npx facturas cert, y corr\xE9:");
351
+ writer.blank();
352
+ writer.command("npx facturas check");
353
+ }
354
+ async function runCert(io, flags, writer) {
355
+ const directory = isAbsolute(flags.dir ?? "") ? flags.dir : resolve2(io.cwd, flags.dir ?? ".");
356
+ const asked = flags.env?.trim().toLowerCase();
357
+ const environment = asked === void 0 || asked === "" ? void 0 : toEnvironment(asked);
358
+ if (asked !== void 0 && asked !== "" && environment === void 0) {
359
+ io.stderr.write("Falta el entorno. Pas\xE1 --env test o --env production.\n");
360
+ return CLI_EXIT.usage;
361
+ }
362
+ const found = findKeys(directory, environment);
363
+ if (found.length > 1) {
364
+ io.stderr.write(
365
+ `Est\xE1n ${found.map((candidate) => candidate.keyName).join(" y ")} en este directorio
366
+ `
367
+ );
368
+ io.stderr.write(
369
+ "y no s\xE9 cu\xE1l quer\xE9s. Eleg\xED con --env test o --env production.\n"
370
+ );
371
+ return CLI_EXIT.failed;
372
+ }
373
+ const [target] = found;
374
+ if (target === void 0) {
375
+ const names = (environment === void 0 ? DISCOVERY_ORDER : [environment]).map((candidate) => privateKeyFileName(candidate)).join(" ni ");
376
+ io.stderr.write(`No encontr\xE9 ${names} en este directorio.
377
+ `);
378
+ io.stderr.write("Gener\xE1 la clave y el CSR con npx facturas init.\n");
379
+ return CLI_EXIT.failed;
380
+ }
381
+ const certificatePath = resolve2(directory, target.certificateName);
382
+ if (existsSync2(certificatePath) && flags.force !== true) {
383
+ io.stderr.write(
384
+ `Ya existe ${target.certificateName}. Us\xE1 --force para sobrescribir.
385
+ `
386
+ );
387
+ return CLI_EXIT.failed;
388
+ }
389
+ const taxId = readCsrTaxIdOf(directory, target.environment);
390
+ const outcome = await pasteCertificate(io, writer, {
391
+ intro: isInteractive(io) ? [
392
+ "Peg\xE1 el certificado que te dio ARCA. Termina solo al ver",
393
+ `${END}. Ctrl-C para salir.`
394
+ ] : [],
395
+ certificatePath,
396
+ certificateName: target.certificateName,
397
+ keyName: target.keyName,
398
+ privateKeyPem: readFileSync2(resolve2(directory, target.keyName), "utf8"),
399
+ ...taxId === void 0 ? {} : { taxId }
400
+ });
401
+ if (outcome === "saved") {
402
+ writer.blank();
403
+ writer.dim("Despu\xE9s:");
404
+ writer.blank();
405
+ writer.command("npx facturas check");
406
+ return CLI_EXIT.ok;
407
+ }
408
+ if (outcome === "cancelled") {
409
+ writer.blank();
410
+ writer.line("Cuando lo tengas, volv\xE9 a correr npx facturas cert, o");
411
+ writer.line(`guardalo ac\xE1 como ${target.certificateName}.`);
412
+ return CLI_EXIT.ok;
413
+ }
414
+ if (outcome === "exhausted") {
415
+ writeManualHint(writer, target.certificateName);
416
+ }
417
+ return CLI_EXIT.failed;
418
+ }
419
+ function findKeys(directory, environment) {
420
+ return (environment === void 0 ? DISCOVERY_ORDER : [environment]).map((candidate) => ({
421
+ environment: candidate,
422
+ keyName: privateKeyFileName(candidate),
423
+ certificateName: certificateFileName(candidate)
424
+ })).filter((candidate) => existsSync2(resolve2(directory, candidate.keyName)));
425
+ }
426
+ function readCsrTaxIdOf(directory, environment) {
427
+ const path = resolve2(directory, `arca-${environment}.csr`);
428
+ if (!existsSync2(path)) {
429
+ return void 0;
430
+ }
431
+ try {
432
+ return readCsrTaxId(readFileSync2(path, "utf8"));
433
+ } catch {
434
+ return void 0;
435
+ }
436
+ }
437
+ function toCertificatePem(lines) {
438
+ const start = lines.indexOf(BEGIN);
439
+ const body = start === -1 ? lines : lines.slice(start);
440
+ return `${body.join("\n")}
441
+ `;
442
+ }
443
+ function readFacts(pem) {
444
+ try {
445
+ return readCertificateFacts(pem);
446
+ } catch {
447
+ return void 0;
448
+ }
449
+ }
450
+ function refuse(pem, facts, request) {
451
+ if (request.taxId !== void 0 && facts.taxId !== void 0 && facts.taxId !== request.taxId) {
452
+ return `El certificado es de otro CUIT: ${facts.taxId}.`;
453
+ }
454
+ let matches;
455
+ try {
456
+ matches = privateKeyMatchesCertificate(pem, request.privateKeyPem);
457
+ } catch {
458
+ return `No pude leer ${request.keyName} como clave PEM.`;
459
+ }
460
+ return matches ? void 0 : `El certificado no corresponde a ${request.keyName}. \xBFSubiste otro CSR?`;
461
+ }
462
+ function isoDate(value) {
463
+ return value.toISOString().slice(0, 10);
464
+ }
465
+
466
+ // src/cli/check.ts
467
+ import { readFileSync as readFileSync3 } from "fs";
468
+ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
469
+
470
+ // src/cli/cuit.ts
471
+ var CHECK_DIGIT_WEIGHTS = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
472
+ var TAX_ID_LENGTH = 11;
473
+ var MODULUS = 11;
474
+ var SEPARATORS = /[\s.-]/g;
475
+ var TAX_ID_FIX = "Son 11 d\xEDgitos y el \xFAltimo es el verificador; pod\xE9s escribirlo con guiones.";
476
+ function normalizeTaxId(raw) {
477
+ return raw.trim().replace(SEPARATORS, "");
478
+ }
479
+ function describeTaxIdProblem(raw) {
480
+ const digits = normalizeTaxId(raw);
481
+ if (!/^\d*$/.test(digits)) {
482
+ return `CUIT inv\xE1lido: ${raw.trim()} tiene caracteres que no son d\xEDgitos.`;
483
+ }
484
+ if (digits.length !== TAX_ID_LENGTH) {
485
+ const plural = digits.length === 1 ? "d\xEDgito" : "d\xEDgitos";
486
+ return `CUIT inv\xE1lido: ${digits} tiene ${digits.length} ${plural} y necesita ${TAX_ID_LENGTH}.`;
487
+ }
488
+ if (!hasValidCheckDigit(digits)) {
489
+ return `CUIT inv\xE1lido: ${digits} no pasa el d\xEDgito verificador.`;
490
+ }
491
+ return void 0;
492
+ }
493
+ function hasValidCheckDigit(digits) {
494
+ const sum = CHECK_DIGIT_WEIGHTS.reduce(
495
+ (total, weight, index) => total + weight * Number(digits[index]),
496
+ 0
497
+ );
498
+ const remainder = sum % MODULUS;
499
+ if (remainder === 1) {
500
+ return false;
501
+ }
502
+ const expected = remainder === 0 ? 0 : MODULUS - remainder;
503
+ return expected === Number(digits[TAX_ID_LENGTH - 1]);
504
+ }
505
+
506
+ // src/cli/arca-steps.ts
507
+ var ARCA_PAGES = {
508
+ login: "https://auth.afip.gob.ar/contribuyente_/login.xhtml",
509
+ wsassService: "WSASS - Autogesti\xF3n Certificados Homologaci\xF3n",
510
+ wsassNewCertificate: "Nuevo Certificado",
511
+ wsassNewCertificateButton: "Crear DN y Obtener Certificado",
512
+ wsassAuthorizeService: "Crear autorizaci\xF3n a servicio",
513
+ wsassAuthorizeButton: "Crear Autorizaci\xF3n de Acceso",
514
+ wsassDnField: "Nombre simb\xF3lico del DN",
515
+ wsassCsrField: "Solicitud de certificado",
516
+ wsassAuthorizeDnField: "Nombre simb\xF3lico del DN a autorizar",
517
+ wsassRepresentedField: "CUIT representado",
518
+ wsassServiceField: "Servicio al que desea acceder",
519
+ wsfeService: "wsfe - Facturaci\xF3n Electr\xF3nica",
520
+ certificates: "Administraci\xF3n de Certificados Digitales",
521
+ certificatesAddAlias: "Agregar alias",
522
+ relationships: "Administrador de Relaciones",
523
+ relationshipsNew: "Nueva Relaci\xF3n",
524
+ relationshipsAdhere: "Adherir Servicio",
525
+ relationshipsPath: "Nueva Relaci\xF3n \u2192 Webservices \u2192 Facturaci\xF3n Electr\xF3nica",
526
+ salesPoints: "Administraci\xF3n de Puntos de Venta y Domicilios",
527
+ salesPointsSystem: "RECE para aplicativo y Web Services",
528
+ salesPointsSystemMonotributo: "Factura Electr\xF3nica \u2013 Monotributo \u2013 Web Services"
529
+ };
530
+ var STEP_INDENT = " ";
531
+ var WRAP_INDENT = " ";
532
+ var FIELD_INDENT = " ";
533
+ var COLUMN_GAP = 3;
534
+ function buildArcaPlan(environment, context, painter) {
535
+ return environment === "test" ? buildTestPlan(context, painter) : buildProductionPlan(context, painter);
536
+ }
537
+ function buildTestPlan(context, painter) {
538
+ const name = (text2) => painter.bold(`"${text2}"`);
539
+ return {
540
+ heading: "Listo. Ahora en ARCA, para homologaci\xF3n:",
541
+ lines: [
542
+ ...step(1, ["Entr\xE1 con clave fiscal en", ARCA_PAGES.login]),
543
+ ...step(2, [
544
+ `Abr\xED ${name(ARCA_PAGES.wsassService)} en Mis Servicios.`,
545
+ `Si no est\xE1, agregalo en ${ARCA_PAGES.relationships} \u2192 ${ARCA_PAGES.relationshipsAdhere}`,
546
+ "\u2192 ARCA \u2192 Servicios Interactivos \u2192 WSASS, y volv\xE9 a entrar. Va con tu",
547
+ "clave fiscal de persona f\xEDsica, nivel 2 o superior: no es delegable."
548
+ ]),
549
+ ...step(
550
+ 3,
551
+ [`En el men\xFA, ${name(ARCA_PAGES.wsassNewCertificate)}:`],
552
+ [
553
+ { label: ARCA_PAGES.wsassDnField, value: context.alias },
554
+ csrField(context)
555
+ ],
556
+ [`Apret\xE1 ${name(ARCA_PAGES.wsassNewCertificateButton)}.`]
557
+ ),
558
+ ...step(4, [
559
+ "El certificado sale en el cuadro de resultado, de",
560
+ "-----BEGIN CERTIFICATE----- a -----END CERTIFICATE-----.",
561
+ context.paste ? "Copialo entero y pegalo ac\xE1 abajo." : `Copialo entero y guardalo ac\xE1 como ${context.certificateName}.`
562
+ ]),
563
+ ...step(
564
+ 5,
565
+ [`En el men\xFA, ${name(ARCA_PAGES.wsassAuthorizeService)}:`],
566
+ [
567
+ { label: ARCA_PAGES.wsassAuthorizeDnField, value: context.alias },
568
+ { label: ARCA_PAGES.wsassRepresentedField, value: context.taxId },
569
+ {
570
+ label: ARCA_PAGES.wsassServiceField,
571
+ value: ARCA_PAGES.wsfeService
572
+ }
573
+ ],
574
+ [`Apret\xE1 ${name(ARCA_PAGES.wsassAuthorizeButton)}.`]
575
+ )
576
+ ]
577
+ };
578
+ }
579
+ function buildProductionPlan(context, painter) {
580
+ const name = (text2) => painter.bold(`"${text2}"`);
581
+ return {
582
+ heading: "Listo. Ahora en ARCA, para producci\xF3n:",
583
+ lines: [
584
+ ...step(1, ["Entr\xE1 con clave fiscal en", ARCA_PAGES.login]),
585
+ ...step(2, [
586
+ `Abr\xED ${name(ARCA_PAGES.certificates)} en Mis Servicios.`,
587
+ `Si no est\xE1, agregalo en ${ARCA_PAGES.relationships} \u2192 ${ARCA_PAGES.relationshipsNew}`,
588
+ "\u2192 BUSCAR \u2192 Servicios Interactivos \u2192 Administraci\xF3n de Certificados",
589
+ "Digitales \u2192 Confirmar, y volv\xE9 a entrar."
590
+ ]),
591
+ ...step(
592
+ 3,
593
+ [`Apret\xE1 ${name(ARCA_PAGES.certificatesAddAlias)}:`],
594
+ [
595
+ { label: "Alias", value: context.alias },
596
+ { label: "Seleccionar archivo", value: context.csrName }
597
+ ],
598
+ [`Apret\xE1 ${name(ARCA_PAGES.certificatesAddAlias)} para subirlo.`]
599
+ ),
600
+ ...step(4, [
601
+ `En la lista, entr\xE1 con ${name("Ver")} y us\xE1 el icono ${name("Descargar")}`,
602
+ "para bajar el certificado (archivo CRT).",
603
+ context.paste ? `Guardalo ac\xE1 como ${context.certificateName}, o abrilo y pegalo abajo.` : `Guardalo ac\xE1 como ${context.certificateName}.`
604
+ ]),
605
+ ...step(
606
+ 5,
607
+ [
608
+ `Volv\xE9 a ${ARCA_PAGES.relationships}, ${name(ARCA_PAGES.relationshipsNew)}:`
609
+ ],
610
+ [
611
+ {
612
+ label: "Servicio",
613
+ value: "BUSCAR \u2192 Webservices \u2192 Facturaci\xF3n Electr\xF3nica"
614
+ },
615
+ {
616
+ label: "Representante",
617
+ value: `BUSCAR \u2192 el computador fiscal ${context.alias}`
618
+ }
619
+ ],
620
+ [
621
+ `Apret\xE1 ${name("Confirmar")}, revis\xE1 y volv\xE9 a apretar ${name("Confirmar")}.`
622
+ ]
623
+ )
624
+ ]
625
+ };
626
+ }
627
+ function step(number, text2, fields = [], after = []) {
628
+ const [first, ...rest] = text2;
629
+ const lines = [{ text: `${STEP_INDENT}${number}. ${first}` }];
630
+ for (const line of rest) {
631
+ lines.push({ text: `${WRAP_INDENT}${line}` });
632
+ }
633
+ const width = columnWidth(fields);
634
+ for (const field of fields) {
635
+ const padding = " ".repeat(width - field.label.length);
636
+ lines.push({
637
+ text: `${FIELD_INDENT}${field.label}:${padding}${field.value}`
638
+ });
639
+ for (const line of field.lines ?? []) {
640
+ lines.push({ text: `${FIELD_INDENT}${line}` });
641
+ }
642
+ }
643
+ for (const line of after) {
644
+ lines.push({ text: `${WRAP_INDENT}${line}` });
645
+ }
646
+ return lines;
647
+ }
648
+ function csrField(context) {
649
+ if (context.clipboard) {
650
+ return {
651
+ label: ARCA_PAGES.wsassCsrField,
652
+ value: "peg\xE1 (ya est\xE1 en tu portapapeles)"
653
+ };
654
+ }
655
+ return {
656
+ label: ARCA_PAGES.wsassCsrField,
657
+ value: "copi\xE1 esto entero:",
658
+ lines: context.csrPem.split("\n").map((line) => line.trim()).filter((line) => line !== "")
659
+ };
660
+ }
661
+ function columnWidth(fields) {
662
+ const widest = fields.reduce(
663
+ (longest, field) => Math.max(longest, field.label.length),
664
+ 0
665
+ );
666
+ return widest + COLUMN_GAP;
667
+ }
668
+
669
+ // src/cli/diagnose.ts
670
+ var CLI_DIAGNOSES = {
671
+ "config.taxId": {
672
+ diagnosis: "Falta el CUIT.",
673
+ fix: "export ARCA_TAX_ID=20123456786"
674
+ },
675
+ "config.taxIdUnknown": {
676
+ diagnosis: "El certificado no dice de qu\xE9 CUIT es.",
677
+ fix: "Pas\xE1 --tax-id 20123456786 o defin\xED ARCA_TAX_ID."
678
+ },
679
+ "config.environment": {
680
+ diagnosis: "Falta el entorno.",
681
+ fix: "export ARCA_ENVIRONMENT=test"
682
+ },
683
+ "config.pem": {
684
+ diagnosis: "Falta el certificado o la clave.",
685
+ fix: "Guard\xE1 arca-<entorno>.crt y arca-<entorno>.key ac\xE1, o pas\xE1 --cert y --key, o defin\xED las variables ARCA_*_PEM."
686
+ },
687
+ "config.files.ambiguous": {
688
+ diagnosis: "Est\xE1n {files} en este directorio y no s\xE9 cu\xE1l quer\xE9s.",
689
+ fix: "Eleg\xED con --env test o --env production."
690
+ },
691
+ "config.files.missingCertificate": {
692
+ diagnosis: "Est\xE1 {file} pero falta {missingFile}.",
693
+ fix: "Descarg\xE1 el certificado de ARCA y guardalo ac\xE1 como {missingFile}."
694
+ },
695
+ "config.files.missingKey": {
696
+ diagnosis: "Est\xE1 {file} pero falta {missingFile}.",
697
+ fix: "Pon\xE9 ac\xE1 la clave con la que generaste el CSR, o pas\xE1 --key."
698
+ },
699
+ "cert.invalid": {
700
+ diagnosis: "El archivo no es un PEM v\xE1lido.",
701
+ fix: "Revis\xE1 que copiaste el bloque completo, con BEGIN y END."
702
+ },
703
+ "cert.taxIdMismatch": {
704
+ diagnosis: "El certificado es del CUIT {certificateTaxId} y el configurado es {taxId}.",
705
+ fix: "Us\xE1 el certificado de ese CUIT, o correg\xED --tax-id o ARCA_TAX_ID."
706
+ },
707
+ "cert.mismatch": {
708
+ diagnosis: "La clave privada no corresponde a este certificado.",
709
+ fix: "Us\xE1 la clave con la que generaste el CSR (arca-<entorno>.key)."
710
+ },
711
+ "cert.expired": {
712
+ diagnosis: "El certificado venci\xF3 el {date}.",
713
+ fix: "Gener\xE1 un CSR nuevo con npx facturas init y renovalo en ARCA."
714
+ },
715
+ "wsaa.certExpired": {
716
+ diagnosis: "El certificado venci\xF3.",
717
+ fix: "Gener\xE1 un CSR nuevo con npx facturas init y renovalo en ARCA."
718
+ },
719
+ "wsaa.certUntrusted": {
720
+ diagnosis: "ARCA no reconoce este certificado en este entorno.",
721
+ fix: "Homologaci\xF3n y producci\xF3n tienen certificados propios; revis\xE1 ARCA_ENVIRONMENT."
722
+ },
723
+ "wsaa.signInvalid": {
724
+ diagnosis: "La firma del pedido no es v\xE1lida.",
725
+ fix: "La clave no corresponde al certificado, o el PEM est\xE1 truncado."
726
+ },
727
+ "wsaa.notAuthorized": {
728
+ diagnosis: "El certificado no est\xE1 autorizado para wsfe.",
729
+ fix: `${ARCA_PAGES.wsassService} \u2192 ${ARCA_PAGES.wsassAuthorizeService} (homologaci\xF3n) / ${ARCA_PAGES.relationships} (producci\xF3n).`
730
+ },
731
+ "wsaa.alreadyAuthenticated": {
732
+ diagnosis: "Ya hay un ticket vigente para este certificado.",
733
+ fix: "Otro proceso o m\xE1quina tiene el ticket vigente. Esper\xE1 hasta 12 horas, o corr\xE9 check desde donde lo pediste."
734
+ },
735
+ "wsaa.clockSkew": {
736
+ diagnosis: "La hora de tu m\xE1quina difiere de la de ARCA.",
737
+ fix: "Sincroniz\xE1 el reloj (NTP) y volv\xE9 a probar."
738
+ },
739
+ "wsaa.transport": {
740
+ diagnosis: "No se pudo conectar con {host}.",
741
+ fix: "Revis\xE1 red, proxy o firewall; ARCA homologaci\xF3n suele caerse los fines de semana."
742
+ },
743
+ "wsfe.missing_relationship": {
744
+ diagnosis: "El certificado no tiene la relaci\xF3n con Facturaci\xF3n Electr\xF3nica.",
745
+ fix: `${ARCA_PAGES.relationships} \u2192 ${ARCA_PAGES.relationshipsPath}.`
746
+ },
747
+ "wsfe.unauthorized_computer": {
748
+ diagnosis: "El certificado o computador no est\xE1 autorizado.",
749
+ fix: "Verific\xE1 que el alias est\xE9 asociado al servicio en este entorno."
750
+ },
751
+ "wsfe.invalid_token": {
752
+ diagnosis: "El ticket fue rechazado.",
753
+ fix: "Volv\xE9 a ejecutar check; si persiste, revis\xE1 el reloj."
754
+ },
755
+ "wsfe.authentication_rejected": {
756
+ diagnosis: "ARCA deneg\xF3 el acceso al servicio.",
757
+ fix: "Revis\xE1 entorno y relaci\xF3n del certificado."
758
+ },
759
+ "wsfe.serviceError": {
760
+ diagnosis: "ARCA respondi\xF3 con un error: {message}."
761
+ },
762
+ "salesPoint.missing": {
763
+ diagnosis: "El punto de venta {salesPoint} no est\xE1 habilitado para web services.",
764
+ fix: `ARCA \u2192 ${ARCA_PAGES.salesPoints} \u2192 Nuevo \u2192 el sistema de web services de tu condici\xF3n ("${ARCA_PAGES.salesPointsSystem}" para responsable inscripto).`
765
+ },
766
+ "salesPoint.none": {
767
+ diagnosis: "ARCA no informa ning\xFAn punto de venta para web services.",
768
+ fix: `ARCA \u2192 ${ARCA_PAGES.salesPoints} \u2192 Nuevo \u2192 "${ARCA_PAGES.salesPointsSystem}" para responsable inscripto, "${ARCA_PAGES.salesPointsSystemMonotributo}" para monotributo.`
769
+ },
770
+ "salesPoint.blocked": {
771
+ diagnosis: "El punto de venta {salesPoint} est\xE1 bloqueado.",
772
+ fix: "Revisalo en ARCA."
773
+ }
774
+ };
775
+ function diagnose(key, values = {}) {
776
+ const row = CLI_DIAGNOSES[key];
777
+ return {
778
+ diagnosis: fill(row.diagnosis, values),
779
+ ...row.fix === void 0 ? {} : { fix: fill(row.fix, values) }
780
+ };
781
+ }
782
+ function diagnoseAuthenticationReason(reason) {
783
+ return diagnose(`wsfe.${reason}`);
784
+ }
785
+ var WSAA_FAULT_ROWS = {
786
+ "cms.cert.expired": "wsaa.certExpired",
787
+ "cms.cert.untrusted": "wsaa.certUntrusted",
788
+ "cms.cert.invalid": "wsaa.certUntrusted",
789
+ "cms.cert.notfound": "wsaa.certUntrusted",
790
+ "cms.bad": "wsaa.signInvalid",
791
+ "cms.sign.invalid": "wsaa.signInvalid",
792
+ "coe.notauthorized": "wsaa.notAuthorized",
793
+ "coe.alreadyauthenticated": "wsaa.alreadyAuthenticated",
794
+ "xml.generationtime.invalid": "wsaa.clockSkew",
795
+ "xml.expirationtime.invalid": "wsaa.clockSkew",
796
+ "xml.expirationtime.expired": "wsaa.clockSkew"
797
+ };
798
+ function diagnoseWsaaFaultCode(faultCode) {
799
+ if (faultCode === void 0) {
800
+ return void 0;
801
+ }
802
+ const bare = faultCode.slice(faultCode.indexOf(":") + 1).toLowerCase();
803
+ const exact = WSAA_FAULT_ROWS[bare];
804
+ if (exact) {
805
+ return exact;
806
+ }
807
+ return bare.startsWith("xml.expirationtime.") ? "wsaa.clockSkew" : void 0;
808
+ }
809
+ function findSoapFault(error) {
810
+ if (error instanceof ArcaSoapFaultError) {
811
+ return error;
812
+ }
813
+ const cause = error instanceof Error ? error.cause : void 0;
814
+ return cause instanceof ArcaSoapFaultError ? cause : void 0;
815
+ }
816
+ function findTransportError(error) {
817
+ if (error instanceof ArcaTransportError) {
818
+ return error;
819
+ }
820
+ const cause = error instanceof Error ? error.cause : void 0;
821
+ return cause instanceof ArcaTransportError ? cause : void 0;
822
+ }
823
+ function describeUnknownError(error) {
824
+ const safe = toArcaSafeErrorMetadata(error);
825
+ return {
826
+ diagnosis: safe.message,
827
+ ...safe.code === void 0 ? {} : { code: safe.code }
828
+ };
829
+ }
830
+ function fill(text2, values) {
831
+ return text2.replace(
832
+ /\{(certificateTaxId|date|file|files|host|message|missingFile|salesPoint|taxId)\}/g,
833
+ (match, key) => {
834
+ const value = values[key];
835
+ return value === void 0 ? match : String(value);
836
+ }
837
+ );
838
+ }
839
+
840
+ // src/cli/check.ts
841
+ var EXPIRY_WARNING_DAYS = 30;
842
+ var EMPTY_IN_TEST_WARNING = "en homologaci\xF3n ARCA suele no informarlos; prob\xE1 igual con issue";
843
+ var MILLISECONDS_PER_DAY = 864e5;
844
+ var LAYER_LABELS = {
845
+ config: "configuraci\xF3n",
846
+ certificate: "certificado y clave",
847
+ wsaa: "WSAA",
848
+ wsfe: "WSFE",
849
+ salesPoints: "puntos de venta"
850
+ };
851
+ var SALES_POINT_MIN = 1;
852
+ var SALES_POINT_MAX = 99999;
853
+ function parseSalesPoint(value) {
854
+ if (!/^\d+$/.test(value)) {
855
+ return void 0;
856
+ }
857
+ const parsed = Number(value);
858
+ return parsed >= SALES_POINT_MIN && parsed <= SALES_POINT_MAX ? parsed : void 0;
859
+ }
860
+ function describeSalesPointProblem(value) {
861
+ const reason = /^\d+$/.test(value) ? `Tiene que estar entre ${SALES_POINT_MIN} y ${SALES_POINT_MAX}` : "Tiene que ser un n\xFAmero entero";
862
+ return `Punto de venta inv\xE1lido: ${value}. ${reason}, por ejemplo --sales-point 3.`;
863
+ }
864
+ async function runCheck(io, flags, writer, json) {
865
+ const { report } = await runCheckLayers(io, flags);
866
+ if (json) {
867
+ writer.json(report);
868
+ } else {
869
+ writeCheckReport(writer, report);
870
+ }
871
+ return report.ok ? CLI_EXIT.ok : CLI_EXIT.failed;
872
+ }
873
+ async function runCheckLayers(io, flags) {
874
+ const layers = [];
875
+ const resolved = resolveConfigLayer(io, flags);
876
+ layers.push(resolved.layer);
877
+ if (!(resolved.layer.ok && resolved.config)) {
878
+ return { report: { ok: false, layers } };
879
+ }
880
+ const config = resolved.config;
881
+ const report = {
882
+ ok: false,
883
+ environment: config.environment,
884
+ taxId: config.taxId,
885
+ layers
886
+ };
887
+ const certificate = checkCertificate(config, io.now());
888
+ layers.push(certificate);
889
+ if (!certificate.ok) {
890
+ return { report };
891
+ }
892
+ const cache = flags.noCache === true ? void 0 : createTicketCache(io.cacheDir);
893
+ const wsaa = await checkWsaa(io, config, cache);
894
+ layers.push(wsaa.layer);
895
+ if (!(wsaa.layer.ok && wsaa.credentials)) {
896
+ return { report };
897
+ }
898
+ const client = io.createClient({
899
+ ...config,
900
+ wsaaSessionStore: cache?.sessionStore ?? createEphemeralSessionStore(wsaa.credentials)
901
+ });
902
+ const wsfe = await checkWsfe(client);
903
+ layers.push(wsfe.layer);
904
+ if (!wsfe.layer.ok) {
905
+ return { report, client };
906
+ }
907
+ const salesPoints = wsfe.salesPoints ?? [];
908
+ report.salesPoints = salesPoints.map(toSalesPointReport);
909
+ const layer = checkSalesPoints(
910
+ report.salesPoints,
911
+ config.environment,
912
+ flags.salesPoint
913
+ );
914
+ layers.push(layer);
915
+ report.ok = layer.ok;
916
+ return { report, client };
917
+ }
918
+ function writeCheckReport(writer, report) {
919
+ for (const layer of report.layers) {
920
+ const label = LAYER_LABELS[layer.name];
921
+ if (layer.ok) {
922
+ writer.ok(label, layer.detail);
923
+ } else {
924
+ writer.fail(label);
925
+ if (layer.diagnosis !== void 0) {
926
+ writer.note(layer.diagnosis);
927
+ }
928
+ if (layer.fix !== void 0) {
929
+ writer.note(layer.fix);
930
+ }
931
+ }
932
+ if (layer.name === "salesPoints") {
933
+ for (const point of report.salesPoints ?? []) {
934
+ writer.note(describeSalesPoint(point));
935
+ }
936
+ }
937
+ if (layer.warning !== void 0) {
938
+ writer.warn(label, layer.warning);
939
+ }
940
+ }
941
+ }
942
+ function resolveCheckEnvironment(io, flags) {
943
+ const chosen = flags.env?.trim().toLowerCase() || io.env.ARCA_ENVIRONMENT?.trim();
944
+ if (chosen) {
945
+ return chosen;
946
+ }
947
+ if (flags.cert?.trim() || io.env.ARCA_CERTIFICATE_PEM?.trim()) {
948
+ return void 0;
949
+ }
950
+ try {
951
+ const discovery = discoverCredentials(resolveDirectory(io, flags.dir));
952
+ return discovery.kind === "found" ? discovery.credentials.environment : void 0;
953
+ } catch {
954
+ return void 0;
955
+ }
956
+ }
957
+ function resolveConfigLayer(io, flags) {
958
+ const chosenEnvironment = flags.env?.trim().toLowerCase() || io.env.ARCA_ENVIRONMENT?.trim();
959
+ let certificatePem;
960
+ let privateKeyPem;
961
+ let discovery = { kind: "none" };
962
+ try {
963
+ certificatePem = readPem(io, flags.cert, io.env.ARCA_CERTIFICATE_PEM);
964
+ privateKeyPem = readPem(io, flags.key, io.env.ARCA_PRIVATE_KEY_PEM);
965
+ if (!(certificatePem && privateKeyPem)) {
966
+ discovery = discoverCredentials(
967
+ resolveDirectory(io, flags.dir),
968
+ toEnvironment2(chosenEnvironment)
969
+ );
970
+ }
971
+ } catch (error) {
972
+ const unknown = describeUnknownError(error);
973
+ return {
974
+ layer: failedLayer(
975
+ "config",
976
+ { diagnosis: unknown.diagnosis },
977
+ unknown.code
978
+ )
979
+ };
980
+ }
981
+ const failure = describeDiscoveryFailure(discovery);
982
+ if (failure) {
983
+ return { layer: failedLayer("config", failure) };
984
+ }
985
+ const found = discovery.kind === "found" ? discovery.credentials : void 0;
986
+ const certificate = certificatePem || found?.certificatePem;
987
+ const privateKey = privateKeyPem || found?.privateKeyPem;
988
+ const taxId = resolveTaxId(io, flags, certificate);
989
+ if ("layer" in taxId) {
990
+ return { layer: taxId.layer };
991
+ }
992
+ const environment = chosenEnvironment || found?.environment;
993
+ if (!environment) {
994
+ return { layer: failedLayer("config", diagnose("config.environment")) };
995
+ }
996
+ if (!(certificate && privateKey)) {
997
+ return { layer: failedLayer("config", diagnose("config.pem")) };
998
+ }
999
+ try {
1000
+ const config = discoverArcaClientConfig({
1001
+ taxId: taxId.taxId,
1002
+ certificatePem: certificate,
1003
+ privateKeyPem: privateKey,
1004
+ environment
1005
+ });
1006
+ assertArcaClientConfig(config);
1007
+ return {
1008
+ layer: {
1009
+ name: "config",
1010
+ ok: true,
1011
+ detail: describeSources({
1012
+ ...describeCredentialSource(flags, found),
1013
+ taxId: taxId.source,
1014
+ ...describeEnvironmentSource(io, flags, config.environment)
1015
+ })
1016
+ },
1017
+ config
1018
+ };
1019
+ } catch (error) {
1020
+ return { layer: failedLayer("config", toConfigDiagnosis(error)) };
1021
+ }
1022
+ }
1023
+ function resolveTaxId(io, flags, certificatePem) {
1024
+ const given = flags.taxId?.trim() || io.env.ARCA_TAX_ID?.trim();
1025
+ if (given) {
1026
+ const problem = describeTaxIdProblem(given);
1027
+ if (problem !== void 0) {
1028
+ return {
1029
+ layer: failedLayer("config", { diagnosis: problem, fix: TAX_ID_FIX })
1030
+ };
1031
+ }
1032
+ return {
1033
+ taxId: normalizeTaxId(given),
1034
+ source: flags.taxId?.trim() ? "--tax-id" : "ARCA_TAX_ID"
1035
+ };
1036
+ }
1037
+ if (certificatePem === void 0) {
1038
+ return { layer: failedLayer("config", diagnose("config.taxId")) };
1039
+ }
1040
+ const fromCertificate = readCertificateTaxId(certificatePem);
1041
+ if (fromCertificate === void 0) {
1042
+ return { layer: failedLayer("config", diagnose("config.taxIdUnknown")) };
1043
+ }
1044
+ return {
1045
+ taxId: fromCertificate,
1046
+ source: `CUIT ${fromCertificate} del certificado`
1047
+ };
1048
+ }
1049
+ function readCertificateTaxId(certificatePem) {
1050
+ try {
1051
+ return readCertificateFacts(certificatePem).taxId;
1052
+ } catch {
1053
+ return void 0;
1054
+ }
1055
+ }
1056
+ function describeDiscoveryFailure(discovery) {
1057
+ if (discovery.kind === "ambiguous") {
1058
+ return diagnose("config.files.ambiguous", {
1059
+ files: discovery.files.join(" y ")
1060
+ });
1061
+ }
1062
+ if (discovery.kind === "incomplete") {
1063
+ return diagnose(
1064
+ discovery.missingKind === "certificate" ? "config.files.missingCertificate" : "config.files.missingKey",
1065
+ { file: discovery.present, missingFile: discovery.missing }
1066
+ );
1067
+ }
1068
+ return void 0;
1069
+ }
1070
+ function resolveDirectory(io, directory) {
1071
+ return isAbsolute2(directory ?? "") ? directory : resolve3(io.cwd, directory ?? ".");
1072
+ }
1073
+ function toEnvironment2(value) {
1074
+ return value === "test" || value === "production" ? value : void 0;
1075
+ }
1076
+ function readPem(io, file, fromEnv) {
1077
+ if (file === void 0 || file === "") {
1078
+ return fromEnv?.trim() || void 0;
1079
+ }
1080
+ const path = isAbsolute2(file) ? file : resolve3(io.cwd, file);
1081
+ return readFileSync3(path, "utf8").trim();
1082
+ }
1083
+ function describeCredentialSource(flags, found) {
1084
+ if (found !== void 0) {
1085
+ return { credentials: `${found.certificateFile} en este directorio` };
1086
+ }
1087
+ const named = [
1088
+ ...flags.cert?.trim() ? ["--cert"] : [],
1089
+ ...flags.key?.trim() ? ["--key"] : []
1090
+ ];
1091
+ return named.length === 0 ? {} : { credentials: named.join(" y ") };
1092
+ }
1093
+ function describeEnvironmentSource(io, flags, environment) {
1094
+ if (flags.env?.trim()) {
1095
+ return { environment: `--env=${environment}` };
1096
+ }
1097
+ if (io.env.ARCA_ENVIRONMENT?.trim()) {
1098
+ return { environment: `ARCA_ENVIRONMENT=${environment}` };
1099
+ }
1100
+ return {};
1101
+ }
1102
+ function describeSources(origin) {
1103
+ return [origin.credentials, origin.taxId, origin.environment].filter((part) => part !== void 0).join(", ");
1104
+ }
1105
+ function toConfigDiagnosis(error) {
1106
+ if (!(error instanceof ArcaConfigurationError)) {
1107
+ return { diagnosis: describeUnknownError(error).diagnosis };
1108
+ }
1109
+ if (error.message.includes("taxId")) {
1110
+ return diagnose("config.taxId");
1111
+ }
1112
+ if (error.message.includes("environment")) {
1113
+ return diagnose("config.environment");
1114
+ }
1115
+ if (error.message.includes("certificatePem") || error.message.includes("privateKeyPem") || error.message.includes("Encrypted private keys")) {
1116
+ return diagnose("cert.invalid");
1117
+ }
1118
+ return { diagnosis: error.message };
1119
+ }
1120
+ function checkCertificate(config, now) {
1121
+ let facts;
1122
+ let matches;
1123
+ try {
1124
+ facts = readCertificateFacts(config.certificatePem);
1125
+ matches = privateKeyMatchesCertificate(
1126
+ config.certificatePem,
1127
+ config.privateKeyPem
1128
+ );
1129
+ } catch {
1130
+ return failedLayer("certificate", diagnose("cert.invalid"));
1131
+ }
1132
+ if (facts.taxId !== void 0 && facts.taxId !== config.taxId) {
1133
+ return failedLayer(
1134
+ "certificate",
1135
+ diagnose("cert.taxIdMismatch", {
1136
+ certificateTaxId: facts.taxId,
1137
+ taxId: config.taxId
1138
+ })
1139
+ );
1140
+ }
1141
+ const expiresAt = toIsoDate(facts.notAfter);
1142
+ if (facts.notAfter.getTime() <= now.getTime()) {
1143
+ return failedLayer(
1144
+ "certificate",
1145
+ diagnose("cert.expired", { date: expiresAt })
1146
+ );
1147
+ }
1148
+ if (!matches) {
1149
+ return failedLayer("certificate", diagnose("cert.mismatch"));
1150
+ }
1151
+ const daysLeft = Math.floor(
1152
+ (facts.notAfter.getTime() - now.getTime()) / MILLISECONDS_PER_DAY
1153
+ );
1154
+ return {
1155
+ name: "certificate",
1156
+ ok: true,
1157
+ detail: `coinciden, vence ${expiresAt}`,
1158
+ expiresAt,
1159
+ ...daysLeft < EXPIRY_WARNING_DAYS ? { warning: `vence en ${daysLeft} ${daysLeft === 1 ? "d\xEDa" : "d\xEDas"}` } : {}
1160
+ };
1161
+ }
1162
+ async function checkWsaa(io, config, cache) {
1163
+ try {
1164
+ const auth = io.createAuth({
1165
+ ...config,
1166
+ ...cache === void 0 ? {} : { wsaaSessionStore: cache.sessionStore }
1167
+ });
1168
+ const credentials = await auth.login("wsfe", {
1169
+ forceRefresh: cache === void 0
1170
+ });
1171
+ return {
1172
+ layer: {
1173
+ name: "wsaa",
1174
+ ok: true,
1175
+ detail: cache?.wasReused() === true ? "ticket vigente" : "ticket obtenido"
1176
+ },
1177
+ credentials
1178
+ };
1179
+ } catch (error) {
1180
+ return { layer: toWsaaFailure(error, config.environment) };
1181
+ }
1182
+ }
1183
+ function toWsaaFailure(error, environment) {
1184
+ const fault = findSoapFault(error);
1185
+ const key = diagnoseWsaaFaultCode(fault?.faultCode);
1186
+ if (key !== void 0) {
1187
+ return failedLayer("wsaa", diagnose(key), fault?.code);
1188
+ }
1189
+ const transport = findTransportError(error);
1190
+ if (transport) {
1191
+ const host = new URL(ARCA_WSAA_CONFIG.endpoint[environment]).host;
1192
+ return failedLayer(
1193
+ "wsaa",
1194
+ diagnose("wsaa.transport", { host }),
1195
+ transport.code
1196
+ );
1197
+ }
1198
+ const unknown = describeUnknownError(error);
1199
+ return failedLayer("wsaa", { diagnosis: unknown.diagnosis }, unknown.code);
1200
+ }
1201
+ async function checkWsfe(client) {
1202
+ try {
1203
+ const status = await client.wsfe.getServerStatus();
1204
+ const down = [
1205
+ ["appServer", status.appServer],
1206
+ ["dbServer", status.dbServer],
1207
+ ["authServer", status.authServer]
1208
+ ].filter(([, value]) => value.toUpperCase() !== "OK");
1209
+ if (down.length > 0) {
1210
+ const message = down.map(([name, value]) => `${name}=${value}`).join(", ");
1211
+ return {
1212
+ layer: failedLayer("wsfe", diagnose("wsfe.serviceError", { message }))
1213
+ };
1214
+ }
1215
+ const salesPoints = await client.wsfe.getSalesPoints({});
1216
+ return {
1217
+ layer: { name: "wsfe", ok: true, detail: "servidor ok" },
1218
+ salesPoints
1219
+ };
1220
+ } catch (error) {
1221
+ return { layer: toWsfeFailure(error) };
1222
+ }
1223
+ }
1224
+ function toWsfeFailure(error) {
1225
+ if (isArcaAuthenticationError(error)) {
1226
+ return {
1227
+ ...failedLayer(
1228
+ "wsfe",
1229
+ diagnoseAuthenticationReason(error.reason),
1230
+ error.code
1231
+ ),
1232
+ reason: error.reason
1233
+ };
1234
+ }
1235
+ if (error instanceof ArcaServiceError || error instanceof ArcaSoapFaultError) {
1236
+ return failedLayer(
1237
+ "wsfe",
1238
+ diagnose("wsfe.serviceError", { message: error.message }),
1239
+ error.code
1240
+ );
1241
+ }
1242
+ const unknown = describeUnknownError(error);
1243
+ return failedLayer("wsfe", { diagnosis: unknown.diagnosis }, unknown.code);
1244
+ }
1245
+ function checkSalesPoints(points, environment, requested) {
1246
+ if (requested !== void 0) {
1247
+ const found = points.find((point) => point.number === requested);
1248
+ if (!found) {
1249
+ if (points.length === 0 && environment === "test") {
1250
+ return {
1251
+ name: "salesPoints",
1252
+ ok: true,
1253
+ detail: `${requested} (no informado)`,
1254
+ warning: EMPTY_IN_TEST_WARNING
1255
+ };
1256
+ }
1257
+ return failedLayer(
1258
+ "salesPoints",
1259
+ diagnose("salesPoint.missing", { salesPoint: requested })
1260
+ );
1261
+ }
1262
+ if (found.blocked) {
1263
+ return failedLayer(
1264
+ "salesPoints",
1265
+ diagnose("salesPoint.blocked", { salesPoint: requested })
1266
+ );
1267
+ }
1268
+ return {
1269
+ name: "salesPoints",
1270
+ ok: true,
1271
+ detail: describeSalesPoint(found)
1272
+ };
1273
+ }
1274
+ if (points.length === 0) {
1275
+ if (environment === "test") {
1276
+ return {
1277
+ name: "salesPoints",
1278
+ ok: true,
1279
+ detail: "ninguno informado",
1280
+ warning: EMPTY_IN_TEST_WARNING
1281
+ };
1282
+ }
1283
+ return failedLayer("salesPoints", diagnose("salesPoint.none"));
1284
+ }
1285
+ return {
1286
+ name: "salesPoints",
1287
+ ok: true,
1288
+ detail: `${points.length} ${points.length === 1 ? "informado" : "informados"}`
1289
+ };
1290
+ }
1291
+ function createTicketCache(directory) {
1292
+ const store = createWsaaStoreAdapter(createFileStore(directory));
1293
+ let reused = false;
1294
+ const remove = store.delete?.bind(store);
1295
+ return {
1296
+ sessionStore: {
1297
+ get: async (key) => {
1298
+ const credentials = await store.get(key);
1299
+ if (credentials) {
1300
+ reused = true;
1301
+ }
1302
+ return credentials;
1303
+ },
1304
+ set: (key, credentials) => store.set(key, credentials),
1305
+ ...remove === void 0 ? {} : { delete: remove }
1306
+ },
1307
+ wasReused: () => reused
1308
+ };
1309
+ }
1310
+ function createEphemeralSessionStore(credentials) {
1311
+ return {
1312
+ get: () => Promise.resolve(credentials),
1313
+ set: () => Promise.resolve()
1314
+ };
1315
+ }
1316
+ function toSalesPointReport(point) {
1317
+ return {
1318
+ number: point.number,
1319
+ blocked: (point.blocked ?? "N").trim().toUpperCase().startsWith("S"),
1320
+ ...point.emissionType === void 0 ? {} : { system: point.emissionType }
1321
+ };
1322
+ }
1323
+ function describeSalesPoint(point) {
1324
+ const state = point.blocked ? "bloqueado" : "habilitado";
1325
+ return point.system === void 0 ? `${point.number} (${state})` : `${point.number} (${state}, ${point.system})`;
1326
+ }
1327
+ function failedLayer(name, row, code) {
1328
+ return {
1329
+ name,
1330
+ ok: false,
1331
+ diagnosis: row.diagnosis,
1332
+ ...row.fix === void 0 ? {} : { fix: row.fix },
1333
+ ...code === void 0 ? {} : { code }
1334
+ };
1335
+ }
1336
+ function toIsoDate(value) {
1337
+ return value.toISOString().slice(0, 10);
1338
+ }
1339
+
1340
+ // src/cli/clipboard.ts
1341
+ import { spawn as spawnProcess } from "child_process";
1342
+ var TIMEOUT_MS = 2e3;
1343
+ function clipboardCommands(platform, env) {
1344
+ if (env.SSH_CONNECTION !== void 0 || env.SSH_TTY !== void 0) {
1345
+ return [];
1346
+ }
1347
+ if (platform === "darwin") {
1348
+ return [{ command: "pbcopy", args: [] }];
1349
+ }
1350
+ if (platform === "win32") {
1351
+ return [{ command: "clip", args: [] }];
1352
+ }
1353
+ const commands = [];
1354
+ if (env.WAYLAND_DISPLAY !== void 0) {
1355
+ commands.push({ command: "wl-copy", args: [] });
1356
+ }
1357
+ if (env.DISPLAY !== void 0) {
1358
+ commands.push(
1359
+ { command: "xclip", args: ["-selection", "clipboard"] },
1360
+ { command: "xsel", args: ["--clipboard", "--input"] }
1361
+ );
1362
+ }
1363
+ return commands;
1364
+ }
1365
+ async function copyToClipboard(text2, options = {}) {
1366
+ const spawn = options.spawn ?? ((command, args) => spawnProcess(command, [...args], {
1367
+ stdio: ["pipe", "ignore", "ignore"]
1368
+ }));
1369
+ const commands = clipboardCommands(
1370
+ options.platform ?? process.platform,
1371
+ options.env ?? process.env
1372
+ );
1373
+ for (const candidate of commands) {
1374
+ const copied = await feed(
1375
+ spawn,
1376
+ candidate,
1377
+ text2,
1378
+ options.timeoutMs ?? TIMEOUT_MS
1379
+ );
1380
+ if (copied) {
1381
+ return true;
1382
+ }
1383
+ }
1384
+ return false;
1385
+ }
1386
+ function feed(spawn, candidate, text2, timeoutMs) {
1387
+ return new Promise((done) => {
1388
+ let settled = false;
1389
+ let timer;
1390
+ const finish = (copied) => {
1391
+ if (settled) {
1392
+ return;
1393
+ }
1394
+ settled = true;
1395
+ if (timer !== void 0) {
1396
+ clearTimeout(timer);
1397
+ }
1398
+ done(copied);
1399
+ };
1400
+ let child;
1401
+ try {
1402
+ child = spawn(candidate.command, candidate.args);
1403
+ } catch {
1404
+ finish(false);
1405
+ return;
1406
+ }
1407
+ timer = setTimeout(() => {
1408
+ child.kill();
1409
+ finish(false);
1410
+ }, timeoutMs);
1411
+ child.on("error", () => finish(false));
1412
+ child.on("close", (code) => finish(code === 0));
1413
+ const stdin = child.stdin;
1414
+ if (stdin === null) {
1415
+ finish(false);
1416
+ return;
1417
+ }
1418
+ stdin.on("error", () => finish(false));
1419
+ try {
1420
+ stdin.write(text2);
1421
+ stdin.end();
1422
+ } catch {
1423
+ finish(false);
1424
+ }
1425
+ });
1426
+ }
1427
+
1428
+ // src/cli/version.ts
1429
+ import { readFileSync as readFileSync4 } from "fs";
1430
+ import { dirname, join } from "path";
1431
+ import { fileURLToPath } from "url";
1432
+ var MAX_DEPTH = 5;
1433
+ function readCliVersion() {
1434
+ let directory = dirname(fileURLToPath(import.meta.url));
1435
+ for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
1436
+ try {
1437
+ const raw = readFileSync4(join(directory, "package.json"), "utf8");
1438
+ const parsed = JSON.parse(raw);
1439
+ if (typeof parsed.version === "string") {
1440
+ return parsed.version;
1441
+ }
1442
+ } catch {
1443
+ }
1444
+ directory = dirname(directory);
1445
+ }
1446
+ return "0.0.0";
1447
+ }
1448
+
1449
+ // src/cli/help.ts
1450
+ var INDENT2 = " ";
1451
+ var COLUMN_GAP2 = 3;
1452
+ var CLI_COMMAND_SUMMARIES = {
1453
+ init: "clave privada y CSR, m\xE1s los pasos exactos en ARCA",
1454
+ cert: "pega el certificado que te dio ARCA y lo guarda",
1455
+ check: "prueba cada capa en orden y nombra la que falla",
1456
+ issue: "una factura de ARS 1 en homologaci\xF3n, solo a pedido"
1457
+ };
1458
+ var HELP_PAGES = {
1459
+ root: {
1460
+ usage: "npx facturas <comando> [opciones]",
1461
+ sections: [
1462
+ {
1463
+ title: "Comandos:",
1464
+ bold: true,
1465
+ rows: [
1466
+ { name: "init", description: CLI_COMMAND_SUMMARIES.init },
1467
+ { name: "cert", description: CLI_COMMAND_SUMMARIES.cert },
1468
+ { name: "check", description: CLI_COMMAND_SUMMARIES.check },
1469
+ { name: "issue", description: CLI_COMMAND_SUMMARIES.issue }
1470
+ ]
1471
+ },
1472
+ {
1473
+ title: "Opciones globales:",
1474
+ rows: [
1475
+ { name: "--json", description: "salida JSON (check e issue)" },
1476
+ { name: "--no-color", description: "sin colores" },
1477
+ {
1478
+ name: "-h, --help",
1479
+ description: "ayuda de un comando: npx facturas check --help"
1480
+ },
1481
+ { name: "-v, --version" }
1482
+ ]
1483
+ }
1484
+ ],
1485
+ examples: [
1486
+ "npx facturas init --cuit 20123456786 --env test",
1487
+ "npx facturas check",
1488
+ "npx facturas issue --sales-point 3 --issuer monotributo"
1489
+ ]
1490
+ },
1491
+ init: {
1492
+ summary: CLI_COMMAND_SUMMARIES.init,
1493
+ usage: "npx facturas init [opciones]",
1494
+ sections: [
1495
+ {
1496
+ title: "Opciones:",
1497
+ rows: [
1498
+ {
1499
+ name: "--cuit <cuit>",
1500
+ description: "CUIT de 11 d\xEDgitos, con o sin guiones"
1501
+ },
1502
+ {
1503
+ name: "--env <test|production>",
1504
+ description: "entorno de destino"
1505
+ },
1506
+ {
1507
+ name: "--name <alias>",
1508
+ description: "common name del CSR (por defecto: facturas)"
1509
+ },
1510
+ {
1511
+ name: "--org <raz\xF3n social>",
1512
+ description: "organizaci\xF3n del CSR (por defecto: el CUIT)"
1513
+ },
1514
+ {
1515
+ name: "--dir <directorio>",
1516
+ description: "d\xF3nde escribir los archivos (por defecto: el actual)"
1517
+ },
1518
+ {
1519
+ name: "--force",
1520
+ description: "sobrescribe los archivos existentes"
1521
+ },
1522
+ {
1523
+ name: "--no-clipboard",
1524
+ description: "no copia el CSR al portapapeles"
1525
+ },
1526
+ {
1527
+ name: "--no-paste",
1528
+ description: "no pregunta por el certificado al final"
1529
+ },
1530
+ { name: "--no-color", description: "sin colores" },
1531
+ { name: "-h, --help", description: "esta ayuda" }
1532
+ ]
1533
+ }
1534
+ ],
1535
+ examples: [
1536
+ "npx facturas init --cuit 20123456786 --env test",
1537
+ "npx facturas init --cuit 20-12345678-6 --env production --name mi-sistema"
1538
+ ],
1539
+ notes: [
1540
+ "Escribe arca-<entorno>.key con permisos 0600 y arca-<entorno>.csr, y",
1541
+ "nunca escribe en ARCA. En homologaci\xF3n copia el CSR al portapapeles, y",
1542
+ "si no puede lo imprime para copiarlo de la terminal. Al final pide el",
1543
+ "certificado y lo guarda como arca-<entorno>.crt; con Ctrl-C, --no-paste",
1544
+ "o sin terminal, lo guard\xE1s vos o lo peg\xE1s despu\xE9s con cert. En una",
1545
+ "terminal pregunta el CUIT y el entorno; sin terminal, --cuit y --env",
1546
+ "son obligatorios."
1547
+ ]
1548
+ },
1549
+ cert: {
1550
+ summary: CLI_COMMAND_SUMMARIES.cert,
1551
+ usage: "npx facturas cert [opciones]",
1552
+ sections: [
1553
+ {
1554
+ title: "Opciones:",
1555
+ rows: [
1556
+ {
1557
+ name: "--env <test|production>",
1558
+ description: "cu\xE1l par usar si est\xE1n los dos"
1559
+ },
1560
+ {
1561
+ name: "--dir <directorio>",
1562
+ description: "d\xF3nde est\xE1n los archivos (por defecto: el actual)"
1563
+ },
1564
+ {
1565
+ name: "--force",
1566
+ description: "sobrescribe el certificado existente"
1567
+ },
1568
+ { name: "--no-color", description: "sin colores" },
1569
+ { name: "-h, --help", description: "esta ayuda" }
1570
+ ]
1571
+ }
1572
+ ],
1573
+ examples: [
1574
+ "npx facturas cert",
1575
+ "npx facturas cert --env production",
1576
+ "npx facturas cert --dir credenciales --force"
1577
+ ],
1578
+ notes: [
1579
+ "Peg\xE1s el certificado que te dio ARCA, de -----BEGIN CERTIFICATE----- a",
1580
+ "-----END CERTIFICATE-----, y lo guarda como arca-<entorno>.crt junto a",
1581
+ "la clave que gener\xF3 init. Antes verifica que sea el certificado de esa",
1582
+ "clave y de ese CUIT, y si no, no escribe nada. Es lo mismo que init",
1583
+ "pregunta al final: cert est\xE1 para cuando lo dejaste para despu\xE9s."
1584
+ ]
1585
+ },
1586
+ check: {
1587
+ summary: CLI_COMMAND_SUMMARIES.check,
1588
+ usage: "npx facturas check [opciones]",
1589
+ sections: [
1590
+ {
1591
+ title: "Opciones:",
1592
+ rows: [
1593
+ {
1594
+ name: "--cert <archivo>",
1595
+ description: "certificado PEM desde un archivo"
1596
+ },
1597
+ {
1598
+ name: "--key <archivo>",
1599
+ description: "clave privada PEM desde un archivo"
1600
+ },
1601
+ {
1602
+ name: "--tax-id <cuit>",
1603
+ description: "CUIT, en lugar de ARCA_TAX_ID"
1604
+ },
1605
+ {
1606
+ name: "--env <test|production>",
1607
+ description: "entorno, en lugar de ARCA_ENVIRONMENT"
1608
+ },
1609
+ {
1610
+ name: "--dir <directorio>",
1611
+ description: "d\xF3nde buscar los archivos (por defecto: el actual)"
1612
+ },
1613
+ {
1614
+ name: "--sales-point <n>",
1615
+ description: "punto de venta a verificar"
1616
+ },
1617
+ {
1618
+ name: "--no-cache",
1619
+ description: "no reusa ni guarda el ticket WSAA"
1620
+ },
1621
+ { name: "--json", description: "salida JSON" },
1622
+ { name: "--no-color", description: "sin colores" },
1623
+ { name: "-h, --help", description: "esta ayuda" }
1624
+ ]
1625
+ }
1626
+ ],
1627
+ examples: [
1628
+ "npx facturas check",
1629
+ "npx facturas check --sales-point 3",
1630
+ "npx facturas check --cert arca-test.crt --key arca-test.key"
1631
+ ],
1632
+ notes: [
1633
+ "Busca cada valor en este orden: los flags, las variables de entorno y,",
1634
+ "por \xFAltimo, arca-<entorno>.crt y arca-<entorno>.key en el directorio; de",
1635
+ "ah\xED sale tambi\xE9n el CUIT y el entorno. Nunca escribe en ARCA: solo lee.",
1636
+ "Guarda el ticket WSAA en el directorio temporal del sistema para poder",
1637
+ "repetirse, porque ARCA rechaza un segundo login mientras hay uno",
1638
+ "vigente; --no-cache no lo lee ni lo escribe."
1639
+ ]
1640
+ },
1641
+ issue: {
1642
+ summary: CLI_COMMAND_SUMMARIES.issue,
1643
+ usage: "npx facturas issue [opciones]",
1644
+ sections: [
1645
+ {
1646
+ title: "Opciones:",
1647
+ rows: [
1648
+ { name: "--sales-point <n>", description: "punto de venta a usar" },
1649
+ { name: "--issuer <condici\xF3n>", description: "condici\xF3n del emisor" },
1650
+ {
1651
+ name: "--cert <archivo>",
1652
+ description: "certificado PEM desde un archivo"
1653
+ },
1654
+ {
1655
+ name: "--key <archivo>",
1656
+ description: "clave privada PEM desde un archivo"
1657
+ },
1658
+ {
1659
+ name: "--tax-id <cuit>",
1660
+ description: "CUIT, en lugar de ARCA_TAX_ID"
1661
+ },
1662
+ {
1663
+ name: "--env <test|production>",
1664
+ description: "entorno, en lugar de ARCA_ENVIRONMENT"
1665
+ },
1666
+ {
1667
+ name: "--dir <directorio>",
1668
+ description: "d\xF3nde buscar los archivos (por defecto: el actual)"
1669
+ },
1670
+ {
1671
+ name: "--no-cache",
1672
+ description: "no reusa ni guarda el ticket WSAA"
1673
+ },
1674
+ { name: "--json", description: "salida JSON" },
1675
+ { name: "--no-color", description: "sin colores" },
1676
+ { name: "-h, --help", description: "esta ayuda" }
1677
+ ]
1678
+ }
1679
+ ],
1680
+ examples: [
1681
+ "npx facturas issue --sales-point 3 --issuer monotributo",
1682
+ "npx facturas issue --sales-point 3 --issuer responsable_inscripto --json"
1683
+ ],
1684
+ notes: [
1685
+ "Las condiciones de emisor son monotributo, responsable_inscripto,",
1686
+ "exento y no_alcanzado. Emite un comprobante real de homologaci\xF3n y se",
1687
+ "niega fuera de test: corre antes las capas de check, con la misma",
1688
+ "b\xFAsqueda de configuraci\xF3n, y no sigue si alguna falla. En una terminal",
1689
+ "pregunta el punto de venta y el emisor."
1690
+ ]
1691
+ }
1692
+ };
1693
+ function renderHelp(topic, options) {
1694
+ const painter = createPainter(options.color);
1695
+ const page = HELP_PAGES[topic];
1696
+ const lines = [`facturas ${readCliVersion()}`, ""];
1697
+ if (page.summary !== void 0) {
1698
+ lines.push(`${INDENT2}${page.summary}`, "");
1699
+ }
1700
+ lines.push(`${INDENT2}${page.usage}`, "");
1701
+ for (const section of page.sections) {
1702
+ lines.push(painter.dim(section.title), "");
1703
+ const width = columnWidth2(section.rows);
1704
+ for (const row of section.rows) {
1705
+ const name = section.bold === true ? painter.bold(row.name) : row.name;
1706
+ lines.push(
1707
+ row.description === void 0 ? `${INDENT2}${name}` : `${INDENT2}${name}${" ".repeat(width - row.name.length)}${row.description}`
1708
+ );
1709
+ }
1710
+ lines.push("");
1711
+ }
1712
+ lines.push(painter.dim("Ejemplos:"), "");
1713
+ for (const example of page.examples) {
1714
+ lines.push(`${INDENT2}${painter.dim("$")} ${painter.cyan(example)}`);
1715
+ }
1716
+ if (page.notes !== void 0) {
1717
+ lines.push("", painter.dim("Notas:"), "");
1718
+ for (const note of page.notes) {
1719
+ lines.push(`${INDENT2}${note}`);
1720
+ }
1721
+ }
1722
+ return `${lines.join("\n")}
1723
+ `;
1724
+ }
1725
+ function columnWidth2(rows) {
1726
+ const widest = rows.filter((row) => row.description !== void 0).reduce((longest, row) => Math.max(longest, row.name.length), 0);
1727
+ return widest + COLUMN_GAP2;
1728
+ }
1729
+
1730
+ // src/cli/init.ts
1731
+ import {
1732
+ chmodSync,
1733
+ existsSync as existsSync3,
1734
+ mkdirSync,
1735
+ readFileSync as readFileSync5,
1736
+ writeFileSync as writeFileSync2
1737
+ } from "fs";
1738
+ import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
1739
+ var KEY_FILE_MODE = 384;
1740
+ var TAX_ID_ATTEMPTS = 3;
1741
+ var MISSING_TAX_ID = "Falta el CUIT. Pas\xE1 --cuit 20123456786 o ejecut\xE1 init en una terminal.";
1742
+ var MISSING_ENVIRONMENT = "Falta el entorno. Pas\xE1 --env test o --env production.";
1743
+ var GITIGNORE_PATTERNS = ["arca-*.key", "arca-*.crt"];
1744
+ async function runInit(io, flags, writer) {
1745
+ const resolved = await resolveInitInput(io, flags);
1746
+ if (resolved === void 0) {
1747
+ return CLI_EXIT.usage;
1748
+ }
1749
+ const { taxId, environment } = resolved;
1750
+ const directory = isAbsolute3(flags.dir ?? "") ? flags.dir : resolve4(io.cwd, flags.dir ?? ".");
1751
+ const commonName = flags.name?.trim() || "facturas";
1752
+ const alias = `${commonName.replace(/[^A-Za-z0-9]/g, "")}${environment === "test" ? "Test" : "Production"}`;
1753
+ const keyName = `arca-${environment}.key`;
1754
+ const csrName = `arca-${environment}.csr`;
1755
+ const certificateName = `arca-${environment}.crt`;
1756
+ const keyPath = resolve4(directory, keyName);
1757
+ const csrPath = resolve4(directory, csrName);
1758
+ const existing = [keyPath, csrPath].filter((path) => existsSync3(path));
1759
+ if (existing.length > 0 && flags.force !== true) {
1760
+ io.stderr.write(
1761
+ `Ya existe ${existing.map((path) => path).join(" y ")}. Us\xE1 --force para sobrescribir.
1762
+ `
1763
+ );
1764
+ return CLI_EXIT.failed;
1765
+ }
1766
+ const material = createArcaCsrMaterial({
1767
+ taxId,
1768
+ commonName,
1769
+ organization: flags.org?.trim() || taxId
1770
+ });
1771
+ mkdirSync(directory, { recursive: true });
1772
+ writeFileSync2(keyPath, material.privateKeyPem, { mode: KEY_FILE_MODE });
1773
+ chmodSync(keyPath, KEY_FILE_MODE);
1774
+ writeFileSync2(csrPath, material.csrPem);
1775
+ const clipboard = environment === "test" && flags.noClipboard !== true && await io.copyToClipboard(material.csrPem);
1776
+ writer.ok(keyName, "clave privada RSA 2048, permisos 0600");
1777
+ writer.ok(csrName, `CSR para ARCA, CN=${commonName}`);
1778
+ if (clipboard) {
1779
+ writer.ok("portapapeles", `${csrName} copiado, pegalo en el paso 3`);
1780
+ }
1781
+ const ignored = appendToGitignore(directory);
1782
+ if (ignored.length > 0) {
1783
+ writer.ok(".gitignore", `agregu\xE9 ${ignored.join(" y ")}`);
1784
+ }
1785
+ const paste = flags.noPaste !== true && isInteractive(io);
1786
+ writePlan(writer, {
1787
+ alias,
1788
+ certificateName,
1789
+ clipboard,
1790
+ csrName,
1791
+ csrPem: material.csrPem,
1792
+ environment,
1793
+ paste,
1794
+ taxId
1795
+ });
1796
+ const code = paste ? await askForCertificate(io, writer, {
1797
+ certificatePath: resolve4(directory, certificateName),
1798
+ certificateName,
1799
+ keyName,
1800
+ privateKeyPem: material.privateKeyPem,
1801
+ taxId
1802
+ }) : manualCertificate(writer, certificateName);
1803
+ writer.blank();
1804
+ writer.dim("Para tu app las variables son ARCA_TAX_ID, ARCA_ENVIRONMENT,");
1805
+ writer.dim(
1806
+ "ARCA_CERTIFICATE_PEM y ARCA_PRIVATE_KEY_PEM; ver docs/inicio-rapido.md."
1807
+ );
1808
+ return code;
1809
+ }
1810
+ async function askForCertificate(io, writer, request) {
1811
+ const outcome = await pasteCertificate(io, writer, {
1812
+ intro: [
1813
+ "Cuando tengas el certificado (paso 4), pegalo ac\xE1. Termina solo al ver",
1814
+ "-----END CERTIFICATE-----. Ctrl-C para hacerlo despu\xE9s con",
1815
+ "npx facturas cert."
1816
+ ],
1817
+ ...request
1818
+ });
1819
+ if (outcome === "saved") {
1820
+ writer.blank();
1821
+ writer.line("Falta el paso 5 (la autorizaci\xF3n a wsfe). Cuando est\xE9:");
1822
+ writer.blank();
1823
+ writer.command("npx facturas check");
1824
+ return CLI_EXIT.ok;
1825
+ }
1826
+ if (outcome === "refused") {
1827
+ return CLI_EXIT.failed;
1828
+ }
1829
+ writeManualHint(writer, request.certificateName);
1830
+ return outcome === "cancelled" ? CLI_EXIT.ok : CLI_EXIT.failed;
1831
+ }
1832
+ function manualCertificate(writer, certificateName) {
1833
+ writeManualHint(writer, certificateName);
1834
+ return CLI_EXIT.ok;
1835
+ }
1836
+ async function resolveInitInput(io, flags) {
1837
+ const interactive = isInteractive(io);
1838
+ const taxId = await resolveTaxId2(io, flags, interactive);
1839
+ if ("error" in taxId) {
1840
+ io.stderr.write(`${taxId.error}
1841
+ `);
1842
+ return void 0;
1843
+ }
1844
+ const environment = await resolveEnvironment(io, flags, interactive);
1845
+ if (environment === void 0) {
1846
+ io.stderr.write(`${MISSING_ENVIRONMENT}
1847
+ `);
1848
+ return void 0;
1849
+ }
1850
+ return { taxId: taxId.taxId, environment };
1851
+ }
1852
+ async function resolveTaxId2(io, flags, interactive) {
1853
+ const fromFlag = flags.cuit?.trim();
1854
+ if (fromFlag !== void 0 && fromFlag !== "") {
1855
+ const problem2 = describeTaxIdProblem(fromFlag);
1856
+ return problem2 === void 0 ? { taxId: normalizeTaxId(fromFlag) } : { error: problem2 };
1857
+ }
1858
+ if (!interactive) {
1859
+ return { error: MISSING_TAX_ID };
1860
+ }
1861
+ let problem = MISSING_TAX_ID;
1862
+ for (let attempt = 1; attempt <= TAX_ID_ATTEMPTS; attempt += 1) {
1863
+ const answer = await ask(io, "CUIT: ");
1864
+ if (answer === "") {
1865
+ problem = MISSING_TAX_ID;
1866
+ } else {
1867
+ const invalid = describeTaxIdProblem(answer);
1868
+ if (invalid === void 0) {
1869
+ return { taxId: normalizeTaxId(answer) };
1870
+ }
1871
+ problem = invalid;
1872
+ }
1873
+ if (attempt < TAX_ID_ATTEMPTS) {
1874
+ io.stderr.write(`${problem}
1875
+ `);
1876
+ }
1877
+ }
1878
+ return { error: problem };
1879
+ }
1880
+ async function resolveEnvironment(io, flags, interactive) {
1881
+ const fromFlag = flags.env?.trim().toLowerCase();
1882
+ if (fromFlag !== void 0 && fromFlag !== "") {
1883
+ return toEnvironment(fromFlag);
1884
+ }
1885
+ if (!interactive) {
1886
+ return void 0;
1887
+ }
1888
+ const answer = await ask(io, "Entorno [test/production]: ");
1889
+ return toEnvironment(answer.trim().toLowerCase());
1890
+ }
1891
+ function appendToGitignore(directory) {
1892
+ const path = resolve4(directory, ".gitignore");
1893
+ if (!existsSync3(path)) {
1894
+ return [];
1895
+ }
1896
+ const current = readFileSync5(path, "utf8");
1897
+ const lines = new Set(current.split("\n").map((line) => line.trim()));
1898
+ const missing = GITIGNORE_PATTERNS.filter((pattern) => !lines.has(pattern));
1899
+ if (missing.length === 0) {
1900
+ return [];
1901
+ }
1902
+ const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
1903
+ writeFileSync2(path, `${current}${prefix}${missing.join("\n")}
1904
+ `);
1905
+ return [...missing];
1906
+ }
1907
+ function writePlan(writer, context) {
1908
+ const plan = buildArcaPlan(context.environment, context, writer.painter);
1909
+ writer.blank();
1910
+ writer.line(plan.heading);
1911
+ writer.blank();
1912
+ for (const line of plan.lines) {
1913
+ writer.line(line.text);
1914
+ }
1915
+ }
1916
+
1917
+ // src/cli/issue.ts
1918
+ var SMOKE_TEST_MINOR_UNITS = 100;
1919
+ var SMOKE_TEST_VAT_RATE = 21;
1920
+ var SALES_POINT_DIGITS = 5;
1921
+ var VOUCHER_NUMBER_DIGITS = 8;
1922
+ var ISSUER_CONDITIONS = [
1923
+ "monotributo",
1924
+ "responsable_inscripto",
1925
+ "exento",
1926
+ "no_alcanzado"
1927
+ ];
1928
+ async function runIssue(io, flags, writer, json) {
1929
+ const environment = resolveCheckEnvironment(io, flags);
1930
+ if (environment !== void 0 && environment !== "test") {
1931
+ return refuse2(io);
1932
+ }
1933
+ const parameters = await resolveIssueInput(io, flags);
1934
+ if (parameters === void 0) {
1935
+ return CLI_EXIT.usage;
1936
+ }
1937
+ const { report, client } = await runCheckLayers(io, flags);
1938
+ if (!(report.ok && client)) {
1939
+ if (json) {
1940
+ writer.json(report);
1941
+ } else {
1942
+ writeCheckReport(writer, report);
1943
+ }
1944
+ return CLI_EXIT.failed;
1945
+ }
1946
+ if (report.environment !== "test") {
1947
+ return refuse2(io);
1948
+ }
1949
+ const input = buildSmokeTestInput(parameters.issuer, parameters.salesPoint);
1950
+ let outcome;
1951
+ try {
1952
+ outcome = await client.issue(input);
1953
+ } catch (error) {
1954
+ return writeIssueError(writer, report, error, json);
1955
+ }
1956
+ if (json) {
1957
+ writer.json(outcome);
1958
+ return outcome.kind === "authorized" ? CLI_EXIT.ok : CLI_EXIT.failed;
1959
+ }
1960
+ return writeOutcome(writer, outcome, input);
1961
+ }
1962
+ function writeIssueError(writer, report, error, json) {
1963
+ const unknown = describeUnknownError(error);
1964
+ if (json) {
1965
+ writer.json({
1966
+ ok: false,
1967
+ ...report.environment === void 0 ? {} : { environment: report.environment },
1968
+ ...report.taxId === void 0 ? {} : { taxId: report.taxId },
1969
+ error: {
1970
+ ...unknown.code === void 0 ? {} : { code: unknown.code },
1971
+ message: unknown.diagnosis
1972
+ }
1973
+ });
1974
+ return CLI_EXIT.failed;
1975
+ }
1976
+ writer.fail("emisi\xF3n");
1977
+ writer.note(unknown.diagnosis);
1978
+ writer.note(
1979
+ "No sabemos si ARCA lleg\xF3 a autorizar; consult\xE1 el punto de venta antes de repetir."
1980
+ );
1981
+ return CLI_EXIT.failed;
1982
+ }
1983
+ function refuse2(io) {
1984
+ io.stderr.write(
1985
+ "issue solo emite en homologaci\xF3n. Cambi\xE1 ARCA_ENVIRONMENT=test.\n"
1986
+ );
1987
+ return CLI_EXIT.failed;
1988
+ }
1989
+ async function resolveIssueInput(io, flags) {
1990
+ const interactive = isInteractive(io);
1991
+ const salesPoint = await resolveSalesPoint(io, flags, interactive);
1992
+ if (salesPoint === void 0) {
1993
+ io.stderr.write("Falta el punto de venta. Pas\xE1 --sales-point 3.\n");
1994
+ return void 0;
1995
+ }
1996
+ const issuer = await resolveIssuer(io, flags, interactive);
1997
+ if (issuer === void 0) {
1998
+ io.stderr.write(
1999
+ `Falta la condici\xF3n del emisor. Pas\xE1 --issuer ${ISSUER_CONDITIONS.join("|")}.
2000
+ `
2001
+ );
2002
+ return void 0;
2003
+ }
2004
+ return { issuer, salesPoint };
2005
+ }
2006
+ async function resolveSalesPoint(io, flags, interactive) {
2007
+ if (flags.salesPoint !== void 0) {
2008
+ return flags.salesPoint;
2009
+ }
2010
+ if (!interactive) {
2011
+ return void 0;
2012
+ }
2013
+ return parseSalesPoint((await ask(io, "Punto de venta: ")).trim());
2014
+ }
2015
+ async function resolveIssuer(io, flags, interactive) {
2016
+ const fromFlag = flags.issuer?.trim();
2017
+ if (fromFlag) {
2018
+ return toIssuerCondition(fromFlag);
2019
+ }
2020
+ if (!interactive) {
2021
+ return void 0;
2022
+ }
2023
+ const answer = await ask(io, `Emisor [${ISSUER_CONDITIONS.join("/")}]: `);
2024
+ return toIssuerCondition(answer.trim());
2025
+ }
2026
+ function toIssuerCondition(value) {
2027
+ return ISSUER_CONDITIONS.includes(value) ? value : void 0;
2028
+ }
2029
+ function buildSmokeTestInput(issuer, salesPoint) {
2030
+ if (issuer === "responsable_inscripto") {
2031
+ return {
2032
+ issuer,
2033
+ salesPoint,
2034
+ to: { condition: "consumidor_final" },
2035
+ items: [{ gross: SMOKE_TEST_MINOR_UNITS, vat: SMOKE_TEST_VAT_RATE }]
2036
+ };
2037
+ }
2038
+ return {
2039
+ issuer,
2040
+ salesPoint,
2041
+ to: { condition: "consumidor_final" },
2042
+ items: [{ amount: SMOKE_TEST_MINOR_UNITS }]
2043
+ };
2044
+ }
2045
+ function writeOutcome(writer, outcome, input) {
2046
+ if (outcome.kind === "authorized") {
2047
+ writer.ok(describeVoucher(outcome.voucher));
2048
+ writeIssueCall(writer, input);
2049
+ return CLI_EXIT.ok;
2050
+ }
2051
+ if (outcome.kind === "rejected") {
2052
+ writer.fail(
2053
+ `Factura rechazada - ${describeCoordinates(outcome.attempted)}`
2054
+ );
2055
+ for (const issue of outcome.issues) {
2056
+ writer.note(
2057
+ issue.code === void 0 ? issue.message : `${issue.code} ${issue.message}`
2058
+ );
2059
+ }
2060
+ return CLI_EXIT.failed;
2061
+ }
2062
+ if (outcome.kind === "conflict") {
2063
+ writer.fail(`conflicto en ${describeCoordinates(outcome.attempted)}`);
2064
+ writer.note(outcome.reason);
2065
+ writer.note(
2066
+ "Hay otro comprobante en ese n\xFAmero; deten\xE9 el flujo e investig\xE1."
2067
+ );
2068
+ return CLI_EXIT.failed;
2069
+ }
2070
+ writer.fail(`indeterminado ${describeCoordinates(outcome.attempted)}`);
2071
+ writer.note(`${outcome.attempt.reason}, consulta ${outcome.lookup.kind}`);
2072
+ writer.note(
2073
+ "Conserv\xE1 el n\xFAmero y la evidencia; concili\xE1 o repet\xED el mismo input con su clave."
2074
+ );
2075
+ return CLI_EXIT.failed;
2076
+ }
2077
+ function describeVoucher(voucher) {
2078
+ return [
2079
+ `Factura ${voucher.voucherClass} emitida - ${describeCoordinates(voucher)}`,
2080
+ `CAE ${voucher.cae}`,
2081
+ `Vto. CAE ${formatArcaDate(voucher.caeExpiry)}`,
2082
+ `ARS ${formatMinorUnits(voucher.amounts.sentTotal)}`
2083
+ ].join(" ");
2084
+ }
2085
+ function describeCoordinates(coordinates) {
2086
+ return `${String(coordinates.salesPoint).padStart(SALES_POINT_DIGITS, "0")}-${String(
2087
+ coordinates.number
2088
+ ).padStart(VOUCHER_NUMBER_DIGITS, "0")}`;
2089
+ }
2090
+ function formatArcaDate(value) {
2091
+ return /^\d{8}$/.test(value) ? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}` : value;
2092
+ }
2093
+ function formatMinorUnits(minorUnits) {
2094
+ const negative = minorUnits < 0;
2095
+ const digits = String(Math.abs(minorUnits)).padStart(3, "0");
2096
+ const whole = digits.slice(0, -2);
2097
+ const cents = digits.slice(-2);
2098
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
2099
+ return `${negative ? "-" : ""}${grouped},${cents}`;
2100
+ }
2101
+ function writeIssueCall(writer, input) {
2102
+ writer.blank();
2103
+ writer.line("Esta es la llamada que hizo el CLI. Pegala en tu aplicaci\xF3n:");
2104
+ writer.blank();
2105
+ writer.line(" const factura = await arca.issue({");
2106
+ writer.line(` issuer: "${input.issuer}",`);
2107
+ writer.line(` salesPoint: ${input.salesPoint},`);
2108
+ writer.line(' to: { condition: "consumidor_final" },');
2109
+ writer.line(` items: [${input.items.map(formatItem).join(", ")}],`);
2110
+ writer.line(" });");
2111
+ }
2112
+ function formatItem(item) {
2113
+ const fields = Object.entries(item).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}: ${String(value)}`);
2114
+ return `{ ${fields.join(", ")} }`;
2115
+ }
2116
+
2117
+ // src/cli/main.ts
2118
+ var COMMANDS = ["init", "cert", "check", "issue"];
2119
+ var GLOBAL_OPTIONS = {
2120
+ help: { type: "boolean", short: "h" },
2121
+ version: { type: "boolean", short: "v" },
2122
+ json: { type: "boolean" },
2123
+ "no-color": { type: "boolean" }
2124
+ };
2125
+ var COMMAND_OPTIONS = {
2126
+ init: {
2127
+ ...GLOBAL_OPTIONS,
2128
+ cuit: { type: "string" },
2129
+ env: { type: "string" },
2130
+ name: { type: "string" },
2131
+ org: { type: "string" },
2132
+ dir: { type: "string" },
2133
+ force: { type: "boolean" },
2134
+ "no-clipboard": { type: "boolean" },
2135
+ "no-paste": { type: "boolean" }
2136
+ },
2137
+ cert: {
2138
+ ...GLOBAL_OPTIONS,
2139
+ env: { type: "string" },
2140
+ dir: { type: "string" },
2141
+ force: { type: "boolean" }
2142
+ },
2143
+ check: {
2144
+ ...GLOBAL_OPTIONS,
2145
+ cert: { type: "string" },
2146
+ key: { type: "string" },
2147
+ "tax-id": { type: "string" },
2148
+ env: { type: "string" },
2149
+ dir: { type: "string" },
2150
+ "sales-point": { type: "string" },
2151
+ "no-cache": { type: "boolean" }
2152
+ },
2153
+ issue: {
2154
+ ...GLOBAL_OPTIONS,
2155
+ cert: { type: "string" },
2156
+ key: { type: "string" },
2157
+ "tax-id": { type: "string" },
2158
+ env: { type: "string" },
2159
+ dir: { type: "string" },
2160
+ "sales-point": { type: "string" },
2161
+ "no-cache": { type: "boolean" },
2162
+ issuer: { type: "string" }
2163
+ }
2164
+ };
2165
+ async function run(argv, io) {
2166
+ const command = argv.find((token) => !token.startsWith("-"));
2167
+ const wantsHelp = argv.includes("--help") || argv.includes("-h");
2168
+ const wantsVersion = argv.includes("--version") || argv.includes("-v");
2169
+ const noColor = argv.includes("--no-color");
2170
+ const help = (stream, topic) => renderHelp(topic, {
2171
+ color: shouldUseColor(stream, io.env, noColor)
2172
+ });
2173
+ if (wantsVersion) {
2174
+ io.stdout.write(`${readCliVersion()}
2175
+ `);
2176
+ return CLI_EXIT.ok;
2177
+ }
2178
+ if (wantsHelp) {
2179
+ io.stdout.write(help(io.stdout, isCommand(command) ? command : "root"));
2180
+ return CLI_EXIT.ok;
2181
+ }
2182
+ if (command === void 0) {
2183
+ io.stderr.write(help(io.stderr, "root"));
2184
+ return CLI_EXIT.usage;
2185
+ }
2186
+ if (!isCommand(command)) {
2187
+ io.stderr.write(
2188
+ `Comando desconocido: ${command}
2189
+
2190
+ ${help(io.stderr, "root")}`
2191
+ );
2192
+ return CLI_EXIT.usage;
2193
+ }
2194
+ let values;
2195
+ try {
2196
+ ({ values } = parseArgs({
2197
+ args: [...argv],
2198
+ options: COMMAND_OPTIONS[command],
2199
+ allowPositionals: true,
2200
+ strict: true
2201
+ }));
2202
+ } catch (error) {
2203
+ io.stderr.write(
2204
+ `${describeParseError(error)}
2205
+
2206
+ ${help(io.stderr, command)}`
2207
+ );
2208
+ return CLI_EXIT.usage;
2209
+ }
2210
+ const json = values.json === true;
2211
+ const writer = createWriter(io.stdout, {
2212
+ color: shouldUseColor(io.stdout, io.env, noColor)
2213
+ });
2214
+ if (command === "init") {
2215
+ return await runInit(io, toInitFlags(values), writer);
2216
+ }
2217
+ if (command === "cert") {
2218
+ return await runCert(io, toCertFlags(values), writer);
2219
+ }
2220
+ const given = values["sales-point"];
2221
+ const salesPoint = typeof given === "string" ? parseSalesPoint(given) : void 0;
2222
+ if (typeof given === "string" && salesPoint === void 0) {
2223
+ io.stderr.write(`${describeSalesPointProblem(given)}
2224
+ `);
2225
+ return CLI_EXIT.usage;
2226
+ }
2227
+ const shared = toCheckFlags(values, salesPoint);
2228
+ if (command === "check") {
2229
+ return await runCheck(io, shared, writer, json);
2230
+ }
2231
+ return await runIssue(io, toIssueFlags(values, shared), writer, json);
2232
+ }
2233
+ async function main(argv = process.argv.slice(2), io = createDefaultIo()) {
2234
+ try {
2235
+ process.exitCode = await run(argv, io);
2236
+ } catch (error) {
2237
+ io.stderr.write(`${describeUnknownError(error).diagnosis}
2238
+ `);
2239
+ process.exitCode = CLI_EXIT.failed;
2240
+ }
2241
+ }
2242
+ function createDefaultIo() {
2243
+ return {
2244
+ stdout: process.stdout,
2245
+ stderr: process.stderr,
2246
+ stdin: process.stdin,
2247
+ env: process.env,
2248
+ cwd: process.cwd(),
2249
+ cacheDir: join2(tmpdir(), "facturas-cli"),
2250
+ now: () => /* @__PURE__ */ new Date(),
2251
+ copyToClipboard: (text2) => copyToClipboard(text2, {
2252
+ env: process.env,
2253
+ platform: process.platform
2254
+ }),
2255
+ createClient: (options) => createArcaClient(options),
2256
+ createAuth: (config) => createWsaaAuthModule({ config })
2257
+ };
2258
+ }
2259
+ function describeParseError(error) {
2260
+ const message = error instanceof Error ? error.message : String(error);
2261
+ const unknownOption = /Unknown option '([^']+)'/.exec(message)?.[1];
2262
+ if (unknownOption !== void 0) {
2263
+ return `Opci\xF3n desconocida: ${unknownOption}`;
2264
+ }
2265
+ const missingValue = /Option '([^']+)' argument missing/.exec(message)?.[1];
2266
+ if (missingValue !== void 0) {
2267
+ return `Falta el valor de ${missingValue}`;
2268
+ }
2269
+ return "Argumentos inv\xE1lidos.";
2270
+ }
2271
+ function isCommand(value) {
2272
+ return COMMANDS.includes(value);
2273
+ }
2274
+ function toInitFlags(values) {
2275
+ return {
2276
+ ...text(values, "cuit", "cuit"),
2277
+ ...text(values, "env", "env"),
2278
+ ...text(values, "name", "name"),
2279
+ ...text(values, "org", "org"),
2280
+ ...text(values, "dir", "dir"),
2281
+ ...values.force === true ? { force: true } : {},
2282
+ ...values["no-clipboard"] === true ? { noClipboard: true } : {},
2283
+ ...values["no-paste"] === true ? { noPaste: true } : {}
2284
+ };
2285
+ }
2286
+ function toCertFlags(values) {
2287
+ return {
2288
+ ...text(values, "env", "env"),
2289
+ ...text(values, "dir", "dir"),
2290
+ ...values.force === true ? { force: true } : {}
2291
+ };
2292
+ }
2293
+ function toCheckFlags(values, salesPoint) {
2294
+ return {
2295
+ ...text(values, "cert", "cert"),
2296
+ ...text(values, "key", "key"),
2297
+ ...text(values, "tax-id", "taxId"),
2298
+ ...text(values, "env", "env"),
2299
+ ...text(values, "dir", "dir"),
2300
+ ...salesPoint === void 0 ? {} : { salesPoint },
2301
+ ...values["no-cache"] === true ? { noCache: true } : {}
2302
+ };
2303
+ }
2304
+ function toIssueFlags(values, shared) {
2305
+ return { ...shared, ...text(values, "issuer", "issuer") };
2306
+ }
2307
+ function text(values, from, to) {
2308
+ const value = values[from];
2309
+ return typeof value === "string" ? { [to]: value } : {};
2310
+ }
2311
+ export {
2312
+ createDefaultIo,
2313
+ main,
2314
+ run
2315
+ };
2316
+ //# sourceMappingURL=cli.mjs.map