estela 0.1.20 → 0.1.21

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 (70) hide show
  1. package/dist/billing/amortize.d.ts.map +1 -1
  2. package/dist/billing/amortize.js +3 -2
  3. package/dist/billing/amortize.js.map +1 -1
  4. package/dist/billing/invoice.d.ts.map +1 -1
  5. package/dist/billing/invoice.js +19 -18
  6. package/dist/billing/invoice.js.map +1 -1
  7. package/dist/billing/sessionize.d.ts.map +1 -1
  8. package/dist/billing/sessionize.js +3 -2
  9. package/dist/billing/sessionize.js.map +1 -1
  10. package/dist/billing.d.ts.map +1 -1
  11. package/dist/billing.js +5 -4
  12. package/dist/billing.js.map +1 -1
  13. package/dist/bin.js +6 -2
  14. package/dist/bin.js.map +1 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +311 -164
  17. package/dist/cli.js.map +1 -1
  18. package/dist/cloud/auth.d.ts.map +1 -1
  19. package/dist/cloud/auth.js +9 -8
  20. package/dist/cloud/auth.js.map +1 -1
  21. package/dist/cloud/client.d.ts.map +1 -1
  22. package/dist/cloud/client.js +11 -6
  23. package/dist/cloud/client.js.map +1 -1
  24. package/dist/db/schema.d.ts.map +1 -1
  25. package/dist/db/schema.js +4 -3
  26. package/dist/db/schema.js.map +1 -1
  27. package/dist/doctor.d.ts.map +1 -1
  28. package/dist/doctor.js +50 -45
  29. package/dist/doctor.js.map +1 -1
  30. package/dist/i18n/en.d.ts +10 -0
  31. package/dist/i18n/en.d.ts.map +1 -0
  32. package/dist/i18n/en.js +326 -0
  33. package/dist/i18n/en.js.map +1 -0
  34. package/dist/i18n/i18n.test.d.ts +2 -0
  35. package/dist/i18n/i18n.test.d.ts.map +1 -0
  36. package/dist/i18n/i18n.test.js +201 -0
  37. package/dist/i18n/i18n.test.js.map +1 -0
  38. package/dist/i18n/index.d.ts +54 -0
  39. package/dist/i18n/index.d.ts.map +1 -0
  40. package/dist/i18n/index.js +106 -0
  41. package/dist/i18n/index.js.map +1 -0
  42. package/dist/publish.d.ts.map +1 -1
  43. package/dist/publish.js +6 -5
  44. package/dist/publish.js.map +1 -1
  45. package/dist/server.d.ts.map +1 -1
  46. package/dist/server.js +31 -29
  47. package/dist/server.js.map +1 -1
  48. package/dist/setup.d.ts.map +1 -1
  49. package/dist/setup.js +8 -5
  50. package/dist/setup.js.map +1 -1
  51. package/dist/sync/team.d.ts.map +1 -1
  52. package/dist/sync/team.js +7 -6
  53. package/dist/sync/team.js.map +1 -1
  54. package/dist/sync.d.ts.map +1 -1
  55. package/dist/sync.js +7 -6
  56. package/dist/sync.js.map +1 -1
  57. package/dist/team.d.ts.map +1 -1
  58. package/dist/team.js +9 -8
  59. package/dist/team.js.map +1 -1
  60. package/dist/updateCheck.d.ts.map +1 -1
  61. package/dist/updateCheck.js +3 -2
  62. package/dist/updateCheck.js.map +1 -1
  63. package/dist/watchers/claude.d.ts.map +1 -1
  64. package/dist/watchers/claude.js +3 -2
  65. package/dist/watchers/claude.js.map +1 -1
  66. package/node_modules/@estela/shared/dist/money.d.ts +1 -0
  67. package/node_modules/@estela/shared/dist/money.d.ts.map +1 -1
  68. package/node_modules/@estela/shared/dist/money.js +9 -1
  69. package/node_modules/@estela/shared/dist/money.js.map +1 -1
  70. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -69,7 +69,8 @@ Object.defineProperty(exports, "breakdownOfTurn", { enumerable: true, get: funct
69
69
  const claude_js_1 = require("./watchers/claude.js");
70
70
  const git_js_1 = require("./watchers/git.js");
71
71
  const identity_js_1 = require("./watchers/identity.js");
72
- const HELP = `
72
+ const index_js_1 = require("./i18n/index.js");
73
+ const HELP_ES = `
73
74
  estela — registro de horas para desarrollo asistido por IA
74
75
 
75
76
  estela setup
@@ -184,8 +185,132 @@ estela — registro de horas para desarrollo asistido por IA
184
185
  --from-tax-id y --from-email se ignoran.
185
186
 
186
187
  Opciones globales: --db <ruta> (por defecto ${schema_js_1.DEFAULT_DB_PATH})
188
+ --lang es|en Idioma. Sin ella se usa el del sistema;
189
+ ESTELA_LANG=en lo fija para siempre.
187
190
  --version Solo imprime la versión instalada.
188
191
  `;
192
+ const HELP_EN = `
193
+ estela — time tracking for AI-assisted development
194
+
195
+ estela setup
196
+ First run: detects your agents and repositories, and rebuilds your
197
+ history. Two minutes, no questions, no account.
198
+
199
+ estela import [--since YYYY-MM-DD] [--repo <path>]
200
+ Rebuilds your history from agent transcripts and git.
201
+
202
+ estela client add --id <id> --name <name> --currency <EUR|USD|...>
203
+ [--tax-id <tax ID>] [--email <a@b.com>]
204
+ --tax-id and --email are the client details printed on the report
205
+ from "estela report". Running the command again with the same --id
206
+ rewrites the whole record: pass everything you want to keep again,
207
+ anything you leave out is cleared.
208
+ estela project add --id <id> --client <id> --name <name> --repo <path>
209
+ [--rounding <min>] [--ai-cost absorbed|passthrough]
210
+ estela rate set --project <id> --rate <amount> [--from YYYY-MM-DD]
211
+ estela budget --project <id> [--amount <dollars>|none]
212
+ Monthly AI budget. Without --amount, shows the current one. Feeds the
213
+ warning on the Team tab.
214
+
215
+ estela subscription add --id <id> --name <name> --fee <fee> [--currency USD]
216
+ Records a flat subscription (Claude Max, Cursor...). With one, your
217
+ real spend is the fee split by usage, not the per-token total.
218
+
219
+ estela log --project <id> --hours <n> --what "<what you did>"
220
+ [--kind development|meeting|research|review|travel|support|other]
221
+ [--date YYYY-MM-DD] [--minutes <n>]
222
+ Hours no import will ever infer: meetings, travel, research, and
223
+ development without an agent that didn't leave commits either.
224
+
225
+ estela author --project <id> [--email <a@b.com>[,other@c.com]]
226
+ Which email you commit with on that project. Without --email, lists
227
+ the repository's authors so you can pick yours.
228
+
229
+ estela web [--port 4319] Opens the dashboard in your browser.
230
+ estela doctor Checks your data and flags anything that
231
+ would ruin a demo. Run it before publishing.
232
+ estela status What's been captured and not yet assigned.
233
+ estela entries --project <id> Blocks assigned to a project.
234
+ estela ai-cost How your subscription splits across projects.
235
+ estela share --project <id> [--from YYYY-MM-DD] [--to YYYY-MM-DD]
236
+ [--author <your name>] [--with-amounts] [--out <file.html>]
237
+ Shareable report in a single HTML file. Hours and commits; no AI
238
+ usage, which is yours as long as you're the one paying for it.
239
+
240
+ estela publish --project <id> [--author <your name>] [--with-amounts]
241
+ [--token <existing>] [--no-team] [--client <email>[,other]]
242
+ Read-only dashboard so your client can USE the product, not just
243
+ receive a document. Hosted on getestela.dev — Free allows one at a
244
+ time, Pro and Teams have no limit. Requires "estela login" first.
245
+ With --client they get an email and it shows up in their account
246
+ when they sign in to getestela.dev/app. Without --client that list
247
+ isn't touched: republishing never unlinks anyone who already had it.
248
+
249
+ estela team ai-cost --project <id> --allow | --deny
250
+ If whoever invited you asks to see the project's AI cost, this is
251
+ your answer. Without --allow it never leaves your machine, and
252
+ silence isn't a yes. It's shared summed for the project, never per person.
253
+
254
+ estela login --email <you@domain.com> [--api <url>]
255
+ Links this machine to your account. Without it, Estela stays 100%
256
+ local: nothing below changes what you already do without an account.
257
+ estela logout
258
+ Unlinks this machine. Your local data isn't touched.
259
+
260
+ estela sync enable --project <id>
261
+ Turns on sync for a project across your machines. A Pro feature;
262
+ without it, that project never leaves this machine.
263
+ estela sync [--project <id>]
264
+ Uploads and downloads what changed since the last sync. Without
265
+ --project, syncs every enabled project (personal and team).
266
+
267
+ estela team invite --project <id> --email <a@b.com>[,other@c.com] [--kind employee|freelancer]
268
+ Invites someone so their hours on that project are actually measured
269
+ on the dashboard, instead of estimated from commits. A Teams feature.
270
+ Use the emails they commit with, not the ones they sign in with.
271
+ estela team accept --token <token> [--as-id <local-id>]
272
+ Accepts an invitation and gets the project ready to sync. Without
273
+ --as-id it keeps the name it has on the team that invited you; you
274
+ only need it if you already have a project of your own with that name.
275
+ estela team repo --project <id> --add <path>
276
+ Links your local clone to a team project you've already accepted.
277
+ estela team list --project <id>
278
+ Who has accepted, their measured hours, and when they last synced.
279
+ estela team revoke --token <token>
280
+ Removes someone from the team. If you pay for Teams, the charge goes down.
281
+
282
+ estela upgrade --plan pro|teams [--annual]
283
+ Starts the plan. Prints a Stripe Checkout link — payment happens
284
+ there, never in the terminal. --annual bills the whole year at once,
285
+ with two months off compared to monthly.
286
+ estela billing portal
287
+ Change your card or cancel. Stripe handles it, not us.
288
+ estela account
289
+ Your email, your plan, and which machines you've signed in from.
290
+
291
+ estela export --project <id> [--out <file.csv>]
292
+
293
+ estela report --project <id> --cutoff YYYY-MM-DD [--dry-run]
294
+ [--pdf <file.pdf>] [--csv <file.csv>] [--fx <rate>]
295
+ [--number <no.>]
296
+ [--from-name <your name>] [--from-email <email>]
297
+ [--from-tax-id <your tax ID>]
298
+ Report of hours and commits to back up your work. It isn't an
299
+ invoice: Estela doesn't issue tax documents.
300
+ --number sets the report number; without it, reports are numbered
301
+ automatically and sequentially per year (INF-${new Date().getFullYear()}-001, -002...).
302
+ The --from-* flags are your details as the issuer, in the PDF header.
303
+ They need --from-name: without a name that block isn't printed, and
304
+ --from-tax-id and --from-email are ignored.
305
+
306
+ Global options: --db <path> (default ${schema_js_1.DEFAULT_DB_PATH})
307
+ --lang es|en Language. Without it, your system's is used;
308
+ ESTELA_LANG=en sets it for good.
309
+ --version Only prints the installed version.
310
+ `;
311
+ function helpText() {
312
+ return (0, index_js_1.getLang)() === "en" ? HELP_EN : HELP_ES;
313
+ }
189
314
  function parseArgs(argv) {
190
315
  const positional = [];
191
316
  const flags = {};
@@ -213,11 +338,26 @@ function str(args, key) {
213
338
  function required(args, key) {
214
339
  const value = str(args, key);
215
340
  if (!value)
216
- throw new UserError(`Falta --${key}`);
341
+ throw new UserError((0, index_js_1.tr) `Falta --${key}`);
217
342
  return value;
218
343
  }
219
344
  class UserError extends Error {
220
345
  }
346
+ /**
347
+ * Nombre de un tipo de trabajo en el idioma de la terminal. WORK_KIND_LABELS
348
+ * vive en `shared` y está en español; aquí se traduce sin tocar ese paquete.
349
+ */
350
+ function kindLabel(kind) {
351
+ switch (kind) {
352
+ case "development": return (0, index_js_1.tr) `Desarrollo`;
353
+ case "meeting": return (0, index_js_1.tr) `Reunión`;
354
+ case "research": return (0, index_js_1.tr) `Investigación`;
355
+ case "review": return (0, index_js_1.tr) `Revisión`;
356
+ case "travel": return (0, index_js_1.tr) `Desplazamiento`;
357
+ case "support": return (0, index_js_1.tr) `Soporte`;
358
+ case "other": return (0, index_js_1.tr) `Otro`;
359
+ }
360
+ }
221
361
  // ---------------------------------------------------------------------------
222
362
  /**
223
363
  * `estela web` — abre el panel local.
@@ -228,9 +368,9 @@ class UserError extends Error {
228
368
  async function cmdWeb(args, dbPath) {
229
369
  const port = Number(str(args, "port") ?? 4319);
230
370
  const url = await (0, server_js_1.startServer)({ port, dbPath });
231
- console.log(`\n Estela ${url}`);
232
- console.log(` Datos: ${dbPath}`);
233
- console.log(`\n Ctrl+C para parar.\n`);
371
+ console.log((0, index_js_1.tr) `\n Estela ${url}`);
372
+ console.log((0, index_js_1.tr) ` Datos: ${dbPath}`);
373
+ console.log((0, index_js_1.tr) `\n Ctrl+C para parar.\n`);
234
374
  // No devuelve: el servidor se queda escuchando.
235
375
  await new Promise(() => { });
236
376
  }
@@ -267,8 +407,8 @@ async function cmdLogout(dbPath) {
267
407
  async function cmdSetup(args, dbPath) {
268
408
  const db = (0, schema_js_1.openDatabase)(dbPath);
269
409
  try {
270
- console.log("\nEstela\n");
271
- console.log("Leyendo lo que tus agentes ya guardaron en disco…");
410
+ console.log((0, index_js_1.tr) `\nEstela\n`);
411
+ console.log((0, index_js_1.tr) `Leyendo lo que tus agentes ya guardaron en disco…`);
272
412
  const scan = await (0, claude_js_1.scanClaudeCode)({});
273
413
  const turns = (0, claude_js_1.resolveScratchpads)(scan.turns);
274
414
  // Los repos no pueden salir solo de los transcripts: quien programa sin
@@ -286,57 +426,57 @@ async function cmdSetup(args, dbPath) {
286
426
  if (cwdRoot)
287
427
  repos.add(cwdRoot);
288
428
  if (turns.length === 0 && repos.size === 0) {
289
- console.log("\n No se han encontrado sesiones de Claude Code en ~/.claude, ni");
290
- console.log(" un repositorio de Git en esta carpeta. Corre esto de nuevo desde");
291
- console.log(" dentro de tu proyecto, o trabaja un rato con tu agente y vuelve.\n");
429
+ console.log((0, index_js_1.tr) `\n No se han encontrado sesiones de Claude Code en ~/.claude, ni`);
430
+ console.log((0, index_js_1.tr) ` un repositorio de Git en esta carpeta. Corre esto de nuevo desde`);
431
+ console.log((0, index_js_1.tr) ` dentro de tu proyecto, o trabaja un rato con tu agente y vuelve.\n`);
292
432
  return;
293
433
  }
294
434
  if (turns.length > 0) {
295
435
  store.saveTurns(db, turns);
296
436
  store.logScan(db, "claude-code", scan.report);
297
- console.log(` ${scan.report.turnsAccepted} turnos · ` +
298
- `${scan.report.producerVersions.length} versiones de Claude Code`);
437
+ console.log((0, index_js_1.tr) ` ${scan.report.turnsAccepted} turnos · ` +
438
+ (0, index_js_1.tr) `${scan.report.producerVersions.length} versiones de Claude Code`);
299
439
  }
300
440
  else {
301
- console.log(" No se han encontrado sesiones de Claude Code en ~/.claude —");
302
- console.log(" sin problema, se reconstruye igual desde tus commits de Git.");
441
+ console.log((0, index_js_1.tr) ` No se han encontrado sesiones de Claude Code en ~/.claude —`);
442
+ console.log((0, index_js_1.tr) ` sin problema, se reconstruye igual desde tus commits de Git.`);
303
443
  }
304
- console.log(`\nBuscando repositorios… (${repos.size})`);
444
+ console.log((0, index_js_1.tr) `\nBuscando repositorios… (${repos.size})`);
305
445
  const plans = await (0, setup_js_1.planSetup)(db, [...repos], await (0, git_js_1.gitUserEmail)(process.cwd()));
306
446
  const nuevos = (0, setup_js_1.applySetup)(db, plans);
307
447
  for (const plan of plans.filter((x) => !x.existing)) {
308
448
  console.log(` + ${plan.name}` +
309
- (plan.emails.length ? ` (commiteas como ${plan.emails[0]})` : " ⚠ sin autor claro"));
449
+ (plan.emails.length ? (0, index_js_1.tr) ` (commiteas como ${plan.emails[0]})` : (0, index_js_1.tr) ` ⚠ sin autor claro`));
310
450
  }
311
451
  const yaEstaban = plans.filter((x) => x.existing).length;
312
452
  if (yaEstaban)
313
- console.log(` ${yaEstaban} ya estaban configurados y no se tocan`);
314
- console.log("\nReconstruyendo tu historial…");
453
+ console.log((0, index_js_1.tr) ` ${yaEstaban} ya estaban configurados y no se tocan`);
454
+ console.log((0, index_js_1.tr) `\nReconstruyendo tu historial…`);
315
455
  await cmdImport({ _: [], flags: { ...(args.flags["db"] ? { db: args.flags["db"] } : {}) } }, dbPath);
316
456
  const s = (0, setup_js_1.summarize)(db);
317
457
  console.log(`\n${"─".repeat(66)}`);
318
458
  console.log(` ${(0, setup_js_1.summaryLine)(s)}`);
319
459
  if (s.from)
320
- console.log(` del ${s.from} al ${s.to}`);
460
+ console.log((0, index_js_1.tr) ` del ${s.from} al ${s.to}`);
321
461
  if (s.people > 1) {
322
- console.log(` ${s.people} personas han commiteado en esos repositorios`);
462
+ console.log((0, index_js_1.tr) ` ${s.people} personas han commiteado en esos repositorios`);
323
463
  }
324
464
  console.log(`${"─".repeat(66)}\n`);
325
465
  const sinAutor = plans.filter((p) => !p.existing && p.emails.length === 0);
326
466
  if (sinAutor.length) {
327
- console.log(` ⚠ En ${sinAutor.length} repositorio(s) no se ha podido saber con qué`);
328
- console.log(` correo commiteas, así que ahí no se ha capturado nada tuyo:`);
467
+ console.log((0, index_js_1.tr) ` ⚠ En ${sinAutor.length} repositorio(s) no se ha podido saber con qué`);
468
+ console.log((0, index_js_1.tr) ` correo commiteas, así que ahí no se ha capturado nada tuyo:`);
329
469
  for (const p of sinAutor.slice(0, 3)) {
330
- console.log(` estela author --project ${p.projectId}`);
470
+ console.log((0, index_js_1.tr) ` estela author --project ${p.projectId}`);
331
471
  }
332
472
  console.log("");
333
473
  }
334
- console.log(" Ábrelo: estela web");
335
- console.log(" Revísalo: estela doctor");
474
+ console.log((0, index_js_1.tr) ` Ábrelo: estela web`);
475
+ console.log((0, index_js_1.tr) ` Revísalo: estela doctor`);
336
476
  if (nuevos > 0) {
337
- console.log("\n Los proyectos se han creado como internos y sin tarifa. Si alguno");
338
- console.log(" es de un cliente al que facturas, ponle la suya y podrás emitir");
339
- console.log(" informes: estela rate set --project <id> --rate 50\n");
477
+ console.log((0, index_js_1.tr) `\n Los proyectos se han creado como internos y sin tarifa. Si alguno`);
478
+ console.log((0, index_js_1.tr) ` es de un cliente al que facturas, ponle la suya y podrás emitir`);
479
+ console.log((0, index_js_1.tr) ` informes: estela rate set --project <id> --rate 50\n`);
340
480
  }
341
481
  }
342
482
  finally {
@@ -349,10 +489,10 @@ async function cmdImport(args, dbPath) {
349
489
  const sinceRaw = str(args, "since");
350
490
  const since = sinceRaw ? new Date(`${sinceRaw}T00:00:00Z`) : undefined;
351
491
  if (since && Number.isNaN(since.getTime()))
352
- throw new UserError(`Fecha inválida: ${sinceRaw}`);
492
+ throw new UserError((0, index_js_1.tr) `Fecha inválida: ${sinceRaw}`);
353
493
  const repoFilter = str(args, "repo");
354
494
  const repoPaths = repoFilter ? [(0, node_path_1.resolve)(repoFilter)] : undefined;
355
- console.log("Leyendo transcripts de agentes…");
495
+ console.log((0, index_js_1.tr) `Leyendo transcripts de agentes…`);
356
496
  const scan = await (0, claude_js_1.scanClaudeCode)({
357
497
  ...(since ? { since } : {}),
358
498
  ...(repoPaths ? { repoPaths } : {}),
@@ -364,15 +504,15 @@ async function cmdImport(args, dbPath) {
364
504
  const rescued = turns.filter((t, i) => t.repoPath !== scan.turns[i].repoPath).length;
365
505
  const savedTurns = store.saveTurns(db, turns);
366
506
  store.logScan(db, "claude-code", report);
367
- console.log(` ${report.filesRead} archivos · ${report.recordsSeen} registros`);
368
- console.log(` ${report.turnsAccepted} turnos aceptados · ${savedTurns} nuevos`);
369
- console.log(` ${report.duplicatesDropped} duplicados descartados` +
507
+ console.log((0, index_js_1.tr) ` ${report.filesRead} archivos · ${report.recordsSeen} registros`);
508
+ console.log((0, index_js_1.tr) ` ${report.turnsAccepted} turnos aceptados · ${savedTurns} nuevos`);
509
+ console.log((0, index_js_1.tr) ` ${report.duplicatesDropped} duplicados descartados` +
370
510
  (report.turnsAccepted > 0
371
- ? ` (${(report.duplicatesDropped / (report.turnsAccepted + report.duplicatesDropped) * 100).toFixed(0)}% de las filas)`
511
+ ? (0, index_js_1.tr) ` (${(report.duplicatesDropped / (report.turnsAccepted + report.duplicatesDropped) * 100).toFixed(0)}% de las filas)`
372
512
  : ""));
373
- console.log(` ${report.unknownRecords} registros internos ignorados · ${report.malformedRecords} malformados`);
513
+ console.log((0, index_js_1.tr) ` ${report.unknownRecords} registros internos ignorados · ${report.malformedRecords} malformados`);
374
514
  if (report.producerVersions.length) {
375
- console.log(` versiones de Claude Code: ${report.producerVersions.join(", ")}`);
515
+ console.log((0, index_js_1.tr) ` versiones de Claude Code: ${report.producerVersions.join(", ")}`);
376
516
  }
377
517
  for (const warning of report.warnings)
378
518
  console.warn(` ⚠ ${warning}`);
@@ -407,9 +547,13 @@ async function cmdImport(args, dbPath) {
407
547
  }
408
548
  const mine = await (0, identity_js_1.myEmailsByRepo)(db, repos);
409
549
  if (rescued > 0) {
410
- console.log(` ${rescued} turnos de scratchpad devueltos a su repositorio`);
550
+ console.log((0, index_js_1.tr) ` ${rescued} turnos de scratchpad devueltos a su repositorio`);
411
551
  }
412
- console.log(`Git: ${repos.size} repositorios · ${savedCommits} commits nuevos`);
552
+ // En la primera ejecución lo normal es un solo repositorio: "1 repositorios"
553
+ // es lo primero que lee quien acaba de instalar.
554
+ console.log(repos.size === 1
555
+ ? (0, index_js_1.tr) `Git: 1 repositorio · ${savedCommits} commits nuevos`
556
+ : (0, index_js_1.tr) `Git: ${repos.size} repositorios · ${savedCommits} commits nuevos`);
413
557
  // Agrupar en bloques, fusionar por rama y día, e imputar a los proyectos.
414
558
  // Trabajo deducido de los agentes, y además el de los commits que no
415
559
  // solapan con ninguno: quien programa sin IA solo deja ese rastro.
@@ -425,7 +569,7 @@ async function cmdImport(args, dbPath) {
425
569
  const porProyecto = (b) => (b.repoPath ? store.projectForRepo(db, b.repoPath) ?? b.repoPath : "");
426
570
  const blocks = (0, sessionize_js_1.groupByBranchAndDay)(raw, porProyecto);
427
571
  if (fromCommits.length) {
428
- console.log(` ${fromCommits.length} bloques deducidos solo de commits (sin agente)`);
572
+ console.log((0, index_js_1.tr) ` ${fromCommits.length} bloques deducidos solo de commits (sin agente)`);
429
573
  }
430
574
  let entries = 0;
431
575
  let unassigned = 0;
@@ -463,11 +607,11 @@ async function cmdImport(args, dbPath) {
463
607
  store.saveTimeEntry(db, { ...entry, id: `te_${projectId}_${day}_${slug}` });
464
608
  entries++;
465
609
  }
466
- console.log(`Bloques: ${raw.length} detectados → ${blocks.length} tras agrupar por rama y día · ` +
467
- `${entries} imputados · ${unassigned} sin proyecto`);
610
+ console.log((0, index_js_1.tr) `Bloques: ${raw.length} detectados → ${blocks.length} tras agrupar por rama y día · ` +
611
+ (0, index_js_1.tr) `${entries} imputados · ${unassigned} sin proyecto`);
468
612
  if (unassigned > 0) {
469
- console.log(`\nHay ${unassigned} bloques sin proyecto asignado. Regístralos con:`);
470
- console.log(` estela project add --id <id> --client <id> --name <nombre> --repo <ruta>`);
613
+ console.log((0, index_js_1.tr) `\nHay ${unassigned} bloques sin proyecto asignado. Regístralos con:`);
614
+ console.log((0, index_js_1.tr) ` estela project add --id <id> --client <id> --name <nombre> --repo <ruta>`);
471
615
  }
472
616
  }
473
617
  finally {
@@ -500,7 +644,7 @@ function cmdClientAdd(args, dbPath) {
500
644
  ...(str(args, "tax-id") ? { taxId: str(args, "tax-id") } : {}),
501
645
  ...(str(args, "email") ? { email: str(args, "email") } : {}),
502
646
  });
503
- console.log(`Cliente "${required(args, "name")}" guardado. Factura en ${currency}.`);
647
+ console.log((0, index_js_1.tr) `Cliente "${required(args, "name")}" guardado. Factura en ${currency}.`);
504
648
  }
505
649
  finally {
506
650
  db.close();
@@ -511,7 +655,7 @@ function cmdProjectAdd(args, dbPath) {
511
655
  try {
512
656
  const clientId = required(args, "client");
513
657
  if (!store.getClient(db, clientId)) {
514
- throw new UserError(`No existe el cliente "${clientId}". Créalo primero con: estela client add`);
658
+ throw new UserError((0, index_js_1.tr) `No existe el cliente "${clientId}". Créalo primero con: estela client add`);
515
659
  }
516
660
  const repo = str(args, "repo");
517
661
  store.upsertProject(db, {
@@ -524,9 +668,9 @@ function cmdProjectAdd(args, dbPath) {
524
668
  aiCostPolicy: (str(args, "ai-cost") ?? "absorbed"),
525
669
  kind: (str(args, "kind") ?? "client"),
526
670
  });
527
- console.log(`Proyecto "${required(args, "name")}" guardado.`);
671
+ console.log((0, index_js_1.tr) `Proyecto "${required(args, "name")}" guardado.`);
528
672
  if (repo)
529
- console.log(` repositorio: ${(0, node_path_1.resolve)(repo)}`);
673
+ console.log((0, index_js_1.tr) ` repositorio: ${(0, node_path_1.resolve)(repo)}`);
530
674
  }
531
675
  finally {
532
676
  db.close();
@@ -548,27 +692,27 @@ function cmdBudget(args, dbPath) {
548
692
  const projectId = required(args, "project");
549
693
  const project = store.getProject(db, projectId);
550
694
  if (!project)
551
- throw new UserError(`No existe el proyecto "${projectId}".`);
695
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
552
696
  const raw = str(args, "amount");
553
697
  if (raw === undefined) {
554
698
  const current = db.prepare("SELECT ai_budget_micro_usd AS b FROM projects WHERE id = ?").get(projectId);
555
699
  console.log(current.b === null
556
- ? `"${project.name}" no tiene presupuesto de IA.`
557
- : `"${project.name}": $${(current.b / 1e6).toFixed(2)} al mes.`);
700
+ ? (0, index_js_1.tr) `"${project.name}" no tiene presupuesto de IA.`
701
+ : (0, index_js_1.tr) `"${project.name}": $${(current.b / 1e6).toFixed(2)} al mes.`);
558
702
  return;
559
703
  }
560
704
  if (raw === "none") {
561
705
  db.prepare("UPDATE projects SET ai_budget_micro_usd = NULL WHERE id = ?").run(projectId);
562
- console.log(`Presupuesto de "${project.name}" retirado.`);
706
+ console.log((0, index_js_1.tr) `Presupuesto de "${project.name}" retirado.`);
563
707
  return;
564
708
  }
565
709
  const dollars = Number(raw.replace(",", "."));
566
710
  if (!Number.isFinite(dollars) || dollars < 0) {
567
- throw new UserError(`Importe inválido: ${raw}`);
711
+ throw new UserError((0, index_js_1.tr) `Importe inválido: ${raw}`);
568
712
  }
569
713
  db.prepare("UPDATE projects SET ai_budget_micro_usd = ? WHERE id = ?")
570
714
  .run(Math.round(dollars * 1e6), projectId);
571
- console.log(`Presupuesto de IA de "${project.name}": $${dollars.toFixed(2)} al mes.`);
715
+ console.log((0, index_js_1.tr) `Presupuesto de IA de "${project.name}": $${dollars.toFixed(2)} al mes.`);
572
716
  }
573
717
  finally {
574
718
  db.close();
@@ -580,15 +724,15 @@ function cmdRateSet(args, dbPath) {
580
724
  const projectId = required(args, "project");
581
725
  const project = store.getProject(db, projectId);
582
726
  if (!project)
583
- throw new UserError(`No existe el proyecto "${projectId}".`);
727
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
584
728
  const client = store.getClient(db, project.clientId);
585
729
  const currency = str(args, "currency")?.toUpperCase() ?? client.currency;
586
730
  const rate = (0, shared_1.parseMoney)(required(args, "rate"), currency);
587
731
  const fromRaw = str(args, "from");
588
732
  const effectiveFrom = fromRaw ? new Date(`${fromRaw}T00:00:00Z`) : new Date(0);
589
733
  store.addRatePeriod(db, { projectId, hourlyRate: rate, effectiveFrom, effectiveTo: null });
590
- console.log(`Tarifa de "${project.name}": ${(0, shared_1.formatMoney)(rate)}/hora` +
591
- (fromRaw ? ` desde ${fromRaw}.` : " (aplica a todo el histórico)."));
734
+ console.log((0, index_js_1.tr) `Tarifa de "${project.name}": ${(0, shared_1.formatMoney)(rate)}/hora` +
735
+ (fromRaw ? (0, index_js_1.tr) ` desde ${fromRaw}.` : (0, index_js_1.tr) ` (aplica a todo el histórico).`));
592
736
  }
593
737
  finally {
594
738
  db.close();
@@ -598,15 +742,15 @@ function cmdStatus(dbPath) {
598
742
  const db = (0, schema_js_1.openDatabase)(dbPath);
599
743
  try {
600
744
  const turns = db.prepare("SELECT COUNT(*) n, SUM(cost_micro_usd) c, MIN(at) a, MAX(at) b FROM agent_turns").get();
601
- console.log(`Base de datos: ${dbPath}\n`);
602
- console.log(`Turnos de agente: ${turns.n}`);
745
+ console.log((0, index_js_1.tr) `Base de datos: ${dbPath}\n`);
746
+ console.log((0, index_js_1.tr) `Turnos de agente: ${turns.n}`);
603
747
  if (turns.a)
604
- console.log(`Periodo: ${turns.a.slice(0, 10)} → ${turns.b.slice(0, 10)}`);
605
- console.log(`Coste de IA total: ${(0, shared_1.formatAiCost)({ microUsd: turns.c ?? 0 })}\n`);
748
+ console.log((0, index_js_1.tr) `Periodo: ${turns.a.slice(0, 10)} → ${turns.b.slice(0, 10)}`);
749
+ console.log((0, index_js_1.tr) `Coste de IA total: ${(0, shared_1.formatAiCost)({ microUsd: turns.c ?? 0 })}\n`);
606
750
  const clients = store.listClients(db);
607
751
  if (clients.length === 0) {
608
- console.log("Sin clientes configurados. Empieza con:");
609
- console.log(" estela client add --id <id> --name <nombre> --currency EUR");
752
+ console.log((0, index_js_1.tr) `Sin clientes configurados. Empieza con:`);
753
+ console.log((0, index_js_1.tr) ` estela client add --id <id> --name <nombre> --currency EUR`);
610
754
  return;
611
755
  }
612
756
  for (const client of clients) {
@@ -618,13 +762,13 @@ function cmdStatus(dbPath) {
618
762
  const ai = pending.reduce((s, e) => s + e.aiCost.microUsd, 0);
619
763
  const rate = (0, invoice_js_1.rateAt)(store.getRates(db, project.id), project.id, new Date());
620
764
  console.log(` ${project.name}`);
621
- console.log(` tarifa: ${rate ? `${(0, shared_1.formatMoney)(rate)}/h` : "— sin definir —"}`);
622
- console.log(` sin facturar: ${(0, shared_1.formatDuration)(seconds)} en ${pending.length} bloques`);
765
+ console.log((0, index_js_1.tr) ` tarifa: ${rate ? `${(0, shared_1.formatMoney)(rate)}/h` : (0, index_js_1.tr) `— sin definir —`}`);
766
+ console.log((0, index_js_1.tr) ` sin facturar: ${(0, shared_1.formatDuration)(seconds)} en ${pending.length} bloques`);
623
767
  if (rate && seconds > 0) {
624
768
  const amount = { amount: Math.round((rate.amount * seconds) / 3600), currency: rate.currency };
625
- console.log(` importe: ${(0, shared_1.formatMoney)(amount)}`);
769
+ console.log((0, index_js_1.tr) ` importe: ${(0, shared_1.formatMoney)(amount)}`);
626
770
  }
627
- console.log(` coste de IA: ${(0, shared_1.formatAiCost)({ microUsd: ai })}`);
771
+ console.log((0, index_js_1.tr) ` coste de IA: ${(0, shared_1.formatAiCost)({ microUsd: ai })}`);
628
772
  }
629
773
  console.log("");
630
774
  }
@@ -644,15 +788,15 @@ function cmdEntries(args, dbPath) {
644
788
  const projectId = required(args, "project");
645
789
  const project = store.getProject(db, projectId);
646
790
  if (!project)
647
- throw new UserError(`No existe el proyecto "${projectId}".`);
791
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
648
792
  const entries = store.getTimeEntries(db, projectId);
649
793
  if (entries.length === 0) {
650
- console.log("Sin bloques imputados. Ejecuta: estela import");
794
+ console.log((0, index_js_1.tr) `Sin bloques imputados. Ejecuta: estela import`);
651
795
  return;
652
796
  }
653
797
  console.log(`${project.name}\n`);
654
798
  for (const e of entries) {
655
- const flag = e.invoiceId ? "facturado" : e.billable ? "pendiente" : "no facturable";
799
+ const flag = e.invoiceId ? (0, index_js_1.tr) `facturado` : e.billable ? (0, index_js_1.tr) `pendiente` : (0, index_js_1.tr) `no facturable`;
656
800
  console.log(`${e.startedAt.toISOString().slice(0, 16).replace("T", " ")} ` +
657
801
  `${(0, shared_1.formatDuration)(e.seconds).padStart(8)} ` +
658
802
  `${(0, shared_1.formatAiCost)(e.aiCost).padStart(9)} ` +
@@ -675,7 +819,7 @@ function cmdShare(args, dbPath) {
675
819
  const projectId = required(args, "project");
676
820
  const project = store.getProject(db, projectId);
677
821
  if (!project)
678
- throw new UserError(`No existe el proyecto "${projectId}".`);
822
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
679
823
  const client = store.getClient(db, project.clientId);
680
824
  const to = str(args, "to") ?? (0, shared_1.localDate)(new Date());
681
825
  const from = str(args, "from") ?? to.slice(0, 8) + "01";
@@ -696,12 +840,12 @@ function cmdShare(args, dbPath) {
696
840
  });
697
841
  const out = str(args, "out") ?? `informe-${projectId}-${to}.html`;
698
842
  (0, node_fs_1.writeFileSync)(out, html, "utf8");
699
- console.log(`\nInforme: ${out}`);
700
- console.log(` ${project.name} · ${client.name} · ${from} al ${to}`);
701
- console.log(` Un solo fichero. Se abre sin instalar nada y sin depender de tu máquina.`);
843
+ console.log((0, index_js_1.tr) `\nInforme: ${out}`);
844
+ console.log((0, index_js_1.tr) ` ${project.name} · ${client.name} · ${from} al ${to}`);
845
+ console.log((0, index_js_1.tr) ` Un solo fichero. Se abre sin instalar nada y sin depender de tu máquina.`);
702
846
  if (args.flags["with-amounts"] !== true) {
703
- console.log(`\n Incluye horas y commits. Sin importes (añade --with-amounts) y sin`);
704
- console.log(` consumo de IA, que es tuyo mientras la pagues tú.`);
847
+ console.log((0, index_js_1.tr) `\n Incluye horas y commits. Sin importes (añade --with-amounts) y sin`);
848
+ console.log((0, index_js_1.tr) ` consumo de IA, que es tuyo mientras la pagues tú.`);
705
849
  }
706
850
  }
707
851
  finally {
@@ -722,28 +866,28 @@ function cmdLog(args, dbPath) {
722
866
  const projectId = required(args, "project");
723
867
  const project = store.getProject(db, projectId);
724
868
  if (!project) {
725
- const known = store.listProjects(db).map((p) => p.id).join(", ") || "(ninguno)";
726
- throw new UserError(`No existe el proyecto "${projectId}". Los que hay: ${known}`);
869
+ const known = store.listProjects(db).map((p) => p.id).join(", ") || (0, index_js_1.tr) `(ninguno)`;
870
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}". Los que hay: ${known}`);
727
871
  }
