estela 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/statusline.js +34 -21
- package/package.json +3 -1
- package/dist/billing/amortize.d.ts.map +0 -1
- package/dist/billing/amortize.js.map +0 -1
- package/dist/billing/amortize.test.d.ts +0 -2
- package/dist/billing/amortize.test.d.ts.map +0 -1
- package/dist/billing/amortize.test.js +0 -121
- package/dist/billing/amortize.test.js.map +0 -1
- package/dist/billing/assign.d.ts.map +0 -1
- package/dist/billing/assign.js.map +0 -1
- package/dist/billing/assign.test.d.ts +0 -2
- package/dist/billing/assign.test.d.ts.map +0 -1
- package/dist/billing/assign.test.js +0 -153
- package/dist/billing/assign.test.js.map +0 -1
- package/dist/billing/format.test.d.ts +0 -2
- package/dist/billing/format.test.d.ts.map +0 -1
- package/dist/billing/format.test.js +0 -110
- package/dist/billing/format.test.js.map +0 -1
- package/dist/billing/invoice.d.ts.map +0 -1
- package/dist/billing/invoice.js.map +0 -1
- package/dist/billing/invoice.test.d.ts +0 -2
- package/dist/billing/invoice.test.d.ts.map +0 -1
- package/dist/billing/invoice.test.js +0 -200
- package/dist/billing/invoice.test.js.map +0 -1
- package/dist/billing/localize.d.ts.map +0 -1
- package/dist/billing/localize.js.map +0 -1
- package/dist/billing/localize.test.d.ts +0 -2
- package/dist/billing/localize.test.d.ts.map +0 -1
- package/dist/billing/localize.test.js +0 -144
- package/dist/billing/localize.test.js.map +0 -1
- package/dist/billing/sessionize.d.ts.map +0 -1
- package/dist/billing/sessionize.js.map +0 -1
- package/dist/billing/sessionize.test.d.ts +0 -2
- package/dist/billing/sessionize.test.d.ts.map +0 -1
- package/dist/billing/sessionize.test.js +0 -187
- package/dist/billing/sessionize.test.js.map +0 -1
- package/dist/billing.d.ts.map +0 -1
- package/dist/billing.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli-badge.test.d.ts +0 -2
- package/dist/cli-badge.test.d.ts.map +0 -1
- package/dist/cli-badge.test.js +0 -118
- package/dist/cli-badge.test.js.map +0 -1
- package/dist/cli-idioma.test.d.ts +0 -2
- package/dist/cli-idioma.test.d.ts.map +0 -1
- package/dist/cli-idioma.test.js +0 -190
- package/dist/cli-idioma.test.js.map +0 -1
- package/dist/cli-import-recover.test.d.ts +0 -2
- package/dist/cli-import-recover.test.d.ts.map +0 -1
- package/dist/cli-import-recover.test.js +0 -191
- package/dist/cli-import-recover.test.js.map +0 -1
- package/dist/cli-quota.test.d.ts +0 -2
- package/dist/cli-quota.test.d.ts.map +0 -1
- package/dist/cli-quota.test.js +0 -281
- package/dist/cli-quota.test.js.map +0 -1
- package/dist/cli-statusline.test.d.ts +0 -2
- package/dist/cli-statusline.test.d.ts.map +0 -1
- package/dist/cli-statusline.test.js +0 -333
- package/dist/cli-statusline.test.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cloud/auth.d.ts.map +0 -1
- package/dist/cloud/auth.js.map +0 -1
- package/dist/cloud/client.d.ts.map +0 -1
- package/dist/cloud/client.js.map +0 -1
- package/dist/cloud/errors.test.d.ts +0 -2
- package/dist/cloud/errors.test.d.ts.map +0 -1
- package/dist/cloud/errors.test.js +0 -34
- package/dist/cloud/errors.test.js.map +0 -1
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js.map +0 -1
- package/dist/db/schema.test.d.ts +0 -2
- package/dist/db/schema.test.d.ts.map +0 -1
- package/dist/db/schema.test.js +0 -825
- package/dist/db/schema.test.js.map +0 -1
- package/dist/db/store.d.ts.map +0 -1
- package/dist/db/store.js.map +0 -1
- package/dist/demo.d.ts.map +0 -1
- package/dist/demo.js.map +0 -1
- package/dist/demo.test.d.ts +0 -2
- package/dist/demo.test.d.ts.map +0 -1
- package/dist/demo.test.js +0 -148
- package/dist/demo.test.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doctor.test.d.ts +0 -2
- package/dist/doctor.test.d.ts.map +0 -1
- package/dist/doctor.test.js +0 -167
- package/dist/doctor.test.js.map +0 -1
- package/dist/export/badge.d.ts.map +0 -1
- package/dist/export/badge.js.map +0 -1
- package/dist/export/badge.test.d.ts +0 -2
- package/dist/export/badge.test.d.ts.map +0 -1
- package/dist/export/badge.test.js +0 -32
- package/dist/export/badge.test.js.map +0 -1
- package/dist/export/clockify.d.ts.map +0 -1
- package/dist/export/clockify.js.map +0 -1
- package/dist/export/clockify.test.d.ts +0 -2
- package/dist/export/clockify.test.d.ts.map +0 -1
- package/dist/export/clockify.test.js +0 -51
- package/dist/export/clockify.test.js.map +0 -1
- package/dist/export/csv.d.ts.map +0 -1
- package/dist/export/csv.js.map +0 -1
- package/dist/export/dates.d.ts.map +0 -1
- package/dist/export/dates.js.map +0 -1
- package/dist/export/idioma.test.d.ts +0 -2
- package/dist/export/idioma.test.d.ts.map +0 -1
- package/dist/export/idioma.test.js +0 -255
- package/dist/export/idioma.test.js.map +0 -1
- package/dist/export/invoice-pdf.d.ts.map +0 -1
- package/dist/export/invoice-pdf.js.map +0 -1
- package/dist/export/panel.d.ts.map +0 -1
- package/dist/export/panel.js.map +0 -1
- package/dist/export/panel.test.d.ts +0 -2
- package/dist/export/panel.test.d.ts.map +0 -1
- package/dist/export/panel.test.js +0 -386
- package/dist/export/panel.test.js.map +0 -1
- package/dist/export/pdf.d.ts.map +0 -1
- package/dist/export/pdf.js.map +0 -1
- package/dist/export/pdf.test.d.ts +0 -2
- package/dist/export/pdf.test.d.ts.map +0 -1
- package/dist/export/pdf.test.js +0 -172
- package/dist/export/pdf.test.js.map +0 -1
- package/dist/export/pixelfont.d.ts.map +0 -1
- package/dist/export/pixelfont.js.map +0 -1
- package/dist/export/png.d.ts.map +0 -1
- package/dist/export/png.js.map +0 -1
- package/dist/export/receipt.d.ts.map +0 -1
- package/dist/export/receipt.js.map +0 -1
- package/dist/export/receipt.test.d.ts +0 -2
- package/dist/export/receipt.test.d.ts.map +0 -1
- package/dist/export/receipt.test.js +0 -207
- package/dist/export/receipt.test.js.map +0 -1
- package/dist/export/share.d.ts.map +0 -1
- package/dist/export/share.js.map +0 -1
- package/dist/export/share.test.d.ts +0 -2
- package/dist/export/share.test.d.ts.map +0 -1
- package/dist/export/share.test.js +0 -116
- package/dist/export/share.test.js.map +0 -1
- package/dist/i18n/en.d.ts.map +0 -1
- package/dist/i18n/en.js.map +0 -1
- package/dist/i18n/i18n.test.d.ts +0 -2
- package/dist/i18n/i18n.test.d.ts.map +0 -1
- package/dist/i18n/i18n.test.js +0 -226
- package/dist/i18n/i18n.test.js.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/i18n/labels.d.ts.map +0 -1
- package/dist/i18n/labels.js.map +0 -1
- package/dist/metrics/index.d.ts.map +0 -1
- package/dist/metrics/index.js.map +0 -1
- package/dist/metrics/metrics.test.d.ts +0 -2
- package/dist/metrics/metrics.test.d.ts.map +0 -1
- package/dist/metrics/metrics.test.js +0 -162
- package/dist/metrics/metrics.test.js.map +0 -1
- package/dist/metrics/people.d.ts.map +0 -1
- package/dist/metrics/people.js.map +0 -1
- package/dist/metrics/people.test.d.ts +0 -2
- package/dist/metrics/people.test.d.ts.map +0 -1
- package/dist/metrics/people.test.js +0 -92
- package/dist/metrics/people.test.js.map +0 -1
- package/dist/metrics/quota.d.ts.map +0 -1
- package/dist/metrics/quota.js.map +0 -1
- package/dist/metrics/quota.test.d.ts +0 -2
- package/dist/metrics/quota.test.d.ts.map +0 -1
- package/dist/metrics/quota.test.js +0 -325
- package/dist/metrics/quota.test.js.map +0 -1
- package/dist/metrics/team.d.ts.map +0 -1
- package/dist/metrics/team.js.map +0 -1
- package/dist/metrics/team.test.d.ts +0 -2
- package/dist/metrics/team.test.d.ts.map +0 -1
- package/dist/metrics/team.test.js +0 -90
- package/dist/metrics/team.test.js.map +0 -1
- package/dist/node-version.d.ts.map +0 -1
- package/dist/node-version.js.map +0 -1
- package/dist/node-version.test.d.ts +0 -2
- package/dist/node-version.test.d.ts.map +0 -1
- package/dist/node-version.test.js +0 -53
- package/dist/node-version.test.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/paths.test.d.ts +0 -2
- package/dist/paths.test.d.ts.map +0 -1
- package/dist/paths.test.js +0 -32
- package/dist/paths.test.js.map +0 -1
- package/dist/pricing/catalog.d.ts.map +0 -1
- package/dist/pricing/catalog.js.map +0 -1
- package/dist/pricing/cost.d.ts.map +0 -1
- package/dist/pricing/cost.js.map +0 -1
- package/dist/pricing/cost.test.d.ts +0 -2
- package/dist/pricing/cost.test.d.ts.map +0 -1
- package/dist/pricing/cost.test.js +0 -100
- package/dist/pricing/cost.test.js.map +0 -1
- package/dist/project-of.d.ts.map +0 -1
- package/dist/project-of.js.map +0 -1
- package/dist/project-of.test.d.ts +0 -2
- package/dist/project-of.test.d.ts.map +0 -1
- package/dist/project-of.test.js +0 -203
- package/dist/project-of.test.js.map +0 -1
- package/dist/publish.d.ts.map +0 -1
- package/dist/publish.js.map +0 -1
- package/dist/publish.test.d.ts +0 -2
- package/dist/publish.test.d.ts.map +0 -1
- package/dist/publish.test.js +0 -113
- package/dist/publish.test.js.map +0 -1
- package/dist/quota.d.ts.map +0 -1
- package/dist/quota.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/server.test.d.ts +0 -2
- package/dist/server.test.d.ts.map +0 -1
- package/dist/server.test.js +0 -402
- package/dist/server.test.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/setup.test.d.ts +0 -2
- package/dist/setup.test.d.ts.map +0 -1
- package/dist/setup.test.js +0 -144
- package/dist/setup.test.js.map +0 -1
- package/dist/statusline-cache.d.ts.map +0 -1
- package/dist/statusline-cache.js.map +0 -1
- package/dist/statusline-install.d.ts.map +0 -1
- package/dist/statusline-install.js.map +0 -1
- package/dist/statusline-run.d.ts.map +0 -1
- package/dist/statusline-run.js.map +0 -1
- package/dist/statusline.d.ts.map +0 -1
- package/dist/statusline.js.map +0 -1
- package/dist/statusline.test.d.ts +0 -2
- package/dist/statusline.test.d.ts.map +0 -1
- package/dist/statusline.test.js +0 -197
- package/dist/statusline.test.js.map +0 -1
- package/dist/sync/team.d.ts.map +0 -1
- package/dist/sync/team.js.map +0 -1
- package/dist/sync/team.test.d.ts +0 -2
- package/dist/sync/team.test.d.ts.map +0 -1
- package/dist/sync/team.test.js +0 -102
- package/dist/sync/team.test.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/sync.test.d.ts +0 -2
- package/dist/sync.test.d.ts.map +0 -1
- package/dist/sync.test.js +0 -151
- package/dist/sync.test.js.map +0 -1
- package/dist/team.d.ts.map +0 -1
- package/dist/team.js.map +0 -1
- package/dist/team.test.d.ts +0 -2
- package/dist/team.test.d.ts.map +0 -1
- package/dist/team.test.js +0 -163
- package/dist/team.test.js.map +0 -1
- package/dist/updateCheck.d.ts.map +0 -1
- package/dist/updateCheck.js.map +0 -1
- package/dist/updateCheck.test.d.ts +0 -2
- package/dist/updateCheck.test.d.ts.map +0 -1
- package/dist/updateCheck.test.js +0 -27
- package/dist/updateCheck.test.js.map +0 -1
- package/dist/watchers/agents.d.ts.map +0 -1
- package/dist/watchers/agents.js.map +0 -1
- package/dist/watchers/agents.test.d.ts +0 -2
- package/dist/watchers/agents.test.d.ts.map +0 -1
- package/dist/watchers/agents.test.js +0 -96
- package/dist/watchers/agents.test.js.map +0 -1
- package/dist/watchers/claude.d.ts.map +0 -1
- package/dist/watchers/claude.js.map +0 -1
- package/dist/watchers/claude.test.d.ts +0 -2
- package/dist/watchers/claude.test.d.ts.map +0 -1
- package/dist/watchers/claude.test.js +0 -180
- package/dist/watchers/claude.test.js.map +0 -1
- package/dist/watchers/codex.d.ts.map +0 -1
- package/dist/watchers/codex.js.map +0 -1
- package/dist/watchers/codex.test.d.ts +0 -2
- package/dist/watchers/codex.test.d.ts.map +0 -1
- package/dist/watchers/codex.test.js +0 -207
- package/dist/watchers/codex.test.js.map +0 -1
- package/dist/watchers/copilot.d.ts.map +0 -1
- package/dist/watchers/copilot.js.map +0 -1
- package/dist/watchers/copilot.test.d.ts +0 -2
- package/dist/watchers/copilot.test.d.ts.map +0 -1
- package/dist/watchers/copilot.test.js +0 -98
- package/dist/watchers/copilot.test.js.map +0 -1
- package/dist/watchers/git.d.ts.map +0 -1
- package/dist/watchers/git.js.map +0 -1
- package/dist/watchers/git.test.d.ts +0 -2
- package/dist/watchers/git.test.d.ts.map +0 -1
- package/dist/watchers/git.test.js +0 -75
- package/dist/watchers/git.test.js.map +0 -1
- package/dist/watchers/identity.d.ts.map +0 -1
- package/dist/watchers/identity.js.map +0 -1
- package/dist/watchers/identity.test.d.ts +0 -2
- package/dist/watchers/identity.test.d.ts.map +0 -1
- package/dist/watchers/identity.test.js +0 -48
- package/dist/watchers/identity.test.js.map +0 -1
- package/dist/web-ui.test.d.ts +0 -2
- package/dist/web-ui.test.d.ts.map +0 -1
- package/dist/web-ui.test.js +0 -414
- package/dist/web-ui.test.js.map +0 -1
- package/dist/workitems.test.d.ts +0 -2
- package/dist/workitems.test.d.ts.map +0 -1
- package/dist/workitems.test.js +0 -59
- package/dist/workitems.test.js.map +0 -1
package/dist/db/schema.test.js
DELETED
|
@@ -1,825 +0,0 @@
|
|
|
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_sqlite_1 = require("node:sqlite");
|
|
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("./schema.js");
|
|
46
|
-
const store = __importStar(require("./store.js"));
|
|
47
|
-
function tempDb() {
|
|
48
|
-
return (0, node_path_1.join)((0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "estela-db-")), "estela.db");
|
|
49
|
-
}
|
|
50
|
-
function versionOf(db) {
|
|
51
|
-
return db.prepare("SELECT version FROM schema_version").get().version;
|
|
52
|
-
}
|
|
53
|
-
/** La versión a la que llega cualquier base: la de una recién creada. */
|
|
54
|
-
function versionDeUnaBaseNueva() {
|
|
55
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
56
|
-
try {
|
|
57
|
-
return versionOf(db);
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
db.close();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function seedProject(db) {
|
|
64
|
-
store.upsertClient(db, { id: "c", name: "Cliente", currency: "EUR" });
|
|
65
|
-
store.upsertProject(db, {
|
|
66
|
-
id: "p", clientId: "c", name: "Proyecto", repoPaths: ["/repo"],
|
|
67
|
-
billable: true, roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
function entry(over = {}) {
|
|
71
|
-
const startedAt = new Date("2026-08-25T10:00:00Z");
|
|
72
|
-
return {
|
|
73
|
-
projectId: "p", startedAt,
|
|
74
|
-
endedAt: new Date(startedAt.getTime() + 3600_000),
|
|
75
|
-
seconds: 3600, description: "Bloque", billable: true, invoiceId: null,
|
|
76
|
-
aiCost: { microUsd: 0 }, agentSeconds: 0, commitHashes: [], agents: [],
|
|
77
|
-
source: "agent", kind: "development", branch: "main",
|
|
78
|
-
...over,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
// ── Migraciones ────────────────────────────────────────────────────────
|
|
82
|
-
(0, node_test_1.test)("una base antigua se migra en vez de reventar", () => {
|
|
83
|
-
// El fallo que esto previene: CREATE TABLE IF NOT EXISTS no añade columnas a
|
|
84
|
-
// una tabla que ya existe. La sentencia no hace nada y la app peta al primer
|
|
85
|
-
// INSERT con "table has no column named source".
|
|
86
|
-
const path = tempDb();
|
|
87
|
-
// Simula una base v1: sin las columnas source ni kind.
|
|
88
|
-
const old = new node_sqlite_1.DatabaseSync(path);
|
|
89
|
-
old.exec(`
|
|
90
|
-
CREATE TABLE schema_version (version INTEGER NOT NULL);
|
|
91
|
-
INSERT INTO schema_version VALUES (1);
|
|
92
|
-
CREATE TABLE clients (id TEXT PRIMARY KEY, name TEXT NOT NULL, currency TEXT NOT NULL,
|
|
93
|
-
tax_id TEXT, email TEXT, address TEXT);
|
|
94
|
-
CREATE TABLE projects (id TEXT PRIMARY KEY, client_id TEXT NOT NULL, name TEXT NOT NULL,
|
|
95
|
-
billable INTEGER NOT NULL DEFAULT 1,
|
|
96
|
-
rounding_minutes INTEGER NOT NULL DEFAULT 0,
|
|
97
|
-
ai_cost_policy TEXT NOT NULL DEFAULT 'absorbed');
|
|
98
|
-
CREATE TABLE time_entries (
|
|
99
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, started_at TEXT NOT NULL,
|
|
100
|
-
local_date TEXT NOT NULL DEFAULT '', ended_at TEXT NOT NULL, seconds INTEGER NOT NULL,
|
|
101
|
-
description TEXT NOT NULL, billable INTEGER NOT NULL DEFAULT 1,
|
|
102
|
-
approved INTEGER NOT NULL DEFAULT 0, invoice_id TEXT,
|
|
103
|
-
ai_micro_usd INTEGER NOT NULL DEFAULT 0, agent_seconds INTEGER NOT NULL DEFAULT 0,
|
|
104
|
-
commit_hashes TEXT NOT NULL DEFAULT '', agents TEXT NOT NULL DEFAULT '');
|
|
105
|
-
INSERT INTO clients VALUES ('c','Cliente','EUR',NULL,NULL,NULL);
|
|
106
|
-
INSERT INTO projects VALUES ('p','c','Proyecto',1,0,'absorbed');
|
|
107
|
-
INSERT INTO time_entries VALUES
|
|
108
|
-
('viejo','p','2026-07-01T10:00:00Z','2026-07-01','2026-07-01T11:00:00Z',
|
|
109
|
-
3600,'Trabajo anterior',1,0,NULL,0,0,'','');
|
|
110
|
-
`);
|
|
111
|
-
old.close();
|
|
112
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
113
|
-
try {
|
|
114
|
-
const columns = db.prepare("PRAGMA table_info(time_entries)").all()
|
|
115
|
-
.map((c) => c.name);
|
|
116
|
-
strict_1.default.ok(columns.includes("source"), "la migración añadió source");
|
|
117
|
-
strict_1.default.ok(columns.includes("kind"), "la migración añadió kind");
|
|
118
|
-
strict_1.default.equal(versionOf(db), versionDeUnaBaseNueva(), "una base migrada termina en la misma versión que una nueva, no en la de la última migración que corrió");
|
|
119
|
-
// Lo que ya había sigue ahí. Migrar no puede perder el trabajo de meses.
|
|
120
|
-
const previo = db.prepare("SELECT * FROM time_entries WHERE id='viejo'")
|
|
121
|
-
.get();
|
|
122
|
-
strict_1.default.ok(previo, "la fila anterior sobrevive");
|
|
123
|
-
strict_1.default.equal(previo["description"], "Trabajo anterior");
|
|
124
|
-
strict_1.default.equal(previo["source"], "agent", "las filas viejas quedan como 'agent'");
|
|
125
|
-
// Y ahora sí se puede escribir con los campos nuevos.
|
|
126
|
-
seedProject(db);
|
|
127
|
-
strict_1.default.doesNotThrow(() => store.saveTimeEntry(db, entry({ source: "manual" })));
|
|
128
|
-
}
|
|
129
|
-
finally {
|
|
130
|
-
db.close();
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
(0, node_test_1.test)("abrir dos veces la misma base no vuelve a migrar ni falla", () => {
|
|
134
|
-
const path = tempDb();
|
|
135
|
-
(0, schema_js_1.openDatabase)(path).close();
|
|
136
|
-
strict_1.default.doesNotThrow(() => (0, schema_js_1.openDatabase)(path).close());
|
|
137
|
-
});
|
|
138
|
-
(0, node_test_1.test)("una base de una versión futura se rechaza en vez de corromperse", () => {
|
|
139
|
-
const path = tempDb();
|
|
140
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
141
|
-
db.prepare("UPDATE schema_version SET version = ?").run(999);
|
|
142
|
-
db.close();
|
|
143
|
-
strict_1.default.throws(() => (0, schema_js_1.openDatabase)(path), /v999|Actualiza Estela/);
|
|
144
|
-
});
|
|
145
|
-
// ── Lo escrito a mano es intocable ─────────────────────────────────────
|
|
146
|
-
(0, node_test_1.test)("reimportar NO pisa una imputación manual", () => {
|
|
147
|
-
// Reunión de dos horas apuntada a mano el lunes. El martes se reimporta y
|
|
148
|
-
// el bloque deducido de los agentes cae con el mismo id determinista.
|
|
149
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
150
|
-
try {
|
|
151
|
-
seedProject(db);
|
|
152
|
-
const id = "te_p_2026-08-25_main";
|
|
153
|
-
store.saveTimeEntry(db, {
|
|
154
|
-
...entry({ source: "manual", kind: "meeting", seconds: 7200,
|
|
155
|
-
description: "Reunión con el cliente" }),
|
|
156
|
-
id,
|
|
157
|
-
});
|
|
158
|
-
// El import intenta sobrescribir esa misma fila.
|
|
159
|
-
store.saveTimeEntry(db, {
|
|
160
|
-
...entry({ source: "agent", seconds: 600, description: "Desarrollo en main" }),
|
|
161
|
-
id,
|
|
162
|
-
});
|
|
163
|
-
const row = db.prepare("SELECT * FROM time_entries WHERE id = ?")
|
|
164
|
-
.get(id);
|
|
165
|
-
strict_1.default.equal(row["description"], "Reunión con el cliente", "el texto manual se conserva");
|
|
166
|
-
strict_1.default.equal(row["seconds"], 7200, "las 2 horas apuntadas se conservan");
|
|
167
|
-
strict_1.default.equal(row["kind"], "meeting");
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
170
|
-
db.close();
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
(0, node_test_1.test)("reimportar sí actualiza lo deducido de los agentes", () => {
|
|
174
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
175
|
-
try {
|
|
176
|
-
seedProject(db);
|
|
177
|
-
const id = "te_p_2026-08-25_main";
|
|
178
|
-
store.saveTimeEntry(db, { ...entry({ seconds: 600, description: "Primera lectura" }), id });
|
|
179
|
-
store.saveTimeEntry(db, { ...entry({ seconds: 3600, description: "Lectura corregida" }), id });
|
|
180
|
-
const row = db.prepare("SELECT * FROM time_entries WHERE id = ?")
|
|
181
|
-
.get(id);
|
|
182
|
-
strict_1.default.equal(row["seconds"], 3600, "lo deducido sí se rehace");
|
|
183
|
-
strict_1.default.equal(row["description"], "Lectura corregida");
|
|
184
|
-
}
|
|
185
|
-
finally {
|
|
186
|
-
db.close();
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
(0, node_test_1.test)("las horas manuales no traen coste de IA ni commits", () => {
|
|
190
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
191
|
-
try {
|
|
192
|
-
seedProject(db);
|
|
193
|
-
const id = store.saveTimeEntry(db, entry({ source: "manual", kind: "travel" }));
|
|
194
|
-
const saved = store.getTimeEntries(db, "p").find((e) => e.id === id);
|
|
195
|
-
strict_1.default.equal(saved.aiCost.microUsd, 0);
|
|
196
|
-
strict_1.default.deepEqual(saved.commitHashes, []);
|
|
197
|
-
strict_1.default.equal(saved.kind, "travel");
|
|
198
|
-
strict_1.default.equal(saved.source, "manual");
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
db.close();
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
(0, node_test_1.test)("reimportar rehace los commits de un bloque, no solo su texto", () => {
|
|
205
|
-
// El fallo que esto previene: un bloque creado cuando aún no se capturaban
|
|
206
|
-
// los commits del proyecto se quedaba sin ellos para siempre. El ON CONFLICT
|
|
207
|
-
// actualizaba la descripción pero no commit_hashes, así que el respaldo que
|
|
208
|
-
// justifica esa línea de la factura no aparecía nunca.
|
|
209
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
210
|
-
try {
|
|
211
|
-
seedProject(db);
|
|
212
|
-
const id = "te_p_2026-08-26_main";
|
|
213
|
-
// Primer import: el autor no estaba configurado, no hay commits.
|
|
214
|
-
store.saveTimeEntry(db, {
|
|
215
|
-
...entry({ description: "Desarrollo en main", commitHashes: [] }), id,
|
|
216
|
-
});
|
|
217
|
-
// Segundo import, ya con el autor correcto.
|
|
218
|
-
store.saveTimeEntry(db, {
|
|
219
|
-
...entry({ description: "feat: filtro de Canarias", commitHashes: ["b942998abe"] }), id,
|
|
220
|
-
});
|
|
221
|
-
const saved = store.getTimeEntries(db, "p").find((e) => e.id === id);
|
|
222
|
-
strict_1.default.equal(saved.description, "feat: filtro de Canarias");
|
|
223
|
-
strict_1.default.deepEqual(saved.commitHashes, ["b942998abe"], "los commits también se rehacen");
|
|
224
|
-
}
|
|
225
|
-
finally {
|
|
226
|
-
db.close();
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
(0, node_test_1.test)("una imputación manual no recibe commits al reimportar", () => {
|
|
230
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
231
|
-
try {
|
|
232
|
-
seedProject(db);
|
|
233
|
-
const id = "te_p_2026-08-26_main";
|
|
234
|
-
store.saveTimeEntry(db, {
|
|
235
|
-
...entry({ source: "manual", kind: "meeting", description: "Reunión", commitHashes: [] }), id,
|
|
236
|
-
});
|
|
237
|
-
store.saveTimeEntry(db, {
|
|
238
|
-
...entry({ description: "Desarrollo", commitHashes: ["abc123"] }), id,
|
|
239
|
-
});
|
|
240
|
-
const saved = store.getTimeEntries(db, "p").find((e) => e.id === id);
|
|
241
|
-
strict_1.default.equal(saved.description, "Reunión");
|
|
242
|
-
strict_1.default.deepEqual(saved.commitHashes, []);
|
|
243
|
-
}
|
|
244
|
-
finally {
|
|
245
|
-
db.close();
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
// ── Cuándo un panel publicado se queda atrás ───────────────────────────
|
|
249
|
-
(0, node_test_1.test)("un panel se queda atrás solo si los datos cambian de verdad", () => {
|
|
250
|
-
// El fallo que esto previene: comparar contra la hora del trabajo en vez de
|
|
251
|
-
// la de escritura. Una reunión anotada esta tarde lleva fecha de las 10:00,
|
|
252
|
-
// así que parecía anterior a la publicación y el aviso no saltaba nunca.
|
|
253
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
254
|
-
try {
|
|
255
|
-
seedProject(db);
|
|
256
|
-
const id = "te_p_2026-08-26_main";
|
|
257
|
-
const T = (min) => new Date(`2026-08-27T10:${String(min).padStart(2, "0")}:00Z`);
|
|
258
|
-
store.saveTimeEntry(db, { ...entry({ seconds: 3600 }), id }, T(0));
|
|
259
|
-
store.recordPublication(db, "p", "tok", "https://x.dev", T(1));
|
|
260
|
-
strict_1.default.equal(store.listPublications(db)[0].staleBlocks, 0, "recién publicado");
|
|
261
|
-
// Reimportar los mismos valores no cambia nada que el cliente vea.
|
|
262
|
-
store.saveTimeEntry(db, { ...entry({ seconds: 3600 }), id }, T(2));
|
|
263
|
-
strict_1.default.equal(store.listPublications(db)[0].staleBlocks, 0, "un reimport idéntico no envejece el panel");
|
|
264
|
-
// Cambiar la duración sí.
|
|
265
|
-
store.saveTimeEntry(db, { ...entry({ seconds: 7200 }), id }, T(3));
|
|
266
|
-
strict_1.default.equal(store.listPublications(db)[0].staleBlocks, 1, "cambiar las horas sí lo envejece");
|
|
267
|
-
}
|
|
268
|
-
finally {
|
|
269
|
-
db.close();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
(0, node_test_1.test)("trabajo nuevo posterior a la publicación cuenta como desfase", () => {
|
|
273
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
274
|
-
try {
|
|
275
|
-
seedProject(db);
|
|
276
|
-
store.recordPublication(db, "p", "tok", null, new Date("2026-08-27T10:00:00Z"));
|
|
277
|
-
// Fecha de trabajo anterior a la publicación, pero anotado después: es lo
|
|
278
|
-
// que pasa al apuntar a mano una reunión de esta mañana.
|
|
279
|
-
store.saveTimeEntry(db, {
|
|
280
|
-
...entry({ source: "manual", kind: "meeting" }),
|
|
281
|
-
id: "te_manual",
|
|
282
|
-
}, new Date("2026-08-27T18:00:00Z"));
|
|
283
|
-
strict_1.default.equal(store.listPublications(db)[0].staleBlocks, 1);
|
|
284
|
-
}
|
|
285
|
-
finally {
|
|
286
|
-
db.close();
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
(0, node_test_1.test)("republicar pone el contador a cero y conserva el enlace", () => {
|
|
290
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
291
|
-
try {
|
|
292
|
-
seedProject(db);
|
|
293
|
-
store.recordPublication(db, "p", "tok-1", "https://x.dev", new Date("2026-08-27T10:00:00Z"));
|
|
294
|
-
store.saveTimeEntry(db, { ...entry(), id: "te_a" }, new Date("2026-08-27T11:00:00Z"));
|
|
295
|
-
strict_1.default.equal(store.listPublications(db)[0].staleBlocks, 1);
|
|
296
|
-
// Republicar sin repetir la URL: no debe perderse.
|
|
297
|
-
store.recordPublication(db, "p", "tok-1", null, new Date("2026-08-27T12:00:00Z"));
|
|
298
|
-
const pub = store.listPublications(db)[0];
|
|
299
|
-
strict_1.default.equal(pub.staleBlocks, 0);
|
|
300
|
-
strict_1.default.equal(pub.baseUrl, "https://x.dev", "el dominio se conserva");
|
|
301
|
-
strict_1.default.equal(pub.token, "tok-1", "y el token también, o el cliente pierde su enlace");
|
|
302
|
-
}
|
|
303
|
-
finally {
|
|
304
|
-
db.close();
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
(0, node_test_1.test)("una base nueva tiene exactamente las columnas de una migrada", () => {
|
|
308
|
-
// El fallo real: `ai_budget_micro_usd` se añadió en la migración v10 y se
|
|
309
|
-
// olvidó en el esquema base. Una base nueva no ejecuta migraciones —el código
|
|
310
|
-
// asume que el esquema ya está al día— así que a cada instalación nueva le
|
|
311
|
-
// faltaba esa columna y la vista de equipo respondía 500.
|
|
312
|
-
//
|
|
313
|
-
// Nadie lo habría visto desarrollando: las bases existentes sí se migran. Lo
|
|
314
|
-
// descubre quien instala por primera vez, que es justo a quien no puedes
|
|
315
|
-
// permitirte fallarle.
|
|
316
|
-
const nueva = (0, schema_js_1.openDatabase)(":memory:");
|
|
317
|
-
// Una base "vieja": se le quitan las columnas que añadieron las migraciones
|
|
318
|
-
// y se la marca en la versión anterior, para que migre de verdad.
|
|
319
|
-
const migrada = (0, schema_js_1.openDatabase)(":memory:");
|
|
320
|
-
try {
|
|
321
|
-
for (const tabla of ["projects", "commits", "time_entries", "agent_turns"]) {
|
|
322
|
-
const cols = (db) => db.prepare(`PRAGMA table_info(${tabla})`).all()
|
|
323
|
-
.map((c) => c.name).sort((a, b) => a.localeCompare(b));
|
|
324
|
-
strict_1.default.deepEqual(cols(nueva), cols(migrada), `difieren en ${tabla}`);
|
|
325
|
-
}
|
|
326
|
-
// Y las columnas que añaden las migraciones tienen que estar de verdad.
|
|
327
|
-
const projectCols = nueva.prepare("PRAGMA table_info(projects)").all().map((c) => c.name);
|
|
328
|
-
strict_1.default.ok(projectCols.includes("ai_budget_micro_usd"));
|
|
329
|
-
const commitCols = nueva.prepare("PRAGMA table_info(commits)").all().map((c) => c.name);
|
|
330
|
-
strict_1.default.ok(commitCols.includes("author_name"));
|
|
331
|
-
}
|
|
332
|
-
finally {
|
|
333
|
-
nueva.close();
|
|
334
|
-
migrada.close();
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
(0, node_test_1.test)("una base nueva sirve la vista de equipo sin migrar nada", () => {
|
|
338
|
-
// La consulta concreta que reventaba en cada instalación nueva.
|
|
339
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
340
|
-
try {
|
|
341
|
-
strict_1.default.doesNotThrow(() => db.prepare("SELECT id, ai_budget_micro_usd FROM projects WHERE ai_budget_micro_usd IS NOT NULL").all());
|
|
342
|
-
}
|
|
343
|
-
finally {
|
|
344
|
-
db.close();
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
// ── Cierre de proyecto ──────────────────────────────────────────────────
|
|
348
|
-
(0, node_test_1.test)("una base v11 (sin closed_at) se migra a v12 sin perder lo que ya había", () => {
|
|
349
|
-
// La misma clase de fallo que el primer test del fichero: CREATE TABLE IF
|
|
350
|
-
// NOT EXISTS no toca una tabla que ya existe, así que sin la migración
|
|
351
|
-
// explícita, closeProject() reventaría con "no such column: closed_at" en
|
|
352
|
-
// cualquier base creada antes de esta versión.
|
|
353
|
-
const path = tempDb();
|
|
354
|
-
const old = new node_sqlite_1.DatabaseSync(path);
|
|
355
|
-
old.exec(`
|
|
356
|
-
CREATE TABLE schema_version (version INTEGER NOT NULL);
|
|
357
|
-
INSERT INTO schema_version VALUES (11);
|
|
358
|
-
CREATE TABLE clients (id TEXT PRIMARY KEY, name TEXT NOT NULL, currency TEXT NOT NULL,
|
|
359
|
-
tax_id TEXT, email TEXT, address TEXT);
|
|
360
|
-
CREATE TABLE projects (id TEXT PRIMARY KEY, client_id TEXT NOT NULL, name TEXT NOT NULL,
|
|
361
|
-
billable INTEGER NOT NULL DEFAULT 1,
|
|
362
|
-
rounding_minutes INTEGER NOT NULL DEFAULT 0,
|
|
363
|
-
ai_cost_policy TEXT NOT NULL DEFAULT 'absorbed',
|
|
364
|
-
kind TEXT NOT NULL DEFAULT 'client',
|
|
365
|
-
ai_budget_micro_usd INTEGER);
|
|
366
|
-
CREATE TABLE project_repos (project_id TEXT NOT NULL, repo_path TEXT NOT NULL);
|
|
367
|
-
CREATE TABLE project_authors (project_id TEXT NOT NULL, author_email TEXT NOT NULL);
|
|
368
|
-
CREATE TABLE rate_periods (project_id TEXT NOT NULL, hourly_minor INTEGER NOT NULL,
|
|
369
|
-
currency TEXT NOT NULL, effective_from TEXT NOT NULL,
|
|
370
|
-
effective_to TEXT);
|
|
371
|
-
CREATE TABLE time_entries (
|
|
372
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, started_at TEXT NOT NULL,
|
|
373
|
-
local_date TEXT NOT NULL DEFAULT '', ended_at TEXT NOT NULL, seconds INTEGER NOT NULL,
|
|
374
|
-
description TEXT NOT NULL, billable INTEGER NOT NULL DEFAULT 1,
|
|
375
|
-
approved INTEGER NOT NULL DEFAULT 0, invoice_id TEXT,
|
|
376
|
-
ai_micro_usd INTEGER NOT NULL DEFAULT 0, agent_seconds INTEGER NOT NULL DEFAULT 0,
|
|
377
|
-
commit_hashes TEXT NOT NULL DEFAULT '', agents TEXT NOT NULL DEFAULT '',
|
|
378
|
-
source TEXT NOT NULL DEFAULT 'agent', kind TEXT NOT NULL DEFAULT 'development',
|
|
379
|
-
branch TEXT, updated_at TEXT NOT NULL DEFAULT '');
|
|
380
|
-
INSERT INTO clients VALUES ('c','Cliente','EUR',NULL,NULL,NULL);
|
|
381
|
-
INSERT INTO projects VALUES ('p','c','Proyecto',1,0,'absorbed','client',NULL);
|
|
382
|
-
`);
|
|
383
|
-
old.close();
|
|
384
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
385
|
-
try {
|
|
386
|
-
const columns = db.prepare("PRAGMA table_info(projects)").all()
|
|
387
|
-
.map((c) => c.name);
|
|
388
|
-
strict_1.default.ok(columns.includes("closed_at"), "la migración añadió closed_at");
|
|
389
|
-
strict_1.default.equal(versionOf(db), versionDeUnaBaseNueva());
|
|
390
|
-
// El proyecto de antes de migrar sigue ahí, y closeProject funciona ya.
|
|
391
|
-
const antes = store.getProject(db, "p");
|
|
392
|
-
strict_1.default.equal(antes.closedAt, null, "una base migrada empieza con el proyecto abierto");
|
|
393
|
-
store.closeProject(db, "p", new Date("2026-09-17T00:00:00Z"));
|
|
394
|
-
strict_1.default.equal(store.getProject(db, "p").closedAt?.toISOString(), "2026-09-17T00:00:00.000Z");
|
|
395
|
-
}
|
|
396
|
-
finally {
|
|
397
|
-
db.close();
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
(0, node_test_1.test)("cerrar es idempotente: no le cambia la fecha a un proyecto ya cerrado", () => {
|
|
401
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
402
|
-
try {
|
|
403
|
-
seedProject(db);
|
|
404
|
-
store.closeProject(db, "p", new Date("2026-09-01T00:00:00Z"));
|
|
405
|
-
store.closeProject(db, "p", new Date("2026-09-17T00:00:00Z"));
|
|
406
|
-
strict_1.default.equal(store.getProject(db, "p").closedAt?.toISOString(), "2026-09-01T00:00:00.000Z", "la segunda llamada no debe mover la fecha del cierre");
|
|
407
|
-
}
|
|
408
|
-
finally {
|
|
409
|
-
db.close();
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
(0, node_test_1.test)("reabrir deja el proyecto activo, y se puede volver a cerrar después", () => {
|
|
413
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
414
|
-
try {
|
|
415
|
-
seedProject(db);
|
|
416
|
-
store.closeProject(db, "p", new Date("2026-09-01T00:00:00Z"));
|
|
417
|
-
store.reopenProject(db, "p");
|
|
418
|
-
strict_1.default.equal(store.getProject(db, "p").closedAt, null);
|
|
419
|
-
// Reabierto, closeProject vuelve a actuar (no lo bloquea el "ya cerrado").
|
|
420
|
-
store.closeProject(db, "p", new Date("2026-09-17T00:00:00Z"));
|
|
421
|
-
strict_1.default.equal(store.getProject(db, "p").closedAt?.toISOString(), "2026-09-17T00:00:00.000Z");
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
db.close();
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
(0, node_test_1.test)("cerrar un proyecto no borra ni bloquea sus imputaciones", () => {
|
|
428
|
-
// La decisión de diseño que sostiene todo esto: cerrar es un aviso, no un
|
|
429
|
-
// candado. Perder un bloque real capturado de verdad sería peor que dejar
|
|
430
|
-
// que uno se cuele después del cierre — para eso está el aviso de doctor.
|
|
431
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
432
|
-
try {
|
|
433
|
-
seedProject(db);
|
|
434
|
-
const id = store.saveTimeEntry(db, entry());
|
|
435
|
-
store.closeProject(db, "p", new Date("2026-08-25T09:00:00Z"));
|
|
436
|
-
strict_1.default.doesNotThrow(() => store.saveTimeEntry(db, { ...entry(), id: "te_otro" }));
|
|
437
|
-
strict_1.default.ok(store.getTimeEntries(db, "p").some((e) => e.id === id), "lo anterior sigue ahí");
|
|
438
|
-
strict_1.default.equal(store.getTimeEntries(db, "p").length, 2, "y lo nuevo se guarda igual");
|
|
439
|
-
}
|
|
440
|
-
finally {
|
|
441
|
-
db.close();
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
// ── Orden de las migraciones ────────────────────────────────────────────
|
|
445
|
-
(0, node_test_1.test)("una base de varias versiones atrás llega a la actual de una sola vez", () => {
|
|
446
|
-
// El fallo real: las migraciones se recorrían de la más nueva a la más vieja
|
|
447
|
-
// y se anotaba como versión la última que corría, así que una base v3 quedaba
|
|
448
|
-
// en "v4" con todo aplicado y solo convergía a fuerza de abrirla otra vez.
|
|
449
|
-
const path = tempDb();
|
|
450
|
-
const nueva = (0, schema_js_1.openDatabase)(path);
|
|
451
|
-
nueva.exec("UPDATE schema_version SET version = 3");
|
|
452
|
-
nueva.close();
|
|
453
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
454
|
-
try {
|
|
455
|
-
strict_1.default.equal(versionOf(db), versionDeUnaBaseNueva(), "una sola apertura basta para dejarla al día");
|
|
456
|
-
}
|
|
457
|
-
finally {
|
|
458
|
-
db.close();
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
// ── Idioma de los documentos de cada cliente ────────────────────────────
|
|
462
|
-
(0, node_test_1.test)("una base v12 (sin language) se migra y el idioma se guarda y se lee", () => {
|
|
463
|
-
const path = tempDb();
|
|
464
|
-
const prep = (0, schema_js_1.openDatabase)(path);
|
|
465
|
-
prep.exec("INSERT INTO clients (id, name, currency) VALUES ('c', 'Cliente', 'EUR')");
|
|
466
|
-
prep.exec("ALTER TABLE clients DROP COLUMN language");
|
|
467
|
-
prep.exec("UPDATE schema_version SET version = 12");
|
|
468
|
-
prep.close();
|
|
469
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
470
|
-
try {
|
|
471
|
-
const columns = db.prepare("PRAGMA table_info(clients)").all()
|
|
472
|
-
.map((c) => c.name);
|
|
473
|
-
strict_1.default.ok(columns.includes("language"), "la migración añadió language");
|
|
474
|
-
strict_1.default.equal(versionOf(db), versionDeUnaBaseNueva());
|
|
475
|
-
// Un cliente de antes de migrar no tiene idioma: sigue el de la terminal,
|
|
476
|
-
// que es lo que pasaba antes de que existiera el campo.
|
|
477
|
-
strict_1.default.equal(store.getClient(db, "c").language, undefined);
|
|
478
|
-
store.upsertClient(db, { id: "c", name: "Cliente", currency: "EUR", language: "en" });
|
|
479
|
-
strict_1.default.equal(store.getClient(db, "c").language, "en");
|
|
480
|
-
store.upsertClient(db, { id: "c", name: "Cliente", currency: "EUR" });
|
|
481
|
-
strict_1.default.equal(store.getClient(db, "c").language, undefined, "upsertClient escribe lo que recibe: conservar el idioma es cosa de quien lo llama");
|
|
482
|
-
}
|
|
483
|
-
finally {
|
|
484
|
-
db.close();
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
(0, node_test_1.test)("un valor de idioma que no existe en la base se ignora en vez de propagarse", () => {
|
|
488
|
-
const db = (0, schema_js_1.openDatabase)(":memory:");
|
|
489
|
-
try {
|
|
490
|
-
db.exec("INSERT INTO clients (id, name, currency, language) VALUES ('c', 'X', 'EUR', 'klingon')");
|
|
491
|
-
strict_1.default.equal(store.getClient(db, "c").language, undefined);
|
|
492
|
-
}
|
|
493
|
-
finally {
|
|
494
|
-
db.close();
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
/* ── Las facturas emitidas se pueden volver a ver ──────────────────────── */
|
|
498
|
-
(0, node_test_1.test)("listInvoices devuelve lo emitido, de lo más nuevo a lo más viejo", () => {
|
|
499
|
-
// Reportado usándolo: al cortar y facturar, el proyecto desaparecía de la
|
|
500
|
-
// pestaña Informes —que solo lista lo PENDIENTE— y no había ningún sitio
|
|
501
|
-
// donde volver a ver el informe. La factura estaba guardada y era invisible.
|
|
502
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
503
|
-
try {
|
|
504
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
505
|
-
store.upsertProject(db, {
|
|
506
|
-
id: "web", clientId: "acme", name: "Web de ACME", billable: true,
|
|
507
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client", repoPaths: [],
|
|
508
|
-
});
|
|
509
|
-
for (const [num, corte] of [["INF-2026-001", "2026-08-31"], ["INF-2026-002", "2026-09-30"]]) {
|
|
510
|
-
db.prepare(`
|
|
511
|
-
INSERT INTO invoices (id, number, client_id, project_id, issued_at, cutoff_at,
|
|
512
|
-
period_start, currency, subtotal_minor, total_minor, total_seconds,
|
|
513
|
-
ai_micro_usd, lines_json)
|
|
514
|
-
VALUES (?, ?, 'acme', 'web', ?, ?, '2026-07-01T00:00:00.000Z', 'EUR',
|
|
515
|
-
170237, 170237, 471600, 0, '[]')
|
|
516
|
-
`).run(`inv_${num}`, num, `${corte}T12:00:00.000Z`, `${corte}T23:59:59.000Z`);
|
|
517
|
-
}
|
|
518
|
-
const facturas = store.listInvoices(db);
|
|
519
|
-
strict_1.default.equal(facturas.length, 2);
|
|
520
|
-
strict_1.default.equal(facturas[0].number, "INF-2026-002", "la más reciente primero");
|
|
521
|
-
strict_1.default.equal(facturas[0].projectName, "Web de ACME", "con el nombre, no solo el id");
|
|
522
|
-
strict_1.default.equal(facturas[0].clientName, "ACME");
|
|
523
|
-
strict_1.default.equal(facturas[0].totalSeconds, 471600);
|
|
524
|
-
strict_1.default.equal(facturas[0].totalMinor, 170237);
|
|
525
|
-
strict_1.default.equal(facturas[0].currency, "EUR");
|
|
526
|
-
}
|
|
527
|
-
finally {
|
|
528
|
-
db.close();
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
(0, node_test_1.test)("listInvoices puede filtrar por proyecto", () => {
|
|
532
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
533
|
-
try {
|
|
534
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
535
|
-
for (const p of ["web", "api"]) {
|
|
536
|
-
store.upsertProject(db, {
|
|
537
|
-
id: p, clientId: "acme", name: p.toUpperCase(), billable: true,
|
|
538
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client", repoPaths: [],
|
|
539
|
-
});
|
|
540
|
-
db.prepare(`
|
|
541
|
-
INSERT INTO invoices (id, number, client_id, project_id, issued_at, cutoff_at,
|
|
542
|
-
period_start, currency, subtotal_minor, total_minor, total_seconds,
|
|
543
|
-
ai_micro_usd, lines_json)
|
|
544
|
-
VALUES (?, ?, 'acme', ?, '2026-09-30T12:00:00.000Z', '2026-09-30T23:59:59.000Z',
|
|
545
|
-
'2026-07-01T00:00:00.000Z', 'EUR', 100, 100, 3600, 0, '[]')
|
|
546
|
-
`).run(`inv_${p}`, `INF-${p}`, p);
|
|
547
|
-
}
|
|
548
|
-
strict_1.default.equal(store.listInvoices(db, "web").length, 1);
|
|
549
|
-
strict_1.default.equal(store.listInvoices(db, "web")[0].projectId, "web");
|
|
550
|
-
strict_1.default.equal(store.listInvoices(db).length, 2);
|
|
551
|
-
}
|
|
552
|
-
finally {
|
|
553
|
-
db.close();
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
(0, node_test_1.test)("sin facturas emitidas devuelve una lista vacía, no explota", () => {
|
|
557
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
558
|
-
try {
|
|
559
|
-
strict_1.default.deepEqual(store.listInvoices(db), []);
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
db.close();
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
(0, node_test_1.test)("getInvoice reconstruye la factura entera para poder reimprimirla", () => {
|
|
566
|
-
// El tipo Invoice congela importes y reparto de IA al emitir, con este
|
|
567
|
-
// motivo escrito: "un documento que cambia solo no es un documento".
|
|
568
|
-
// Faltaba poder leerlo de vuelta.
|
|
569
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
570
|
-
try {
|
|
571
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
572
|
-
store.upsertProject(db, {
|
|
573
|
-
id: "web", clientId: "acme", name: "Web", billable: true, repoPaths: [],
|
|
574
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
575
|
-
});
|
|
576
|
-
db.prepare(`
|
|
577
|
-
INSERT INTO invoices (id, number, client_id, project_id, issued_at, cutoff_at,
|
|
578
|
-
period_start, currency, subtotal_minor, total_minor, total_seconds,
|
|
579
|
-
ai_micro_usd, usd_fx_rate, ai_amort_minor, ai_amort_cur, notes, lines_json)
|
|
580
|
-
VALUES ('inv_1', 'INF-2026-001', 'acme', 'web', '2026-09-23T12:00:00.000Z',
|
|
581
|
-
'2026-09-23T23:59:59.000Z', '2026-07-29T03:21:05.036Z', 'EUR',
|
|
582
|
-
170237, 170237, 471600, 4200000, 0.92, 1500, 'EUR', 'agosto',
|
|
583
|
-
'[{"description":"Desarrollo","seconds":471600,"amount":{"amount":170237,"currency":"EUR"}}]')
|
|
584
|
-
`).run();
|
|
585
|
-
const f = store.getInvoice(db, "INF-2026-001");
|
|
586
|
-
strict_1.default.ok(f, "la factura tiene que aparecer por su número");
|
|
587
|
-
strict_1.default.equal(f.number, "INF-2026-001");
|
|
588
|
-
strict_1.default.equal(f.total.amount, 170237);
|
|
589
|
-
strict_1.default.equal(f.total.currency, "EUR");
|
|
590
|
-
strict_1.default.equal(f.totalSeconds, 471600);
|
|
591
|
-
strict_1.default.equal(f.lines.length, 1, "las líneas congeladas, no recalculadas");
|
|
592
|
-
strict_1.default.equal(f.lines[0].description, "Desarrollo");
|
|
593
|
-
strict_1.default.equal(f.aiCost.microUsd, 4200000);
|
|
594
|
-
strict_1.default.equal(f.usdFxRate, 0.92);
|
|
595
|
-
strict_1.default.equal(f.aiAmortized?.amount, 1500, "el reparto de IA, tal y como se congeló");
|
|
596
|
-
strict_1.default.equal(f.notes, "agosto");
|
|
597
|
-
strict_1.default.ok(f.cutoffAt instanceof Date, "las fechas vuelven como Date, no como texto");
|
|
598
|
-
}
|
|
599
|
-
finally {
|
|
600
|
-
db.close();
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
(0, node_test_1.test)("getInvoice con un número que no existe devuelve null", () => {
|
|
604
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
605
|
-
try {
|
|
606
|
-
strict_1.default.equal(store.getInvoice(db, "INF-2099-999"), null);
|
|
607
|
-
}
|
|
608
|
-
finally {
|
|
609
|
-
db.close();
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
/* ── Un ajuste a mano sobrevive al reimport ────────────────────────────── */
|
|
613
|
-
function entradaDeAgente(dbPath, seconds) {
|
|
614
|
-
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
615
|
-
try {
|
|
616
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
617
|
-
store.upsertProject(db, {
|
|
618
|
-
id: "web", clientId: "acme", name: "Web", billable: true, repoPaths: [],
|
|
619
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
620
|
-
});
|
|
621
|
-
return store.saveTimeEntry(db, {
|
|
622
|
-
id: "te_fija", projectId: "web",
|
|
623
|
-
startedAt: new Date("2026-09-23T09:00:00.000Z"),
|
|
624
|
-
endedAt: new Date("2026-09-23T10:00:00.000Z"),
|
|
625
|
-
seconds, description: "feat: cartera de servicios", billable: true,
|
|
626
|
-
invoiceId: null, aiCost: { microUsd: 0 }, agentSeconds: seconds,
|
|
627
|
-
commitHashes: [], agents: [], source: "agent", kind: "development", branch: "main",
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
finally {
|
|
631
|
-
db.close();
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
(0, node_test_1.test)("un ajuste a mano no se pierde al reimportar", () => {
|
|
635
|
-
// Reportado con datos reales: se editaba un bloque a 5h 50m, el auto-import
|
|
636
|
-
// corría a los cinco minutos y volvía a dejarlo en lo que midió el agente.
|
|
637
|
-
const path = tempDb();
|
|
638
|
-
entradaDeAgente(path, 9504); // 2h 38m medidas
|
|
639
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
640
|
-
try {
|
|
641
|
-
store.adjustEntrySeconds(db, "te_fija", 21000, "Reunión con el cliente e investigación");
|
|
642
|
-
// El reimport vuelve a traer lo que mide el agente, como siempre.
|
|
643
|
-
store.saveTimeEntry(db, {
|
|
644
|
-
id: "te_fija", projectId: "web",
|
|
645
|
-
startedAt: new Date("2026-09-23T09:00:00.000Z"),
|
|
646
|
-
endedAt: new Date("2026-09-23T10:00:00.000Z"),
|
|
647
|
-
seconds: 9504, description: "feat: cartera de servicios", billable: true,
|
|
648
|
-
invoiceId: null, aiCost: { microUsd: 0 }, agentSeconds: 9504,
|
|
649
|
-
commitHashes: [], agents: [], source: "agent", kind: "development", branch: "main",
|
|
650
|
-
});
|
|
651
|
-
const r = db.prepare("SELECT seconds, measured_seconds, adjust_reason FROM time_entries WHERE id = 'te_fija'")
|
|
652
|
-
.get();
|
|
653
|
-
strict_1.default.equal(r.seconds, 21000, "lo ajustado manda: es lo que se factura");
|
|
654
|
-
strict_1.default.equal(r.measured_seconds, 9504, "y lo medido se guarda, para poder enseñar la diferencia");
|
|
655
|
-
strict_1.default.match(r.adjust_reason, /Reunión con el cliente/);
|
|
656
|
-
}
|
|
657
|
-
finally {
|
|
658
|
-
db.close();
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
(0, node_test_1.test)("sin ajuste, el reimport sigue mandando", () => {
|
|
662
|
-
const path = tempDb();
|
|
663
|
-
entradaDeAgente(path, 3600);
|
|
664
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
665
|
-
try {
|
|
666
|
-
store.saveTimeEntry(db, {
|
|
667
|
-
id: "te_fija", projectId: "web",
|
|
668
|
-
startedAt: new Date("2026-09-23T09:00:00.000Z"),
|
|
669
|
-
endedAt: new Date("2026-09-23T10:00:00.000Z"),
|
|
670
|
-
seconds: 7200, description: "feat: cartera de servicios", billable: true,
|
|
671
|
-
invoiceId: null, aiCost: { microUsd: 0 }, agentSeconds: 7200,
|
|
672
|
-
commitHashes: [], agents: [], source: "agent", kind: "development", branch: "main",
|
|
673
|
-
});
|
|
674
|
-
const r = db.prepare("SELECT seconds FROM time_entries WHERE id = 'te_fija'").get();
|
|
675
|
-
strict_1.default.equal(r.seconds, 7200, "corregir la medición es justo para lo que sirve reimportar");
|
|
676
|
-
}
|
|
677
|
-
finally {
|
|
678
|
-
db.close();
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
(0, node_test_1.test)("un ajuste exige un motivo", () => {
|
|
682
|
-
const path = tempDb();
|
|
683
|
-
entradaDeAgente(path, 3600);
|
|
684
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
685
|
-
try {
|
|
686
|
-
// Cambiar horas que alguien paga sin decir por qué es lo contrario de lo
|
|
687
|
-
// que promete el producto.
|
|
688
|
-
strict_1.default.throws(() => store.adjustEntrySeconds(db, "te_fija", 7200, ""));
|
|
689
|
-
strict_1.default.throws(() => store.adjustEntrySeconds(db, "te_fija", 7200, " "));
|
|
690
|
-
const r = db.prepare("SELECT seconds FROM time_entries WHERE id = 'te_fija'").get();
|
|
691
|
-
strict_1.default.equal(r.seconds, 3600, "y no se guarda nada a medias");
|
|
692
|
-
}
|
|
693
|
-
finally {
|
|
694
|
-
db.close();
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
(0, node_test_1.test)("reimportar no toca una imputación ya facturada", () => {
|
|
698
|
-
// La fila detrás de una factura emitida es historia. Pisarla deja el panel
|
|
699
|
-
// diciendo una cosa y el PDF entregado otra.
|
|
700
|
-
const path = tempDb();
|
|
701
|
-
entradaDeAgente(path, 9504);
|
|
702
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
703
|
-
try {
|
|
704
|
-
db.prepare("INSERT INTO invoices (id, number, client_id, project_id, issued_at, cutoff_at, period_start, currency, subtotal_minor, total_minor, total_seconds, ai_micro_usd, lines_json) VALUES ('inv_1','INF-1','acme','web','2026-09-24T04:01:39.000Z','2026-09-23T23:59:59.000Z','2026-07-01T00:00:00.000Z','EUR',0,0,9504,0,'[]')").run();
|
|
705
|
-
db.prepare("UPDATE time_entries SET invoice_id = 'inv_1' WHERE id = 'te_fija'").run();
|
|
706
|
-
store.saveTimeEntry(db, {
|
|
707
|
-
id: "te_fija", projectId: "web",
|
|
708
|
-
startedAt: new Date("2026-09-23T09:00:00.000Z"),
|
|
709
|
-
endedAt: new Date("2026-09-23T10:00:00.000Z"),
|
|
710
|
-
seconds: 123, description: "otra cosa", billable: true,
|
|
711
|
-
invoiceId: null, aiCost: { microUsd: 0 }, agentSeconds: 123,
|
|
712
|
-
commitHashes: [], agents: [], source: "agent", kind: "development", branch: "main",
|
|
713
|
-
});
|
|
714
|
-
const r = db.prepare("SELECT seconds, description FROM time_entries WHERE id = 'te_fija'")
|
|
715
|
-
.get();
|
|
716
|
-
strict_1.default.equal(r.seconds, 9504, "lo facturado no se recalcula");
|
|
717
|
-
strict_1.default.equal(r.description, "feat: cartera de servicios");
|
|
718
|
-
}
|
|
719
|
-
finally {
|
|
720
|
-
db.close();
|
|
721
|
-
}
|
|
722
|
-
});
|
|
723
|
-
// ── Tickets ───────────────────────────────────────────────────────────
|
|
724
|
-
function reimportarCon(db, workItems) {
|
|
725
|
-
store.saveTimeEntry(db, {
|
|
726
|
-
id: "te_fija", projectId: "web",
|
|
727
|
-
startedAt: new Date("2026-09-23T09:00:00.000Z"),
|
|
728
|
-
endedAt: new Date("2026-09-23T10:00:00.000Z"),
|
|
729
|
-
seconds: 3600, description: "feat: cartera de servicios", billable: true,
|
|
730
|
-
invoiceId: null, aiCost: { microUsd: 0 }, agentSeconds: 3600,
|
|
731
|
-
commitHashes: [], agents: [], source: "agent", kind: "development", branch: "main",
|
|
732
|
-
workItems,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
(0, node_test_1.test)("los tickets se rehacen al reimportar, salvo los puestos a mano", () => {
|
|
736
|
-
const path = tempDb();
|
|
737
|
-
entradaDeAgente(path, 3600);
|
|
738
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
739
|
-
try {
|
|
740
|
-
const tickets = () => store.getTimeEntries(db, "web")[0].workItems;
|
|
741
|
-
strict_1.default.deepEqual(tickets(), [], "una entrada de antes de los tickets lee una lista vacía");
|
|
742
|
-
reimportarCon(db, ["azure:1234"]);
|
|
743
|
-
strict_1.default.deepEqual(tickets(), ["azure:1234"]);
|
|
744
|
-
// Corregido a mano: el reimport, que sigue detectando el 1234, no lo pisa.
|
|
745
|
-
store.setEntryWorkItems(db, "te_fija", ["azure:5678"]);
|
|
746
|
-
reimportarCon(db, ["azure:1234"]);
|
|
747
|
-
strict_1.default.deepEqual(tickets(), ["azure:5678"]);
|
|
748
|
-
// Quitar la corrección devuelve el bloque a lo detectado.
|
|
749
|
-
store.setEntryWorkItems(db, "te_fija", null);
|
|
750
|
-
reimportarCon(db, ["azure:1234"]);
|
|
751
|
-
strict_1.default.deepEqual(tickets(), ["azure:1234"]);
|
|
752
|
-
strict_1.default.throws(() => store.setEntryWorkItems(db, "no-existe", ["azure:1"]));
|
|
753
|
-
}
|
|
754
|
-
finally {
|
|
755
|
-
db.close();
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
(0, node_test_1.test)("el gestor de tareas de un proyecto se guarda y se quita sin tocar el resto", () => {
|
|
759
|
-
const path = tempDb();
|
|
760
|
-
entradaDeAgente(path, 3600);
|
|
761
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
762
|
-
try {
|
|
763
|
-
strict_1.default.equal(store.getProject(db, "web").tracker, null);
|
|
764
|
-
store.setProjectTracker(db, "web", { system: "jira", prefixes: ["PROJ"] });
|
|
765
|
-
const p = store.getProject(db, "web");
|
|
766
|
-
strict_1.default.deepEqual(p.tracker, { system: "jira", prefixes: ["PROJ"] });
|
|
767
|
-
strict_1.default.equal(p.name, "Web", "el resto del proyecto sigue igual");
|
|
768
|
-
store.setProjectTracker(db, "web", null);
|
|
769
|
-
strict_1.default.equal(store.getProject(db, "web").tracker, null);
|
|
770
|
-
strict_1.default.throws(() => store.setProjectTracker(db, "no-existe", null));
|
|
771
|
-
}
|
|
772
|
-
finally {
|
|
773
|
-
db.close();
|
|
774
|
-
}
|
|
775
|
-
});
|
|
776
|
-
// ── Windows ───────────────────────────────────────────────────────────
|
|
777
|
-
(0, node_test_1.test)("en Windows, una sesión del agente cae en el proyecto que dio de alta git", () => {
|
|
778
|
-
const db = (0, schema_js_1.openDatabase)(tempDb());
|
|
779
|
-
try {
|
|
780
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
781
|
-
// setup guarda la ruta de git (C:/...) y el agente trae la de Windows (C:\...).
|
|
782
|
-
store.upsertProject(db, {
|
|
783
|
-
id: "web", clientId: "acme", name: "Web", billable: true, repoPaths: ["C:/Users/ana/web"],
|
|
784
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
785
|
-
});
|
|
786
|
-
strict_1.default.equal(store.projectForRepo(db, "C:\\Users\\ana\\web"), "web");
|
|
787
|
-
strict_1.default.equal(store.projectForRepo(db, "c:\\users\\ana\\web\\src\\UI"), "web");
|
|
788
|
-
strict_1.default.equal(store.projectForRepo(db, "C:\\Users\\ana\\web2"), null);
|
|
789
|
-
}
|
|
790
|
-
finally {
|
|
791
|
-
db.close();
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
(0, node_test_1.test)("la migración 16 deja iguales las rutas de Windows ya guardadas, sin duplicar", () => {
|
|
795
|
-
const path = tempDb();
|
|
796
|
-
const db = (0, schema_js_1.openDatabase)(path);
|
|
797
|
-
store.upsertClient(db, { id: "acme", name: "ACME", currency: "EUR" });
|
|
798
|
-
store.upsertProject(db, {
|
|
799
|
-
id: "web", clientId: "acme", name: "Web", billable: true, repoPaths: [],
|
|
800
|
-
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client",
|
|
801
|
-
});
|
|
802
|
-
// Como las dejaba una versión anterior: mezcladas, y un commit guardado dos veces.
|
|
803
|
-
db.exec(`
|
|
804
|
-
INSERT INTO project_repos (project_id, repo_path) VALUES ('web', 'C:\\Users\\ana\\web');
|
|
805
|
-
INSERT INTO commits (repo_path, hash, at, author_email, subject) VALUES
|
|
806
|
-
('C:/Users/ana/web', 'abc', '2026-09-01T10:00:00Z', 'a@b.com', 'uno'),
|
|
807
|
-
('c:\\Users\\ana\\web', 'abc', '2026-09-01T10:00:00Z', 'a@b.com', 'uno'),
|
|
808
|
-
('c:\\Users\\ana\\web', 'def', '2026-09-01T11:00:00Z', 'a@b.com', 'dos');
|
|
809
|
-
INSERT INTO agent_turns (turn_id, agent, session_id, at, model, repo_path)
|
|
810
|
-
VALUES ('t1', 'claude-code', 's', '2026-09-01T10:00:00Z', 'm', 'C:\\Users\\ana\\web\\src');
|
|
811
|
-
UPDATE schema_version SET version = 15;
|
|
812
|
-
`);
|
|
813
|
-
db.close();
|
|
814
|
-
const migrada = (0, schema_js_1.openDatabase)(path);
|
|
815
|
-
try {
|
|
816
|
-
const col = (sql) => migrada.prepare(sql).all().map((r) => r.p);
|
|
817
|
-
strict_1.default.deepEqual(col("SELECT repo_path AS p FROM project_repos"), ["C:/Users/ana/web"]);
|
|
818
|
-
strict_1.default.deepEqual(col("SELECT repo_path || ' ' || hash AS p FROM commits ORDER BY hash"), ["C:/Users/ana/web abc", "C:/Users/ana/web def"]);
|
|
819
|
-
strict_1.default.deepEqual(col("SELECT repo_path AS p FROM agent_turns"), ["C:/Users/ana/web/src"]);
|
|
820
|
-
}
|
|
821
|
-
finally {
|
|
822
|
-
migrada.close();
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
//# sourceMappingURL=schema.test.js.map
|