estela 0.1.28 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -7
- package/dist/billing/invoice.d.ts.map +1 -1
- package/dist/billing/invoice.js +2 -1
- package/dist/billing/invoice.js.map +1 -1
- package/dist/billing/localize.d.ts +37 -0
- package/dist/billing/localize.d.ts.map +1 -0
- package/dist/billing/localize.js +72 -0
- package/dist/billing/localize.js.map +1 -0
- package/dist/billing/localize.test.d.ts +2 -0
- package/dist/billing/localize.test.d.ts.map +1 -0
- package/dist/billing/localize.test.js +144 -0
- package/dist/billing/localize.test.js.map +1 -0
- package/dist/billing/sessionize.d.ts.map +1 -1
- package/dist/billing/sessionize.js +4 -7
- package/dist/billing/sessionize.js.map +1 -1
- package/dist/cli-idioma.test.d.ts +2 -0
- package/dist/cli-idioma.test.d.ts.map +1 -0
- package/dist/cli-idioma.test.js +190 -0
- package/dist/cli-idioma.test.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +154 -51
- package/dist/cli.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +40 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/db/schema.test.js +294 -4
- package/dist/db/schema.test.js.map +1 -1
- package/dist/db/store.d.ts +50 -0
- package/dist/db/store.d.ts.map +1 -1
- package/dist/db/store.js +151 -5
- package/dist/db/store.js.map +1 -1
- package/dist/export/csv.d.ts.map +1 -1
- package/dist/export/csv.js +49 -18
- package/dist/export/csv.js.map +1 -1
- package/dist/export/dates.d.ts +13 -0
- package/dist/export/dates.d.ts.map +1 -0
- package/dist/export/dates.js +29 -0
- package/dist/export/dates.js.map +1 -0
- package/dist/export/idioma.test.d.ts +2 -0
- package/dist/export/idioma.test.d.ts.map +1 -0
- package/dist/export/idioma.test.js +251 -0
- package/dist/export/idioma.test.js.map +1 -0
- package/dist/export/invoice-pdf.d.ts.map +1 -1
- package/dist/export/invoice-pdf.js +26 -22
- package/dist/export/invoice-pdf.js.map +1 -1
- package/dist/export/panel.d.ts.map +1 -1
- package/dist/export/panel.js +165 -94
- package/dist/export/panel.js.map +1 -1
- package/dist/export/panel.test.js +15 -8
- package/dist/export/panel.test.js.map +1 -1
- package/dist/export/share.d.ts.map +1 -1
- package/dist/export/share.js +6 -28
- package/dist/export/share.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +90 -10
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/i18n.test.js +25 -0
- package/dist/i18n/i18n.test.js.map +1 -1
- package/dist/i18n/index.d.ts +15 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +20 -0
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/labels.d.ts +12 -0
- package/dist/i18n/labels.d.ts.map +1 -0
- package/dist/i18n/labels.js +25 -0
- package/dist/i18n/labels.js.map +1 -0
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +3 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/pricing/catalog.d.ts.map +1 -1
- package/dist/pricing/catalog.js +23 -0
- package/dist/pricing/catalog.js.map +1 -1
- package/dist/pricing/cost.test.js +26 -0
- package/dist/pricing/cost.test.js.map +1 -1
- package/dist/publish.d.ts +6 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +5 -2
- package/dist/publish.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +141 -31
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +87 -0
- package/dist/server.test.js.map +1 -1
- package/dist/watchers/agents.d.ts +37 -0
- package/dist/watchers/agents.d.ts.map +1 -0
- package/dist/watchers/agents.js +30 -0
- package/dist/watchers/agents.js.map +1 -0
- package/dist/watchers/agents.test.d.ts +2 -0
- package/dist/watchers/agents.test.d.ts.map +1 -0
- package/dist/watchers/agents.test.js +94 -0
- package/dist/watchers/agents.test.js.map +1 -0
- package/dist/watchers/codex.d.ts +3 -0
- package/dist/watchers/codex.d.ts.map +1 -0
- package/dist/watchers/codex.js +263 -0
- package/dist/watchers/codex.js.map +1 -0
- package/dist/watchers/codex.test.d.ts +2 -0
- package/dist/watchers/codex.test.d.ts.map +1 -0
- package/dist/watchers/codex.test.js +207 -0
- package/dist/watchers/codex.test.js.map +1 -0
- package/node_modules/@estela/shared/dist/types.d.ts +8 -0
- package/node_modules/@estela/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@estela/shared/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/web/app.css +32 -0
- package/web/app.js +92 -8
- package/web/i18n.js +30 -6
- package/web/index.html +3 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
40
|
+
const node_child_process_1 = require("node:child_process");
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const node_os_1 = require("node:os");
|
|
43
|
+
const node_path_1 = require("node:path");
|
|
44
|
+
const node_test_1 = require("node:test");
|
|
45
|
+
const schema_js_1 = require("./db/schema.js");
|
|
46
|
+
const store = __importStar(require("./db/store.js"));
|
|
47
|
+
/**
|
|
48
|
+
* El idioma de los documentos, ejecutando la CLI de verdad.
|
|
49
|
+
*
|
|
50
|
+
* Lo que se prueba aquí es lo que ve una persona: qué idioma sale en el fichero
|
|
51
|
+
* que le manda a su cliente, cuándo se le avisa de dónde fijarlo, y que volver a
|
|
52
|
+
* ejecutar `client add` para cambiar un correo no le cambie los PDFs de idioma
|
|
53
|
+
* sin decirle nada. Ver `documentLang` en i18n/index.ts.
|
|
54
|
+
*/
|
|
55
|
+
const BIN = (0, node_path_1.join)(__dirname, "bin.js");
|
|
56
|
+
function estela(db, lang, ...args) {
|
|
57
|
+
const r = (0, node_child_process_1.spawnSync)(process.execPath, [BIN, ...args, "--db", db], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
env: { ...process.env, ESTELA_LANG: lang, NO_COLOR: "1" },
|
|
60
|
+
});
|
|
61
|
+
return { code: r.status, out: `${r.stdout}${r.stderr}` };
|
|
62
|
+
}
|
|
63
|
+
/** Una base con un cliente, un proyecto con tarifa y un bloque facturable. */
|
|
64
|
+
function preparar(language) {
|
|
65
|
+
const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "estela-idioma-"));
|
|
66
|
+
const path = (0, node_path_1.join)(dir, "estela.db");
|
|
67
|
+
const db = (0, schema_js_1.openDatabase)(path);
|
|
68
|
+
try {
|
|
69
|
+
store.upsertClient(db, {
|
|
70
|
+
id: "acme", name: "ACME", currency: "EUR", taxId: "B-1", email: "ap@acme.test",
|
|
71
|
+
...(language ? { language } : {}),
|
|
72
|
+
});
|
|
73
|
+
store.upsertProject(db, {
|
|
74
|
+
id: "p", clientId: "acme", name: "Web", repoPaths: [],
|
|
75
|
+
billable: true, roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
76
|
+
});
|
|
77
|
+
store.addRatePeriod(db, {
|
|
78
|
+
projectId: "p", hourlyRate: { amount: 5000, currency: "EUR" },
|
|
79
|
+
effectiveFrom: new Date("2026-01-01T00:00:00Z"), effectiveTo: null,
|
|
80
|
+
});
|
|
81
|
+
const startedAt = new Date("2026-08-10T10:00:00Z");
|
|
82
|
+
store.saveTimeEntry(db, {
|
|
83
|
+
id: "e1", projectId: "p", startedAt,
|
|
84
|
+
endedAt: new Date(startedAt.getTime() + 3600_000), seconds: 3600,
|
|
85
|
+
description: "Trabajo", billable: true, invoiceId: null,
|
|
86
|
+
aiCost: { microUsd: 0 }, agentSeconds: 3600, commitHashes: [], agents: [],
|
|
87
|
+
source: "agent", kind: "development", branch: "main",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
db.close();
|
|
92
|
+
}
|
|
93
|
+
return { db: path, dir };
|
|
94
|
+
}
|
|
95
|
+
const htmlLang = (file) => /<html lang="(\w+)"/.exec((0, node_fs_1.readFileSync)(file, "utf8"))[1];
|
|
96
|
+
// ── share: el documento sale en el idioma del cliente ───────────────────
|
|
97
|
+
(0, node_test_1.test)("sin idioma fijado, el informe sale en el de la terminal y avisa dónde fijarlo", () => {
|
|
98
|
+
const { db, dir } = preparar();
|
|
99
|
+
const out = (0, node_path_1.join)(dir, "r.html");
|
|
100
|
+
const es = estela(db, "es", "share", "--project", "p", "--out", out);
|
|
101
|
+
strict_1.default.equal(es.code, 0, es.out);
|
|
102
|
+
strict_1.default.equal(htmlLang(out), "es");
|
|
103
|
+
strict_1.default.match(es.out, /Sale en español, el idioma de tu terminal/);
|
|
104
|
+
// El comando viene ya escrito, con los datos del cliente: `client add` reescribe
|
|
105
|
+
// la ficha entera y teclearlo de memoria es perder el NIF sin querer.
|
|
106
|
+
strict_1.default.match(es.out, /estela client add --id acme --name "ACME" --currency EUR --tax-id "B-1" --email "ap@acme.test" --language <es\|en>/);
|
|
107
|
+
const en = estela(db, "en", "share", "--project", "p", "--out", out);
|
|
108
|
+
strict_1.default.equal(htmlLang(out), "en");
|
|
109
|
+
strict_1.default.match(en.out, /It comes out in English, your terminal's language/);
|
|
110
|
+
});
|
|
111
|
+
(0, node_test_1.test)("el idioma del cliente manda sobre el de la terminal, y no hay aviso", () => {
|
|
112
|
+
// Es el caso para el que existe todo esto: terminal en inglés, cliente en español.
|
|
113
|
+
const { db, dir } = preparar("es");
|
|
114
|
+
const out = (0, node_path_1.join)(dir, "r.html");
|
|
115
|
+
const r = estela(db, "en", "share", "--project", "p", "--out", out);
|
|
116
|
+
strict_1.default.equal(r.code, 0, r.out);
|
|
117
|
+
strict_1.default.equal(htmlLang(out), "es", "el documento sale en el idioma del cliente");
|
|
118
|
+
strict_1.default.doesNotMatch(r.out, /It comes out in|To set it/, "ya está decidido: no se avisa");
|
|
119
|
+
strict_1.default.match(r.out, /Report:/, "lo que imprime la terminal sigue en el idioma de la terminal");
|
|
120
|
+
});
|
|
121
|
+
(0, node_test_1.test)("--lang en el comando manda sobre todo", () => {
|
|
122
|
+
const { db, dir } = preparar("es");
|
|
123
|
+
const out = (0, node_path_1.join)(dir, "r.html");
|
|
124
|
+
const r = estela(db, "es", "share", "--project", "p", "--out", out, "--lang", "en");
|
|
125
|
+
strict_1.default.equal(r.code, 0, r.out);
|
|
126
|
+
strict_1.default.equal(htmlLang(out), "en");
|
|
127
|
+
strict_1.default.doesNotMatch(r.out, /To set it|Para fijarlo/);
|
|
128
|
+
});
|
|
129
|
+
// ── export y report ─────────────────────────────────────────────────────
|
|
130
|
+
(0, node_test_1.test)("el CSV sale en el idioma del cliente; por la salida estándar no lleva ningún aviso dentro", () => {
|
|
131
|
+
const { db, dir } = preparar("en");
|
|
132
|
+
const aFichero = estela(db, "es", "export", "--project", "p", "--out", (0, node_path_1.join)(dir, "h.csv"));
|
|
133
|
+
strict_1.default.equal(aFichero.code, 0, aFichero.out);
|
|
134
|
+
strict_1.default.ok((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, "h.csv"), "utf8").includes("date,start,end,hours"));
|
|
135
|
+
// Sin idioma fijado y por stdout: el aviso se colaría dentro del CSV.
|
|
136
|
+
const sin = preparar();
|
|
137
|
+
const porStdout = estela(sin.db, "es", "export", "--project", "p");
|
|
138
|
+
strict_1.default.equal(porStdout.code, 0, porStdout.out);
|
|
139
|
+
strict_1.default.doesNotMatch(porStdout.out, /Para fijarlo|Sale en/);
|
|
140
|
+
strict_1.default.ok(porStdout.out.replace("", "").startsWith("fecha,inicio"));
|
|
141
|
+
});
|
|
142
|
+
(0, node_test_1.test)("el PDF del informe de horas sale en el idioma del cliente", () => {
|
|
143
|
+
const en = preparar("en");
|
|
144
|
+
const pdf = (0, node_path_1.join)(en.dir, "f.pdf");
|
|
145
|
+
const r = estela(en.db, "es", "report", "--project", "p", "--cutoff", "2026-08-31", "--dry-run", "--pdf", pdf);
|
|
146
|
+
strict_1.default.equal(r.code, 0, r.out);
|
|
147
|
+
const texto = (0, node_fs_1.readFileSync)(pdf).toString("latin1");
|
|
148
|
+
strict_1.default.ok(texto.includes("(HOURS REPORT)"));
|
|
149
|
+
strict_1.default.ok(!texto.includes("INFORME DE HORAS"));
|
|
150
|
+
const es = preparar("es");
|
|
151
|
+
const pdfEs = (0, node_path_1.join)(es.dir, "f.pdf");
|
|
152
|
+
estela(es.db, "en", "report", "--project", "p", "--cutoff", "2026-08-31", "--dry-run", "--pdf", pdfEs);
|
|
153
|
+
strict_1.default.ok((0, node_fs_1.readFileSync)(pdfEs).toString("latin1").includes("(INFORME DE HORAS)"));
|
|
154
|
+
});
|
|
155
|
+
// ── client add ──────────────────────────────────────────────────────────
|
|
156
|
+
const idiomaEnBase = (db) => {
|
|
157
|
+
const conn = (0, schema_js_1.openDatabase)(db);
|
|
158
|
+
try {
|
|
159
|
+
return store.getClient(conn, "acme")?.language;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
conn.close();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
(0, node_test_1.test)("client add --language lo fija, volver a ejecutarlo sin él lo conserva, y auto lo quita", () => {
|
|
166
|
+
const { db } = preparar();
|
|
167
|
+
const fija = estela(db, "es", "client", "add", "--id", "acme", "--name", "ACME", "--currency", "EUR", "--language", "en");
|
|
168
|
+
strict_1.default.equal(fija.code, 0, fija.out);
|
|
169
|
+
strict_1.default.match(fija.out, /Sus documentos salen en inglés/);
|
|
170
|
+
strict_1.default.equal(idiomaEnBase(db), "en");
|
|
171
|
+
// Cambiar un correo no puede cambiar el idioma de los PDFs sin avisar.
|
|
172
|
+
estela(db, "es", "client", "add", "--id", "acme", "--name", "ACME", "--currency", "EUR", "--email", "nuevo@acme.test");
|
|
173
|
+
strict_1.default.equal(idiomaEnBase(db), "en", "sin --language se conserva el que había");
|
|
174
|
+
estela(db, "es", "client", "add", "--id", "acme", "--name", "ACME", "--currency", "EUR", "--language", "auto");
|
|
175
|
+
strict_1.default.equal(idiomaEnBase(db), undefined, "auto vuelve a seguir la terminal");
|
|
176
|
+
});
|
|
177
|
+
(0, node_test_1.test)("client add rechaza un idioma que no existe, sin guardar nada", () => {
|
|
178
|
+
const { db } = preparar();
|
|
179
|
+
const r = estela(db, "es", "client", "add", "--id", "acme", "--name", "Otro", "--currency", "EUR", "--language", "fr");
|
|
180
|
+
strict_1.default.notEqual(r.code, 0);
|
|
181
|
+
strict_1.default.match(r.out, /--language admite es, en o auto, y recibí "fr"/);
|
|
182
|
+
const conn = (0, schema_js_1.openDatabase)(db);
|
|
183
|
+
try {
|
|
184
|
+
strict_1.default.equal(store.getClient(conn, "acme").name, "ACME", "no se tocó la ficha");
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
conn.close();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
//# sourceMappingURL=cli-idioma.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-idioma.test.js","sourceRoot":"","sources":["../src/cli-idioma.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAuCA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAuCA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAmrDpD,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -67,10 +67,11 @@ const auth_js_1 = require("./cloud/auth.js");
|
|
|
67
67
|
const client_js_1 = require("./cloud/client.js");
|
|
68
68
|
const cost_js_1 = require("./pricing/cost.js");
|
|
69
69
|
Object.defineProperty(exports, "breakdownOfTurn", { enumerable: true, get: function () { return cost_js_1.breakdownOfTurn; } });
|
|
70
|
-
const
|
|
70
|
+
const agents_js_1 = require("./watchers/agents.js");
|
|
71
71
|
const git_js_1 = require("./watchers/git.js");
|
|
72
72
|
const identity_js_1 = require("./watchers/identity.js");
|
|
73
73
|
const index_js_1 = require("./i18n/index.js");
|
|
74
|
+
const labels_js_1 = require("./i18n/labels.js");
|
|
74
75
|
const HELP_ES = `
|
|
75
76
|
estela — registro de horas para desarrollo asistido por IA
|
|
76
77
|
|
|
@@ -83,10 +84,17 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
83
84
|
|
|
84
85
|
estela client add --id <id> --name <nombre> --currency <EUR|USD|...>
|
|
85
86
|
[--tax-id <NIF/CIF>] [--email <a@b.com>]
|
|
87
|
+
[--language es|en|auto]
|
|
86
88
|
--tax-id y --email son los datos del cliente que saldrán impresos en
|
|
87
89
|
el informe de "estela report". Repetir el comando con el mismo --id
|
|
88
90
|
reescribe la ficha entera: vuelve a pasar todo lo que quieras
|
|
89
91
|
conservar, lo que omitas se queda vacío.
|
|
92
|
+
--language es el idioma de lo que recibe ESE cliente: el PDF, el CSV,
|
|
93
|
+
el informe y el panel compartido. Es del cliente y no de tu terminal:
|
|
94
|
+
puedes usar Estela en español y facturarle a una empresa en inglés.
|
|
95
|
+
Sin --language se usa el de tu terminal (y se te avisa). Es lo único
|
|
96
|
+
que NO se borra al repetir el comando; "auto" lo quita a propósito.
|
|
97
|
+
Un "--lang en" suelto en cualquier comando manda sobre todo.
|
|
90
98
|
estela project add --id <id> --client <id> --name <nombre> --repo <ruta>
|
|
91
99
|
[--rounding <min>] [--ai-cost absorbed|passthrough]
|
|
92
100
|
estela project close --project <id>
|
|
@@ -210,10 +218,17 @@ estela — time tracking for AI-assisted development
|
|
|
210
218
|
|
|
211
219
|
estela client add --id <id> --name <name> --currency <EUR|USD|...>
|
|
212
220
|
[--tax-id <tax ID>] [--email <a@b.com>]
|
|
221
|
+
[--language es|en|auto]
|
|
213
222
|
--tax-id and --email are the client details printed on the report
|
|
214
223
|
from "estela report". Running the command again with the same --id
|
|
215
224
|
rewrites the whole record: pass everything you want to keep again,
|
|
216
225
|
anything you leave out is cleared.
|
|
226
|
+
--language is the language of what THAT client receives: the PDF, the
|
|
227
|
+
CSV, the report and the shared panel. It belongs to the client, not to
|
|
228
|
+
your terminal: you can use Estela in Spanish and bill a company in
|
|
229
|
+
English. Without --language your terminal's is used (and you're told).
|
|
230
|
+
It's the one thing that is NOT cleared when you run the command again;
|
|
231
|
+
"auto" removes it on purpose. A bare "--lang en" on any command wins.
|
|
217
232
|
estela project add --id <id> --client <id> --name <name> --repo <path>
|
|
218
233
|
[--rounding <min>] [--ai-cost absorbed|passthrough]
|
|
219
234
|
estela project close --project <id>
|
|
@@ -360,20 +375,34 @@ function required(args, key) {
|
|
|
360
375
|
}
|
|
361
376
|
class UserError extends Error {
|
|
362
377
|
}
|
|
378
|
+
/** El `--lang` escrito en el comando, si es uno que existe. Distinto del de la terminal. */
|
|
379
|
+
function explicitLang(args) {
|
|
380
|
+
const v = str(args, "lang");
|
|
381
|
+
return v === "es" || v === "en" ? v : undefined;
|
|
382
|
+
}
|
|
363
383
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
384
|
+
* En qué idioma sale el documento que recibe este cliente: `--lang`, luego el
|
|
385
|
+
* idioma que se le fijó, y solo si nada de eso, el de la terminal.
|
|
366
386
|
*/
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
387
|
+
function docLangOf(args, client) {
|
|
388
|
+
return (0, index_js_1.documentLang)({ explicit: explicitLang(args), clientLanguage: client.language });
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Si el idioma salió de la terminal, y no de una decisión, se dice — con el
|
|
392
|
+
* comando ya escrito. `client add` reescribe la ficha entera, así que pedirle a
|
|
393
|
+
* alguien que lo teclee de memoria es pedirle que pierda el NIF sin querer.
|
|
394
|
+
*/
|
|
395
|
+
function hintDocLang(args, client, lang) {
|
|
396
|
+
if (client.language || explicitLang(args))
|
|
397
|
+
return;
|
|
398
|
+
const dice = lang === "es" ? (0, index_js_1.tr) `español` : (0, index_js_1.tr) `inglés`;
|
|
399
|
+
const cmd = `estela client add --id ${client.id} --name ${JSON.stringify(client.name)} ` +
|
|
400
|
+
`--currency ${client.currency}` +
|
|
401
|
+
(client.taxId ? ` --tax-id ${JSON.stringify(client.taxId)}` : "") +
|
|
402
|
+
(client.email ? ` --email ${JSON.stringify(client.email)}` : "") +
|
|
403
|
+
` --language <es|en>`;
|
|
404
|
+
console.log((0, index_js_1.tr) `\n Sale en ${dice}, el idioma de tu terminal: a este cliente no se le fijó ninguno.`);
|
|
405
|
+
console.log((0, index_js_1.tr) ` Para fijarlo: ${cmd}`);
|
|
377
406
|
}
|
|
378
407
|
// ---------------------------------------------------------------------------
|
|
379
408
|
/**
|
|
@@ -382,9 +411,24 @@ function kindLabel(kind) {
|
|
|
382
411
|
* Quien instala la CLI no tiene forma de arrancar el servidor: hasta ahora
|
|
383
412
|
* vivía en un script del repositorio, que solo existe si te lo has clonado.
|
|
384
413
|
*/
|
|
414
|
+
/**
|
|
415
|
+
* Arranca el panel convirtiendo el fallo en algo legible.
|
|
416
|
+
*
|
|
417
|
+
* `startServer` ya rechaza con una frase en vez de un errno, pero si llega
|
|
418
|
+
* como Error normal, el catch del final imprime el objeto entero con su
|
|
419
|
+
* traza. Como UserError, sale la frase sola.
|
|
420
|
+
*/
|
|
421
|
+
async function arrancarPanel(opciones) {
|
|
422
|
+
try {
|
|
423
|
+
return await (0, server_js_1.startServer)(opciones);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
throw new UserError(error instanceof Error ? error.message : String(error));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
385
429
|
async function cmdWeb(args, dbPath) {
|
|
386
430
|
const port = Number(str(args, "port") ?? 4319);
|
|
387
|
-
const url = await (
|
|
431
|
+
const url = await arrancarPanel({ port, dbPath });
|
|
388
432
|
console.log((0, index_js_1.tr) `\n Estela ${url}`);
|
|
389
433
|
console.log((0, index_js_1.tr) ` Datos: ${dbPath}`);
|
|
390
434
|
console.log((0, index_js_1.tr) `\n Ctrl+C para parar.\n`);
|
|
@@ -410,7 +454,7 @@ async function cmdDemo(args, dbPath) {
|
|
|
410
454
|
db.close();
|
|
411
455
|
}
|
|
412
456
|
const port = Number(str(args, "port") ?? 4320);
|
|
413
|
-
const url = await (
|
|
457
|
+
const url = await arrancarPanel({ port, dbPath: rutaDemo, autoImportMinutes: 0, demo: true });
|
|
414
458
|
console.log((0, index_js_1.tr) `\n Estela — demo con datos inventados ${url}`);
|
|
415
459
|
console.log((0, index_js_1.tr) ` No es tu trabajo: no se ha tocado tu base ni se ha leído nada tuyo.`);
|
|
416
460
|
console.log((0, index_js_1.tr) ` Para el tuyo de verdad: estela setup`);
|
|
@@ -447,13 +491,45 @@ async function cmdLogout(dbPath) {
|
|
|
447
491
|
* Dos minutos, sin preguntas y sin cuenta. Si al terminar esa persona no ve
|
|
448
492
|
* algo cierto sobre su propio trabajo, no habrá una segunda ejecución.
|
|
449
493
|
*/
|
|
494
|
+
/** Nombre del agente tal y como lo llama quien lo usa, no como lo llamamos aquí. */
|
|
495
|
+
function agentName(agent) {
|
|
496
|
+
switch (agent) {
|
|
497
|
+
case "claude-code": return "Claude Code";
|
|
498
|
+
case "codex": return "Codex";
|
|
499
|
+
case "cursor": return "Cursor";
|
|
500
|
+
case "gemini-cli": return "Gemini CLI";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* El parte de un escaneo.
|
|
505
|
+
*
|
|
506
|
+
* Se imprime por agente y no sumado: si Codex cambia de formato y empieza a
|
|
507
|
+
* dar registros malformados, un total conjunto lo diluye con la salud de
|
|
508
|
+
* Claude Code y el canario deja de avisar de nada.
|
|
509
|
+
*/
|
|
510
|
+
function printScanReport(scan) {
|
|
511
|
+
const r = scan.report;
|
|
512
|
+
console.log(` ${agentName(scan.agent)}`);
|
|
513
|
+
console.log((0, index_js_1.tr) ` ${r.filesRead} archivos · ${r.recordsSeen} registros`);
|
|
514
|
+
console.log((0, index_js_1.tr) ` ${r.turnsAccepted} turnos aceptados`);
|
|
515
|
+
console.log((0, index_js_1.tr) ` ${r.duplicatesDropped} duplicados descartados` +
|
|
516
|
+
(r.turnsAccepted > 0
|
|
517
|
+
? (0, index_js_1.tr) ` (${(r.duplicatesDropped / (r.turnsAccepted + r.duplicatesDropped) * 100).toFixed(0)}% de las filas)`
|
|
518
|
+
: ""));
|
|
519
|
+
console.log((0, index_js_1.tr) ` ${r.unknownRecords} registros internos ignorados · ${r.malformedRecords} malformados`);
|
|
520
|
+
if (r.producerVersions.length) {
|
|
521
|
+
console.log((0, index_js_1.tr) ` versiones: ${r.producerVersions.join(", ")}`);
|
|
522
|
+
}
|
|
523
|
+
for (const warning of r.warnings)
|
|
524
|
+
console.warn(` ⚠ ${agentName(scan.agent)}: ${warning}`);
|
|
525
|
+
}
|
|
450
526
|
async function cmdSetup(args, dbPath) {
|
|
451
527
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
452
528
|
try {
|
|
453
529
|
console.log((0, index_js_1.tr) `\nEstela\n`);
|
|
454
530
|
console.log((0, index_js_1.tr) `Leyendo lo que tus agentes ya guardaron en disco…`);
|
|
455
|
-
const
|
|
456
|
-
const turns = (0,
|
|
531
|
+
const scans = await (0, agents_js_1.scanAgents)({});
|
|
532
|
+
const turns = (0, agents_js_1.allTurns)(scans);
|
|
457
533
|
// Los repos no pueden salir solo de los transcripts: quien programa sin
|
|
458
534
|
// IA, o con un agente que no deja rastro en ~/.claude (Cursor, Copilot),
|
|
459
535
|
// tenía cero repos que mirar y `setup` se rendía ahí mismo — aunque la
|
|
@@ -469,19 +545,22 @@ async function cmdSetup(args, dbPath) {
|
|
|
469
545
|
if (cwdRoot)
|
|
470
546
|
repos.add(cwdRoot);
|
|
471
547
|
if (turns.length === 0 && repos.size === 0) {
|
|
472
|
-
console.log((0, index_js_1.tr) `\n No se han encontrado sesiones de
|
|
473
|
-
console.log((0, index_js_1.tr) ` un repositorio de Git en esta carpeta. Corre esto de nuevo desde`);
|
|
548
|
+
console.log((0, index_js_1.tr) `\n No se han encontrado sesiones de agentes en ~/.claude ni ~/.codex,`);
|
|
549
|
+
console.log((0, index_js_1.tr) ` ni un repositorio de Git en esta carpeta. Corre esto de nuevo desde`);
|
|
474
550
|
console.log((0, index_js_1.tr) ` dentro de tu proyecto, o trabaja un rato con tu agente y vuelve.\n`);
|
|
475
551
|
return;
|
|
476
552
|
}
|
|
477
553
|
if (turns.length > 0) {
|
|
478
554
|
store.saveTurns(db, turns);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
555
|
+
for (const scan of scans)
|
|
556
|
+
store.logScan(db, scan.agent, scan.report);
|
|
557
|
+
// Nombrar los agentes encontrados, en vez de contar versiones: es lo
|
|
558
|
+
// que quien acaba de instalar quiere confirmar —que ha visto lo suyo—.
|
|
559
|
+
const vistos = scans.filter((s) => s.report.turnsAccepted > 0).map((s) => agentName(s.agent));
|
|
560
|
+
console.log((0, index_js_1.tr) ` ${turns.length} turnos · ${vistos.join(", ")}`);
|
|
482
561
|
}
|
|
483
562
|
else {
|
|
484
|
-
console.log((0, index_js_1.tr) ` No se han encontrado sesiones de
|
|
563
|
+
console.log((0, index_js_1.tr) ` No se han encontrado sesiones de agentes en ~/.claude ni ~/.codex —`);
|
|
485
564
|
console.log((0, index_js_1.tr) ` sin problema, se reconstruye igual desde tus commits de Git.`);
|
|
486
565
|
}
|
|
487
566
|
console.log((0, index_js_1.tr) `\nBuscando repositorios… (${repos.size})`);
|
|
@@ -536,29 +615,25 @@ async function cmdImport(args, dbPath) {
|
|
|
536
615
|
const repoFilter = str(args, "repo");
|
|
537
616
|
const repoPaths = repoFilter ? [(0, node_path_1.resolve)(repoFilter)] : undefined;
|
|
538
617
|
console.log((0, index_js_1.tr) `Leyendo transcripts de agentes…`);
|
|
539
|
-
const
|
|
618
|
+
const scans = await (0, agents_js_1.scanAgents)({
|
|
540
619
|
...(since ? { since } : {}),
|
|
541
620
|
...(repoPaths ? { repoPaths } : {}),
|
|
542
621
|
});
|
|
543
|
-
const report = scan.report;
|
|
544
622
|
// Devolver al repositorio el trabajo hecho en scratchpads, o esas horas
|
|
545
623
|
// quedan sin proyecto y desaparecen del parte.
|
|
546
|
-
const turns = (0,
|
|
547
|
-
const rescued =
|
|
624
|
+
const turns = (0, agents_js_1.allTurns)(scans);
|
|
625
|
+
const rescued = scans.reduce((n, s) => n + s.scratchpadsResolved, 0);
|
|
548
626
|
const savedTurns = store.saveTurns(db, turns);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
for (const warning of report.warnings)
|
|
561
|
-
console.warn(` ⚠ ${warning}`);
|
|
627
|
+
for (const scan of scans)
|
|
628
|
+
store.logScan(db, scan.agent, scan.report);
|
|
629
|
+
// Un bloque por agente, y solo de los que tienen algo. Quien no use Codex
|
|
630
|
+
// no debe leer cuatro líneas de ceros para enterarse de eso.
|
|
631
|
+
console.log((0, index_js_1.tr) ` ${savedTurns} turnos nuevos guardados`);
|
|
632
|
+
for (const scan of scans) {
|
|
633
|
+
if (scan.report.filesRead === 0)
|
|
634
|
+
continue;
|
|
635
|
+
printScanReport(scan);
|
|
636
|
+
}
|
|
562
637
|
// Commits de cada repositorio que aparece en los transcripts, MÁS todo
|
|
563
638
|
// repo ya vinculado a un proyecto aunque ningún transcript lo mencione
|
|
564
639
|
// — si no, un proyecto que `setup` acaba de registrar desde el
|
|
@@ -680,14 +755,28 @@ function cmdClientAdd(args, dbPath) {
|
|
|
680
755
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
681
756
|
try {
|
|
682
757
|
const currency = required(args, "currency").toUpperCase();
|
|
758
|
+
const id = required(args, "id");
|
|
759
|
+
// El idioma es la única parte de la ficha que NO se borra al repetir el
|
|
760
|
+
// comando sin ella: quien vuelve a ejecutar `client add` solo para cambiar un
|
|
761
|
+
// correo no espera que los PDFs de ese cliente cambien de idioma sin avisar.
|
|
762
|
+
// "auto" es la forma de quitarlo a propósito.
|
|
763
|
+
const asked = str(args, "language");
|
|
764
|
+
if (asked !== undefined && asked !== "es" && asked !== "en" && asked !== "auto") {
|
|
765
|
+
throw new UserError((0, index_js_1.tr) `--language admite es, en o auto, y recibí "${asked}".`);
|
|
766
|
+
}
|
|
767
|
+
const language = asked === "auto" ? undefined : (asked ?? store.getClient(db, id)?.language);
|
|
683
768
|
store.upsertClient(db, {
|
|
684
|
-
id
|
|
769
|
+
id,
|
|
685
770
|
name: required(args, "name"),
|
|
686
771
|
currency,
|
|
687
772
|
...(str(args, "tax-id") ? { taxId: str(args, "tax-id") } : {}),
|
|
688
773
|
...(str(args, "email") ? { email: str(args, "email") } : {}),
|
|
774
|
+
...(language ? { language } : {}),
|
|
689
775
|
});
|
|
690
776
|
console.log((0, index_js_1.tr) `Cliente "${required(args, "name")}" guardado. Factura en ${currency}.`);
|
|
777
|
+
if (language) {
|
|
778
|
+
console.log((0, index_js_1.tr) ` Sus documentos salen en ${language === "es" ? (0, index_js_1.tr) `español` : (0, index_js_1.tr) `inglés`}.`);
|
|
779
|
+
}
|
|
691
780
|
}
|
|
692
781
|
finally {
|
|
693
782
|
db.close();
|
|
@@ -928,7 +1017,8 @@ function cmdShare(args, dbPath) {
|
|
|
928
1017
|
const to = str(args, "to") ?? (0, shared_1.localDate)(new Date());
|
|
929
1018
|
const from = str(args, "from") ?? to.slice(0, 8) + "01";
|
|
930
1019
|
const rates = store.getRates(db, projectId);
|
|
931
|
-
const
|
|
1020
|
+
const lang = docLangOf(args, client);
|
|
1021
|
+
const html = (0, index_js_1.withLang)(lang, () => (0, share_js_1.buildShareReport)({
|
|
932
1022
|
project, client,
|
|
933
1023
|
entries: store.getTimeEntries(db, projectId),
|
|
934
1024
|
from, to,
|
|
@@ -941,7 +1031,7 @@ function cmdShare(args, dbPath) {
|
|
|
941
1031
|
commitsOf: (hashes) => hashes.length
|
|
942
1032
|
? db.prepare(`SELECT hash, subject FROM commits WHERE hash IN (${hashes.map(() => "?").join(",")})`).all(...hashes)
|
|
943
1033
|
: [],
|
|
944
|
-
});
|
|
1034
|
+
}));
|
|
945
1035
|
const out = str(args, "out") ?? `informe-${projectId}-${to}.html`;
|
|
946
1036
|
(0, node_fs_1.writeFileSync)(out, html, "utf8");
|
|
947
1037
|
console.log((0, index_js_1.tr) `\nInforme: ${out}`);
|
|
@@ -951,6 +1041,7 @@ function cmdShare(args, dbPath) {
|
|
|
951
1041
|
console.log((0, index_js_1.tr) `\n Incluye horas y commits. Sin importes (añade --with-amounts) y sin`);
|
|
952
1042
|
console.log((0, index_js_1.tr) ` consumo de IA, que es tuyo mientras la pagues tú.`);
|
|
953
1043
|
}
|
|
1044
|
+
hintDocLang(args, client, lang);
|
|
954
1045
|
}
|
|
955
1046
|
finally {
|
|
956
1047
|
db.close();
|
|
@@ -991,7 +1082,7 @@ function cmdLog(args, dbPath) {
|
|
|
991
1082
|
throw new UserError((0, index_js_1.tr) `Fecha inválida: ${day}`);
|
|
992
1083
|
// A media mañana: no sabemos la hora real y fingir precisión sería peor.
|
|
993
1084
|
const startedAt = new Date(`${day}T10:00:00`);
|
|
994
|
-
const what = str(args, "what") ?? kindLabel(kind);
|
|
1085
|
+
const what = str(args, "what") ?? (0, labels_js_1.kindLabel)(kind);
|
|
995
1086
|
const id = store.saveTimeEntry(db, {
|
|
996
1087
|
projectId,
|
|
997
1088
|
startedAt,
|
|
@@ -1012,7 +1103,7 @@ function cmdLog(args, dbPath) {
|
|
|
1012
1103
|
const amount = rate
|
|
1013
1104
|
? ` · ${(0, shared_1.formatMoney)({ amount: Math.round((rate.amount * seconds) / 3600), currency: rate.currency })}`
|
|
1014
1105
|
: "";
|
|
1015
|
-
console.log(`\n${kindLabel(kind)} · ${(0, shared_1.formatDuration)(seconds)}${amount}`);
|
|
1106
|
+
console.log(`\n${(0, labels_js_1.kindLabel)(kind)} · ${(0, shared_1.formatDuration)(seconds)}${amount}`);
|
|
1016
1107
|
console.log(` ${what}`);
|
|
1017
1108
|
console.log(` ${project.name} · ${day}`);
|
|
1018
1109
|
console.log((0, index_js_1.tr) `\n Registro manual: un import no lo va a tocar. (${id})`);
|
|
@@ -1057,6 +1148,7 @@ async function cmdPublish(args, dbPath) {
|
|
|
1057
1148
|
...(clientes?.length ? { clients: clientes } : {}),
|
|
1058
1149
|
withAmounts: args.flags["with-amounts"] === true,
|
|
1059
1150
|
includeTeam: args.flags["no-team"] !== true,
|
|
1151
|
+
...(explicitLang(args) ? { language: explicitLang(args) } : {}),
|
|
1060
1152
|
});
|
|
1061
1153
|
}
|
|
1062
1154
|
catch (error) {
|
|
@@ -1076,6 +1168,8 @@ async function cmdPublish(args, dbPath) {
|
|
|
1076
1168
|
console.log((0, index_js_1.tr) ` El token es lo único que protege la página: quien tenga el enlace, entra.`);
|
|
1077
1169
|
console.log((0, index_js_1.tr) ` Al volver a publicar desde esta máquina, este enlace se reusa solo.`);
|
|
1078
1170
|
console.log((0, index_js_1.tr) ` Desde otra máquina, pasa --token ${result.token} o el cliente perderá su enlace.`);
|
|
1171
|
+
const owner = store.getClient(db, store.getProject(db, projectId).clientId);
|
|
1172
|
+
hintDocLang(args, owner, docLangOf(args, owner));
|
|
1079
1173
|
}
|
|
1080
1174
|
finally {
|
|
1081
1175
|
db.close();
|
|
@@ -1441,11 +1535,15 @@ function cmdExport(args, dbPath) {
|
|
|
1441
1535
|
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1442
1536
|
const client = store.getClient(db, project.clientId);
|
|
1443
1537
|
const rates = store.getRates(db, projectId);
|
|
1444
|
-
const
|
|
1538
|
+
const lang = docLangOf(args, client);
|
|
1539
|
+
const csv = (0, index_js_1.withLang)(lang, () => (0, csv_js_1.timeEntriesToCsv)(store.getTimeEntries(db, projectId), project, client, (at) => (0, invoice_js_1.rateAt)(rates, projectId, at)));
|
|
1445
1540
|
const out = str(args, "out");
|
|
1446
1541
|
if (out) {
|
|
1447
1542
|
(0, node_fs_1.writeFileSync)(out, csv, "utf8");
|
|
1448
1543
|
console.log((0, index_js_1.tr) `CSV: ${out}`);
|
|
1544
|
+
// Solo si va a un fichero: por la salida estándar el aviso se colaría
|
|
1545
|
+
// dentro del propio CSV.
|
|
1546
|
+
hintDocLang(args, client, lang);
|
|
1449
1547
|
}
|
|
1450
1548
|
else {
|
|
1451
1549
|
process.stdout.write(csv);
|
|
@@ -1540,13 +1638,16 @@ function cmdInvoice(args, dbPath) {
|
|
|
1540
1638
|
const amortized = subs.length
|
|
1541
1639
|
? (0, amortize_js_1.shareForProject)((0, amortize_js_1.amortize)(store.consumptionByProjectMonth(db), subs, store.totalConsumptionByMonth(db)), projectId, [...months])
|
|
1542
1640
|
: null;
|
|
1543
|
-
|
|
1641
|
+
// Las líneas de un informe se congelan al emitirlo, así que se emiten ya en
|
|
1642
|
+
// el idioma del cliente: es el que verá en el PDF y en el CSV.
|
|
1643
|
+
const lang = docLangOf(args, client);
|
|
1644
|
+
const invoice = (0, index_js_1.withLang)(lang, () => (0, invoice_js_1.issueInvoice)({
|
|
1544
1645
|
client, project,
|
|
1545
1646
|
rates: store.getRates(db, projectId),
|
|
1546
1647
|
entries, cutoffAt, number,
|
|
1547
1648
|
...(fx ? { usdFxRate: Number(fx) } : {}),
|
|
1548
1649
|
...(amortized ? { aiAmortized: amortized } : {}),
|
|
1549
|
-
});
|
|
1650
|
+
}));
|
|
1550
1651
|
console.log((0, invoice_js_1.renderInvoice)(invoice, client, project));
|
|
1551
1652
|
// Coste real de IA del periodo: la cuota de tus suscripciones repartida
|
|
1552
1653
|
// por consumo. Es lo que de verdad te costó, frente a la tarifa API.
|
|
@@ -1567,7 +1668,7 @@ function cmdInvoice(args, dbPath) {
|
|
|
1567
1668
|
const pdfPath = str(args, "pdf");
|
|
1568
1669
|
if (pdfPath) {
|
|
1569
1670
|
const issuerName = str(args, "from-name");
|
|
1570
|
-
const pdf = (0, invoice_pdf_js_1.invoiceToPdf)(invoice, client, project, {
|
|
1671
|
+
const pdf = (0, index_js_1.withLang)(lang, () => (0, invoice_pdf_js_1.invoiceToPdf)(invoice, client, project, {
|
|
1571
1672
|
...(issuerName ? {
|
|
1572
1673
|
issuer: {
|
|
1573
1674
|
name: issuerName,
|
|
@@ -1576,15 +1677,17 @@ function cmdInvoice(args, dbPath) {
|
|
|
1576
1677
|
},
|
|
1577
1678
|
} : {}),
|
|
1578
1679
|
...(invoice.aiAmortized ? { amortizedAiCost: invoice.aiAmortized } : {}),
|
|
1579
|
-
});
|
|
1680
|
+
}));
|
|
1580
1681
|
(0, node_fs_1.writeFileSync)(pdfPath, pdf);
|
|
1581
1682
|
console.log((0, index_js_1.tr) `\nPDF: ${pdfPath} (${(pdf.length / 1024).toFixed(1)} KB, ${invoice.lines.length} conceptos)`);
|
|
1582
1683
|
}
|
|
1583
1684
|
const csvPath = str(args, "csv");
|
|
1584
1685
|
if (csvPath) {
|
|
1585
|
-
(0, node_fs_1.writeFileSync)(csvPath, (0, csv_js_1.invoiceToCsv)(invoice, client, project), "utf8");
|
|
1686
|
+
(0, node_fs_1.writeFileSync)(csvPath, (0, index_js_1.withLang)(lang, () => (0, csv_js_1.invoiceToCsv)(invoice, client, project)), "utf8");
|
|
1586
1687
|
console.log((0, index_js_1.tr) `CSV: ${csvPath}`);
|
|
1587
1688
|
}
|
|
1689
|
+
if (pdfPath || csvPath)
|
|
1690
|
+
hintDocLang(args, client, lang);
|
|
1588
1691
|
if (dryRun) {
|
|
1589
1692
|
console.log((0, index_js_1.tr) `\n[--dry-run] No se guardó nada. Repite sin --dry-run para emitirlo.`);
|
|
1590
1693
|
return;
|