728
872
  const kind = (str(args, "kind") ?? "meeting");
729
873
  if (!(kind in shared_1.WORK_KIND_LABELS)) {
730
- throw new UserError(`Tipo "${kind}" no reconocido. Usa uno de: ${Object.keys(shared_1.WORK_KIND_LABELS).join(", ")}`);
874
+ throw new UserError((0, index_js_1.tr) `Tipo "${kind}" no reconocido. Usa uno de: ${Object.keys(shared_1.WORK_KIND_LABELS).join(", ")}`);
731
875
  }
732
876
  const hoursRaw = str(args, "hours");
733
877
  const minutesRaw = str(args, "minutes");
734
878
  if (!hoursRaw && !minutesRaw)
735
- throw new UserError("Indica --hours o --minutes");
879
+ throw new UserError((0, index_js_1.tr) `Indica --hours o --minutes`);
736
880
  const seconds = Math.round((hoursRaw ? Number(hoursRaw.replace(",", ".")) * 3600 : 0) +
737
881
  (minutesRaw ? Number(minutesRaw) * 60 : 0));
738
882
  if (!Number.isFinite(seconds) || seconds <= 0) {
739
- throw new UserError(`Duración inválida: ${hoursRaw ?? minutesRaw}`);
883
+ throw new UserError((0, index_js_1.tr) `Duración inválida: ${hoursRaw ?? minutesRaw}`);
740
884
  }
741
885
  const day = str(args, "date") ?? (0, shared_1.localDate)(new Date());
742
886
  if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
743
- throw new UserError(`Fecha inválida: ${day}`);
887
+ throw new UserError((0, index_js_1.tr) `Fecha inválida: ${day}`);
744
888
  // A media mañana: no sabemos la hora real y fingir precisión sería peor.
745
889
  const startedAt = new Date(`${day}T10:00:00`);
746
- const what = str(args, "what") ?? shared_1.WORK_KIND_LABELS[kind];
890
+ const what = str(args, "what") ?? kindLabel(kind);
747
891
  const id = store.saveTimeEntry(db, {
748
892
  projectId,
749
893
  startedAt,
@@ -764,10 +908,10 @@ function cmdLog(args, dbPath) {
764
908
  const amount = rate
765
909
  ? ` · ${(0, shared_1.formatMoney)({ amount: Math.round((rate.amount * seconds) / 3600), currency: rate.currency })}`
766
910
  : "";
767
- console.log(`\n${shared_1.WORK_KIND_LABELS[kind]} · ${(0, shared_1.formatDuration)(seconds)}${amount}`);
911
+ console.log(`\n${kindLabel(kind)} · ${(0, shared_1.formatDuration)(seconds)}${amount}`);
768
912
  console.log(` ${what}`);
769
913
  console.log(` ${project.name} · ${day}`);
770
- console.log(`\n Registro manual: un import no lo va a tocar. (${id})`);
914
+ console.log((0, index_js_1.tr) `\n Registro manual: un import no lo va a tocar. (${id})`);
771
915
  }
772
916
  finally {
773
917
  db.close();
@@ -816,18 +960,18 @@ async function cmdPublish(args, dbPath) {
816
960
  throw new UserError(error.message);
817
961
  throw error;
818
962
  }
819
- console.log(`\nPanel publicado.\n`);
820
- console.log(` Enlace: ${result.url}\n`);
963
+ console.log((0, index_js_1.tr) `\nPanel publicado.\n`);
964
+ console.log((0, index_js_1.tr) ` Enlace: ${result.url}\n`);
821
965
  if (result.adopted) {
822
- console.log(` Este panel ya existía y queda vinculado a tu cuenta desde ahora.\n`);
966
+ console.log((0, index_js_1.tr) ` Este panel ya existía y queda vinculado a tu cuenta desde ahora.\n`);
823
967
  }
824
968
  if (clientes?.length) {
825
- console.log(` Compartido con: ${clientes.join(", ")}`);
826
- console.log(` Al entrar en getestela.dev/app con ese correo, lo verán ahí.\n`);
969
+ console.log((0, index_js_1.tr) ` Compartido con: ${clientes.join(", ")}`);
970
+ console.log((0, index_js_1.tr) ` Al entrar en getestela.dev/app con ese correo, lo verán ahí.\n`);
827
971
  }
828
- console.log(` El token es lo único que protege la página: quien tenga el enlace, entra.`);
829
- console.log(` Al volver a publicar desde esta máquina, este enlace se reusa solo.`);
830
- console.log(` Desde otra máquina, pasa --token ${result.token} o el cliente perderá su enlace.`);
972
+ console.log((0, index_js_1.tr) ` El token es lo único que protege la página: quien tenga el enlace, entra.`);
973
+ console.log((0, index_js_1.tr) ` Al volver a publicar desde esta máquina, este enlace se reusa solo.`);
974
+ console.log((0, index_js_1.tr) ` Desde otra máquina, pasa --token ${result.token} o el cliente perderá su enlace.`);
831
975
  }
832
976
  finally {
833
977
  db.close();
@@ -843,16 +987,16 @@ function cmdSyncEnable(args, dbPath) {
843
987
  try {
844
988
  const projectId = required(args, "project");
845
989
  if (!store.getProject(db, projectId)) {
846
- throw new UserError(`No existe el proyecto "${projectId}".`);
990
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
847
991
  }
848
992
  if (!store.listPersonalSyncCandidates(db).includes(projectId)) {
849
- throw new UserError(`"${projectId}" ya está enlazado a un equipo y no puede sincronizarse también como personal.`);
993
+ throw new UserError((0, index_js_1.tr) `"${projectId}" ya está enlazado a un equipo y no puede sincronizarse también como personal.`);
850
994
  }
851
995
  store.setProjectSync(db, {
852
996
  projectId, scope: "personal", remoteProjectId: projectId,
853
997
  remoteOrgId: null, inviteToken: null,
854
998
  });
855
- console.log(`Sync activado para "${projectId}". Ejecuta "estela sync" para subir y bajar.`);
999
+ console.log((0, index_js_1.tr) `Sync activado para "${projectId}". Ejecuta "estela sync" para subir y bajar.`);
856
1000
  }
857
1001
  finally {
858
1002
  db.close();
@@ -865,7 +1009,7 @@ async function cmdSync(args, dbPath) {
865
1009
  const only = str(args, "project");
866
1010
  const projectIds = only ? [only] : store.listProjectSyncs(db).map((s) => s.projectId);
867
1011
  if (projectIds.length === 0) {
868
- console.log(`Ningún proyecto sincroniza todavía. Actívalo con: estela sync enable --project <id>`);
1012
+ console.log((0, index_js_1.tr) `Ningún proyecto sincroniza todavía. Actívalo con: estela sync enable --project <id>`);
869
1013
  return;
870
1014
  }
871
1015
  for (const projectId of projectIds) {
@@ -875,11 +1019,11 @@ async function cmdSync(args, dbPath) {
875
1019
  try {
876
1020
  if (scope === "team") {
877
1021
  const outcome = await (0, team_js_1.syncTeamProject)(db, projectId);
878
- console.log(`${label}: ${(0, shared_1.formatDuration)(outcome.seconds)} totales enviadas`);
1022
+ console.log((0, index_js_1.tr) `${label}: ${(0, shared_1.formatDuration)(outcome.seconds)} totales enviadas`);
879
1023
  }
880
1024
  else {
881
1025
  const outcome = await (0, sync_js_1.syncProject)(db, projectId);
882
- console.log(`${label}: ${outcome.pushed} subidas, ${outcome.pulled} bajadas`);
1026
+ console.log((0, index_js_1.tr) `${label}: ${outcome.pushed} subidas, ${outcome.pulled} bajadas`);
883
1027
  }
884
1028
  }
885
1029
  catch (error) {
@@ -907,7 +1051,7 @@ async function cmdTeamInvite(args, dbPath) {
907
1051
  const emails = required(args, "email").split(",").map((e) => e.trim()).filter(Boolean);
908
1052
  const kind = (str(args, "kind") ?? "employee");
909
1053
  if (kind !== "employee" && kind !== "freelancer") {
910
- throw new UserError(`--kind debe ser "employee" o "freelancer".`);
1054
+ throw new UserError((0, index_js_1.tr) `--kind debe ser "employee" o "freelancer".`);
911
1055
  }
912
1056
  let result;
913
1057
  try {
@@ -918,10 +1062,10 @@ async function cmdTeamInvite(args, dbPath) {
918
1062
  throw new UserError(error.message);
919
1063
  throw error;
920
1064
  }
921
- console.log(`\nInvitación creada para: ${emails.join(", ")}\n`);
922
- console.log(` Pásale esto (por el canal que uses con él, no hace falta correo):\n`);
923
- console.log(` estela login --email <su-correo>`);
924
- console.log(` estela team accept --token ${result.token} --as-id <id-que-elija>\n`);
1065
+ console.log((0, index_js_1.tr) `\nInvitación creada para: ${emails.join(", ")}\n`);
1066
+ console.log((0, index_js_1.tr) ` Pásale esto (por el canal que uses con él, no hace falta correo):\n`);
1067
+ console.log((0, index_js_1.tr) ` estela login --email <su-correo>`);
1068
+ console.log((0, index_js_1.tr) ` estela team accept --token ${result.token} --as-id <id-que-elija>\n`);
925
1069
  }
926
1070
  finally {
927
1071
  db.close();
@@ -949,23 +1093,23 @@ async function cmdTeamAiCost(args, dbPath) {
949
1093
  const allow = args.flags["allow"] === true;
950
1094
  const deny = args.flags["deny"] === true;
951
1095
  if (allow === deny) {
952
- throw new UserError("Elige una: --allow para compartirlo, --deny para no hacerlo.");
1096
+ throw new UserError((0, index_js_1.tr) `Elige una: --allow para compartirlo, --deny para no hacerlo.`);
953
1097
  }
954
1098
  const sync = store.getProjectSync(db, projectId);
955
1099
  if (!sync || sync.scope !== "team" || !sync.inviteToken) {
956
- throw new UserError(`"${projectId}" no es un proyecto de equipo.`);
1100
+ throw new UserError((0, index_js_1.tr) `"${projectId}" no es un proyecto de equipo.`);
957
1101
  }
958
1102
  const account = store.getCloudAccount(db);
959
1103
  if (!account)
960
- throw new UserError("Vincula la máquina con \"estela login\" antes.");
1104
+ throw new UserError((0, index_js_1.tr) `Vincula la máquina con "estela login" antes.`);
961
1105
  const decision = allow ? "granted" : "declined";
962
1106
  await (0, client_js_1.cloudPost)(account.apiBaseUrl, "/team/ai-cost/decision", { token: sync.inviteToken, decision }, { deviceToken: account.deviceToken });
963
1107
  store.setAiConsent(db, projectId, false, decision);
964
1108
  console.log(allow
965
- ? `\nHecho. El coste de IA de "${projectId}" se sumará al del proyecto en el ` +
966
- `siguiente "estela sync".\n\n Nunca se enseña por persona, solo el total del proyecto.\n`
967
- : `\nHecho. El coste de IA de "${projectId}" NO se comparte, y se avisa a quien lo pidió.\n\n` +
968
- ` Tus horas se siguen midiendo igual: esto solo afecta al gasto de IA.\n`);
1109
+ ? (0, index_js_1.tr) `\nHecho. El coste de IA de "${projectId}" se sumará al del proyecto en el ` +
1110
+ (0, index_js_1.tr) `siguiente "estela sync".\n\n Nunca se enseña por persona, solo el total del proyecto.\n`
1111
+ : (0, index_js_1.tr) `\nHecho. El coste de IA de "${projectId}" NO se comparte, y se avisa a quien lo pidió.\n\n` +
1112
+ (0, index_js_1.tr) ` Tus horas se siguen midiendo igual: esto solo afecta al gasto de IA.\n`);
969
1113
  }
970
1114
  finally {
971
1115
  db.close();
@@ -987,14 +1131,14 @@ async function cmdTeamAccept(args, dbPath) {
987
1131
  throw new UserError(error.message);
988
1132
  throw error;
989
1133
  }
990
- console.log(`\nInvitación aceptada: "${result.projectName}"`);
991
- console.log(` Declarada para: ${result.emails.join(", ")} (${result.inviteeKind})`);
992
- console.log(` Si no eres tú, avisa a quien te invitó.\n`);
993
- console.log(`En esta máquina se llama "${result.localProjectId}".\n`);
994
- console.log(`Ahora vincula tu repositorio local y sincroniza:\n`);
995
- console.log(` estela team repo --project ${result.localProjectId} --add <ruta-de-tu-clon>`);
996
- console.log(` estela import`);
997
- console.log(` estela sync\n`);
1134
+ console.log((0, index_js_1.tr) `\nInvitación aceptada: "${result.projectName}"`);
1135
+ console.log((0, index_js_1.tr) ` Declarada para: ${result.emails.join(", ")} (${result.inviteeKind})`);
1136
+ console.log((0, index_js_1.tr) ` Si no eres tú, avisa a quien te invitó.\n`);
1137
+ console.log((0, index_js_1.tr) `En esta máquina se llama "${result.localProjectId}".\n`);
1138
+ console.log((0, index_js_1.tr) `Ahora vincula tu repositorio local y sincroniza:\n`);
1139
+ console.log((0, index_js_1.tr) ` estela team repo --project ${result.localProjectId} --add <ruta-de-tu-clon>`);
1140
+ console.log((0, index_js_1.tr) ` estela import`);
1141
+ console.log((0, index_js_1.tr) ` estela sync\n`);
998
1142
  }
999
1143
  finally {
1000
1144
  db.close();
@@ -1007,9 +1151,9 @@ function cmdTeamRepo(args, dbPath) {
1007
1151
  const projectId = required(args, "project");
1008
1152
  const repo = required(args, "add");
1009
1153
  if (!store.getProject(db, projectId))
1010
- throw new UserError(`No existe el proyecto "${projectId}".`);
1154
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
1011
1155
  store.addProjectRepo(db, projectId, (0, node_path_1.resolve)(repo));
1012
- console.log(`Repositorio vinculado a "${projectId}": ${(0, node_path_1.resolve)(repo)}`);
1156
+ console.log((0, index_js_1.tr) `Repositorio vinculado a "${projectId}": ${(0, node_path_1.resolve)(repo)}`);
1013
1157
  }
1014
1158
  finally {
1015
1159
  db.close();
@@ -1030,11 +1174,11 @@ async function cmdTeamList(args, dbPath) {
1030
1174
  throw error;
1031
1175
  }
1032
1176
  if (members.length === 0) {
1033
- console.log(`Nadie ha aceptado todavía en "${projectId}".`);
1177
+ console.log((0, index_js_1.tr) `Nadie ha aceptado todavía en "${projectId}".`);
1034
1178
  return;
1035
1179
  }
1036
1180
  for (const m of members) {
1037
- const synced = m.syncedAt ? `sincronizado ${m.syncedAt.slice(0, 10)}` : "sin sincronizar aún";
1181
+ const synced = m.syncedAt ? (0, index_js_1.tr) `sincronizado ${m.syncedAt.slice(0, 10)}` : (0, index_js_1.tr) `sin sincronizar aún`;
1038
1182
  console.log(` ${m.emails.join(", ")} (${m.inviteeKind}) · ${(0, shared_1.formatDuration)(m.seconds)} · ${synced}`);
1039
1183
  }
1040
1184
  }
@@ -1055,7 +1199,7 @@ async function cmdTeamRevoke(args, dbPath) {
1055
1199
  throw new UserError(error.message);
1056
1200
  throw error;
1057
1201
  }
1058
- console.log(`Invitación revocada.`);
1202
+ console.log((0, index_js_1.tr) `Invitación revocada.`);
1059
1203
  }
1060
1204
  finally {
1061
1205
  db.close();
@@ -1067,7 +1211,7 @@ async function cmdUpgrade(args, dbPath) {
1067
1211
  try {
1068
1212
  const plan = required(args, "plan");
1069
1213
  if (plan !== "pro" && plan !== "teams")
1070
- throw new UserError(`--plan debe ser "pro" o "teams".`);
1214
+ throw new UserError((0, index_js_1.tr) `--plan debe ser "pro" o "teams".`);
1071
1215
  const interval = args.flags["annual"] ? "yearly" : "monthly";
1072
1216
  let result;
1073
1217
  try {
@@ -1078,7 +1222,7 @@ async function cmdUpgrade(args, dbPath) {
1078
1222
  throw new UserError(error.message);
1079
1223
  throw error;
1080
1224
  }
1081
- console.log(`\nAbre esto para completar el alta a ${plan === "pro" ? "Pro" : "Teams"}` +
1225
+ console.log((0, index_js_1.tr) `\nAbre esto para completar el alta a ${plan === "pro" ? "Pro" : "Teams"}` +
1082
1226
  `${interval === "yearly" ? " (anual)" : ""}:\n`);
1083
1227
  console.log(` ${result.url}\n`);
1084
1228
  }
@@ -1099,7 +1243,7 @@ async function cmdBillingPortal(dbPath) {
1099
1243
  throw new UserError(error.message);
1100
1244
  throw error;
1101
1245
  }
1102
- console.log(`\nGestiona tu pago aquí:\n\n ${result.url}\n`);
1246
+ console.log((0, index_js_1.tr) `\nGestiona tu pago aquí:\n\n ${result.url}\n`);
1103
1247
  }
1104
1248
  finally {
1105
1249
  db.close();
@@ -1111,8 +1255,8 @@ async function cmdAccount(dbPath) {
1111
1255
  try {
1112
1256
  const account = store.getCloudAccount(db);
1113
1257
  if (!account) {
1114
- throw new UserError(`Esta máquina no está vinculada a ninguna cuenta. Vincúlala con:\n\n` +
1115
- ` estela login --email tu@correo.com\n`);
1258
+ throw new UserError((0, index_js_1.tr) `Esta máquina no está vinculada a ninguna cuenta. Vincúlala con:\n\n` +
1259
+ (0, index_js_1.tr) ` estela login --email tu@correo.com\n`);
1116
1260
  }
1117
1261
  let info;
1118
1262
  try {
@@ -1120,13 +1264,13 @@ async function cmdAccount(dbPath) {
1120
1264
  }
1121
1265
  catch (error) {
1122
1266
  if (error instanceof client_js_1.CloudError && error.status === 401) {
1123
- throw new UserError(`Tu sesión ya no vale. Vuelve a vincular la máquina:\n\n estela login --email tu@correo.com\n`);
1267
+ throw new UserError((0, index_js_1.tr) `Tu sesión ya no vale. Vuelve a vincular la máquina:\n\n estela login --email tu@correo.com\n`);
1124
1268
  }
1125
1269
  throw error;
1126
1270
  }
1127
- console.log(`\n${info.email} · plan ${info.plan}\n`);
1271
+ console.log((0, index_js_1.tr) `\n${info.email} · plan ${info.plan}\n`);
1128
1272
  for (const d of info.devices) {
1129
- console.log(` ${d.label ?? "(sin nombre)"} · v${d.clientVersion ?? "?"} · visto ${d.lastSeenAt?.slice(0, 10) ?? "nunca"}`);
1273
+ console.log((0, index_js_1.tr) ` ${d.label ?? (0, index_js_1.tr) `(sin nombre)`} · v${d.clientVersion ?? "?"} · visto ${d.lastSeenAt?.slice(0, 10) ?? (0, index_js_1.tr) `nunca`}`);
1130
1274
  }
1131
1275
  }
1132
1276
  finally {
@@ -1146,25 +1290,25 @@ async function cmdAuthor(args, dbPath) {
1146
1290
  const projectId = required(args, "project");
1147
1291
  const project = store.getProject(db, projectId);
1148
1292
  if (!project)
1149
- throw new UserError(`No existe el proyecto "${projectId}".`);
1293
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
1150
1294
  const emails = (str(args, "email") ?? "").split(",").map((e) => e.trim()).filter(Boolean);
1151
1295
  if (emails.length === 0) {
1152
1296
  const repo = project.repoPaths[0];
1153
1297
  if (!repo)
1154
- throw new UserError("El proyecto no tiene repositorio asignado.");
1155
- console.log(`\nAutores en ${project.name}:\n`);
1298
+ throw new UserError((0, index_js_1.tr) `El proyecto no tiene repositorio asignado.`);
1299
+ console.log((0, index_js_1.tr) `\nAutores en ${project.name}:\n`);
1156
1300
  for (const a of await (0, git_js_1.repoAuthors)(repo)) {
1157
- console.log(` ${a.email.padEnd(38)} ${String(a.commits).padStart(5)} commits, hasta ${a.lastAt}`);
1301
+ console.log((0, index_js_1.tr) ` ${a.email.padEnd(38)} ${String(a.commits).padStart(5)} commits, hasta ${a.lastAt}`);
1158
1302
  }
1159
1303
  const current = store.getProjectAuthors(db, projectId);
1160
- console.log(`\nConfigurado ahora: ${current.length ? current.join(", ") : "— nada —"}`);
1161
- console.log(`\nElige el tuyo con:`);
1162
- console.log(` estela author --project ${projectId} --email tu@correo.com`);
1304
+ console.log((0, index_js_1.tr) `\nConfigurado ahora: ${current.length ? current.join(", ") : (0, index_js_1.tr) `— nada —`}`);
1305
+ console.log((0, index_js_1.tr) `\nElige el tuyo con:`);
1306
+ console.log((0, index_js_1.tr) ` estela author --project ${projectId} --email tu@correo.com`);
1163
1307
  return;
1164
1308
  }
1165
1309
  store.setProjectAuthors(db, projectId, emails);
1166
- console.log(`\n${project.name}: commits filtrados por ${emails.join(", ")}`);
1167
- console.log(`Ejecuta "estela import" para recoger los que faltaban.`);
1310
+ console.log((0, index_js_1.tr) `\n${project.name}: commits filtrados por ${emails.join(", ")}`);
1311
+ console.log((0, index_js_1.tr) `Ejecuta "estela import" para recoger los que faltaban.`);
1168
1312
  }
1169
1313
  finally {
1170
1314
  db.close();
@@ -1190,14 +1334,14 @@ function cmdExport(args, dbPath) {
1190
1334
  const projectId = required(args, "project");
1191
1335
  const project = store.getProject(db, projectId);
1192
1336
  if (!project)
1193
- throw new UserError(`No existe el proyecto "${projectId}".`);
1337
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
1194
1338
  const client = store.getClient(db, project.clientId);
1195
1339
  const rates = store.getRates(db, projectId);
1196
1340
  const csv = (0, csv_js_1.timeEntriesToCsv)(store.getTimeEntries(db, projectId), project, client, (at) => (0, invoice_js_1.rateAt)(rates, projectId, at));
1197
1341
  const out = str(args, "out");
1198
1342
  if (out) {
1199
1343
  (0, node_fs_1.writeFileSync)(out, csv, "utf8");
1200
- console.log(`CSV: ${out}`);
1344
+ console.log((0, index_js_1.tr) `CSV: ${out}`);
1201
1345
  }
1202
1346
  else {
1203
1347
  process.stdout.write(csv);
@@ -1220,8 +1364,8 @@ function cmdSubscriptionAdd(args, dbPath) {
1220
1364
  effectiveFrom: fromRaw ? new Date(`${fromRaw}T00:00:00Z`) : new Date(0),
1221
1365
  effectiveTo: null,
1222
1366
  });
1223
- console.log(`Suscripción "${str(args, "name") ?? required(args, "id")}": ${(0, shared_1.formatMoney)(fee)}/mes.`);
1224
- console.log("Se repartirá entre proyectos según lo que consumió cada uno.");
1367
+ console.log((0, index_js_1.tr) `Suscripción "${str(args, "name") ?? required(args, "id")}": ${(0, shared_1.formatMoney)(fee)}/mes.`);
1368
+ console.log((0, index_js_1.tr) `Se repartirá entre proyectos según lo que consumió cada uno.`);
1225
1369
  }
1226
1370
  finally {
1227
1371
  db.close();
@@ -1234,13 +1378,13 @@ function cmdAiCost(dbPath) {
1234
1378
  const subs = store.listSubscriptions(db);
1235
1379
  const rows = store.consumptionByProjectMonth(db);
1236
1380
  if (rows.length === 0) {
1237
- console.log("Sin consumo registrado. Ejecuta: estela import");
1381
+ console.log((0, index_js_1.tr) `Sin consumo registrado. Ejecuta: estela import`);
1238
1382
  return;
1239
1383
  }
1240
1384
  if (subs.length === 0) {
1241
- console.log("Sin suscripciones registradas: el coste se muestra a tarifa API.\n");
1242
- console.log("Si pagas cuota fija, regístrala para ver el gasto real:");
1243
- console.log(' estela subscription add --id claude-max --name "Claude Max" --fee 200\n');
1385
+ console.log((0, index_js_1.tr) `Sin suscripciones registradas: el coste se muestra a tarifa API.\n`);
1386
+ console.log((0, index_js_1.tr) `Si pagas cuota fija, regístrala para ver el gasto real:`);
1387
+ console.log((0, index_js_1.tr) ` estela subscription add --id claude-max --name "Claude Max" --fee 200\n`);
1244
1388
  }
1245
1389
  const shares = (0, amortize_js_1.amortize)(rows, subs, store.totalConsumptionByMonth(db));
1246
1390
  const names = new Map(store.listProjects(db).map((p) => [p.id, p.name]));
@@ -1248,7 +1392,7 @@ function cmdAiCost(dbPath) {
1248
1392
  for (const s of shares) {
1249
1393
  if (s.month !== month) {
1250
1394
  month = s.month;
1251
- console.log(`\n${month} (consumo total: ${(0, shared_1.formatAiCost)(s.monthTotal)} equiv. API)`);
1395
+ console.log((0, index_js_1.tr) `\n${month} (consumo total: ${(0, shared_1.formatAiCost)(s.monthTotal)} equiv. API)`);
1252
1396
  }
1253
1397
  const label = (names.get(s.projectId) ?? s.projectId).slice(0, 34).padEnd(36);
1254
1398
  const pct = `${(s.share * 100).toFixed(1)}%`.padStart(7);
@@ -1256,8 +1400,8 @@ function cmdAiCost(dbPath) {
1256
1400
  console.log(` ${label}${(0, shared_1.formatAiCost)(s.consumption).padStart(10)}${pct}${real}`);
1257
1401
  }
1258
1402
  if (subs.length > 0) {
1259
- console.log(`\nLa última columna es dinero real: tu cuota repartida por consumo.`);
1260
- console.log(`La primera es la tarifa API equivalente, útil solo como medida de uso.`);
1403
+ console.log((0, index_js_1.tr) `\nLa última columna es dinero real: tu cuota repartida por consumo.`);
1404
+ console.log((0, index_js_1.tr) `La primera es la tarifa API equivalente, útil solo como medida de uso.`);
1261
1405
  }
1262
1406
  }
1263
1407
  finally {
@@ -1270,12 +1414,12 @@ function cmdInvoice(args, dbPath) {
1270
1414
  const projectId = required(args, "project");
1271
1415
  const project = store.getProject(db, projectId);
1272
1416
  if (!project)
1273
- throw new UserError(`No existe el proyecto "${projectId}".`);
1417
+ throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
1274
1418
  const client = store.getClient(db, project.clientId);
1275
1419
  const cutoffRaw = required(args, "cutoff");
1276
1420
  const cutoffAt = new Date(`${cutoffRaw}T23:59:59Z`);
1277
1421
  if (Number.isNaN(cutoffAt.getTime()))
1278
- throw new UserError(`Fecha inválida: ${cutoffRaw}`);
1422
+ throw new UserError((0, index_js_1.tr) `Fecha inválida: ${cutoffRaw}`);
1279
1423
  const dryRun = args.flags["dry-run"] === true;
1280
1424
  const number = str(args, "number") ?? store.nextInvoiceNumber(db, "INF");
1281
1425
  const fx = str(args, "fx");
@@ -1303,16 +1447,16 @@ function cmdInvoice(args, dbPath) {
1303
1447
  // Coste real de IA del periodo: la cuota de tus suscripciones repartida
1304
1448
  // por consumo. Es lo que de verdad te costó, frente a la tarifa API.
1305
1449
  if (invoice.aiAmortized) {
1306
- console.log(`Coste real de IA imputado desde tu suscripción: ${(0, shared_1.formatMoney)(invoice.aiAmortized)}`);
1450
+ console.log((0, index_js_1.tr) `Coste real de IA imputado desde tu suscripción: ${(0, shared_1.formatMoney)(invoice.aiAmortized)}`);
1307
1451
  }
1308
1452
  if (subs.length === 0) {
1309
- console.log("\nSin suscripciones registradas. Si pagas cuota fija, el importe de arriba");
1310
- console.log("es tarifa API equivalente, no lo que gastaste. Regístrala con:");
1311
- console.log(' estela subscription add --id claude-max --name "Claude Max" --fee 200');
1453
+ console.log((0, index_js_1.tr) `\nSin suscripciones registradas. Si pagas cuota fija, el importe de arriba`);
1454
+ console.log((0, index_js_1.tr) `es tarifa API equivalente, no lo que gastaste. Regístrala con:`);
1455
+ console.log((0, index_js_1.tr) ` estela subscription add --id claude-max --name "Claude Max" --fee 200`);
1312
1456
  }
1313
1457
  if (fx) {
1314
1458
  const m = (0, invoice_js_1.marginOf)(invoice, Number(fx));
1315
- console.log(`\nMargen a tarifa API: ${(0, shared_1.formatMoney)(m.margin)} de ${(0, shared_1.formatMoney)(m.revenue)} ` +
1459
+ console.log((0, index_js_1.tr) `\nMargen a tarifa API: ${(0, shared_1.formatMoney)(m.margin)} de ${(0, shared_1.formatMoney)(m.revenue)} ` +
1316
1460
  `(${m.marginPct.toFixed(1)}%)`);
1317
1461
  }
1318
1462
  // --- Exportables ---------------------------------------------------------
@@ -1330,22 +1474,22 @@ function cmdInvoice(args, dbPath) {
1330
1474
  ...(invoice.aiAmortized ? { amortizedAiCost: invoice.aiAmortized } : {}),
1331
1475
  });
1332
1476
  (0, node_fs_1.writeFileSync)(pdfPath, pdf);
1333
- console.log(`\nPDF: ${pdfPath} (${(pdf.length / 1024).toFixed(1)} KB, ${invoice.lines.length} conceptos)`);
1477
+ console.log((0, index_js_1.tr) `\nPDF: ${pdfPath} (${(pdf.length / 1024).toFixed(1)} KB, ${invoice.lines.length} conceptos)`);
1334
1478
  }
1335
1479
  const csvPath = str(args, "csv");
1336
1480
  if (csvPath) {
1337
1481
  (0, node_fs_1.writeFileSync)(csvPath, (0, csv_js_1.invoiceToCsv)(invoice, client, project), "utf8");
1338
- console.log(`CSV: ${csvPath}`);
1482
+ console.log((0, index_js_1.tr) `CSV: ${csvPath}`);
1339
1483
  }
1340
1484
  if (dryRun) {
1341
- console.log("\n[--dry-run] No se guardó nada. Repite sin --dry-run para emitirlo.");
1485
+ console.log((0, index_js_1.tr) `\n[--dry-run] No se guardó nada. Repite sin --dry-run para emitirlo.`);
1342
1486
  return;
1343
1487
  }
1344
1488
  const billed = entries
1345
1489
  .filter((e) => e.billable && e.invoiceId === null && e.endedAt <= cutoffAt)
1346
1490
  .map((e) => e.id);
1347
1491
  store.saveInvoice(db, invoice, billed);
1348
- console.log(`\nInforme ${invoice.number} emitido. ${billed.length} bloques marcados como informados.`);
1492
+ console.log((0, index_js_1.tr) `\nInforme ${invoice.number} emitido. ${billed.length} bloques marcados como informados.`);
1349
1493
  }
1350
1494
  finally {
1351
1495
  db.close();
@@ -1362,6 +1506,9 @@ const VERSION = require("../package.json").version;
1362
1506
  async function main() {
1363
1507
  const argv = process.argv.slice(2);
1364
1508
  const args = parseArgs(argv);
1509
+ // Lo primero, antes de imprimir nada: hasta la ayuda sale ya en su idioma.
1510
+ (0, index_js_1.setLang)((0, index_js_1.detectLang)({ flag: str(args, "lang"), env: process.env, platform: process.platform }));
1511
+ (0, shared_1.setMoneyLocale)((0, index_js_1.getLang)() === "en" ? "en-US" : "es-EC");
1365
1512
  // Global y antes que nada: "estela --version" no debe abrir ninguna base de
1366
1513
  // datos ni comprobar el resto de argumentos para responder algo tan simple.
1367
1514
  if (args.flags["version"] === true) {
@@ -1471,10 +1618,10 @@ async function main() {
1471
1618
  break;
1472
1619
  case "":
1473
1620
  case "help":
1474
- console.log(HELP);
1621
+ console.log(helpText());
1475
1622
  break;
1476
1623
  default:
1477
- console.error(`Comando desconocido: "${command}"\n${HELP}`);
1624
+ console.error((0, index_js_1.tr) `Comando desconocido: "${command}"\n${helpText()}`);
1478
1625
  process.exitCode = 1;
1479
1626
  }
1480
1627
  }