estela 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/billing/amortize.d.ts +44 -0
- package/dist/billing/amortize.d.ts.map +1 -0
- package/dist/billing/amortize.js +83 -0
- package/dist/billing/amortize.js.map +1 -0
- package/dist/billing/amortize.test.d.ts +2 -0
- package/dist/billing/amortize.test.d.ts.map +1 -0
- package/dist/billing/amortize.test.js +121 -0
- package/dist/billing/amortize.test.js.map +1 -0
- package/dist/billing/format.test.d.ts +2 -0
- package/dist/billing/format.test.d.ts.map +1 -0
- package/dist/billing/format.test.js +110 -0
- package/dist/billing/format.test.js.map +1 -0
- package/dist/billing/invoice.d.ts +52 -0
- package/dist/billing/invoice.d.ts.map +1 -0
- package/dist/billing/invoice.js +148 -0
- package/dist/billing/invoice.js.map +1 -0
- package/dist/billing/invoice.test.d.ts +2 -0
- package/dist/billing/invoice.test.d.ts.map +1 -0
- package/dist/billing/invoice.test.js +200 -0
- package/dist/billing/invoice.test.js.map +1 -0
- package/dist/billing/sessionize.d.ts +104 -0
- package/dist/billing/sessionize.d.ts.map +1 -0
- package/dist/billing/sessionize.js +0 -0
- package/dist/billing/sessionize.js.map +1 -0
- package/dist/billing/sessionize.test.d.ts +2 -0
- package/dist/billing/sessionize.test.d.ts.map +1 -0
- package/dist/billing/sessionize.test.js +67 -0
- package/dist/billing/sessionize.test.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1091 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud/auth.d.ts +4 -0
- package/dist/cloud/auth.d.ts.map +1 -0
- package/dist/cloud/auth.js +98 -0
- package/dist/cloud/auth.js.map +1 -0
- package/dist/cloud/client.d.ts +12 -0
- package/dist/cloud/client.d.ts.map +1 -0
- package/dist/cloud/client.js +34 -0
- package/dist/cloud/client.js.map +1 -0
- package/dist/db/schema.d.ts +11 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +424 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/schema.test.d.ts +2 -0
- package/dist/db/schema.test.d.ts.map +1 -0
- package/dist/db/schema.test.js +335 -0
- package/dist/db/schema.test.js.map +1 -0
- package/dist/db/store.d.ts +113 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +550 -0
- package/dist/db/store.js.map +1 -0
- package/dist/doctor.d.ts +24 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +229 -0
- package/dist/doctor.js.map +1 -0
- package/dist/export/csv.d.ts +9 -0
- package/dist/export/csv.d.ts.map +1 -0
- package/dist/export/csv.js +81 -0
- package/dist/export/csv.js.map +1 -0
- package/dist/export/invoice-pdf.d.ts +21 -0
- package/dist/export/invoice-pdf.d.ts.map +1 -0
- package/dist/export/invoice-pdf.js +135 -0
- package/dist/export/invoice-pdf.js.map +1 -0
- package/dist/export/panel.d.ts +78 -0
- package/dist/export/panel.d.ts.map +1 -0
- package/dist/export/panel.js +693 -0
- package/dist/export/panel.js.map +1 -0
- package/dist/export/panel.test.d.ts +2 -0
- package/dist/export/panel.test.d.ts.map +1 -0
- package/dist/export/panel.test.js +273 -0
- package/dist/export/panel.test.js.map +1 -0
- package/dist/export/pdf.d.ts +32 -0
- package/dist/export/pdf.d.ts.map +1 -0
- package/dist/export/pdf.js +229 -0
- package/dist/export/pdf.js.map +1 -0
- package/dist/export/pdf.test.d.ts +2 -0
- package/dist/export/pdf.test.d.ts.map +1 -0
- package/dist/export/pdf.test.js +151 -0
- package/dist/export/pdf.test.js.map +1 -0
- package/dist/export/share.d.ts +42 -0
- package/dist/export/share.d.ts.map +1 -0
- package/dist/export/share.js +181 -0
- package/dist/export/share.js.map +1 -0
- package/dist/export/share.test.d.ts +2 -0
- package/dist/export/share.test.d.ts.map +1 -0
- package/dist/export/share.test.js +116 -0
- package/dist/export/share.test.js.map +1 -0
- package/dist/metrics/index.d.ts +101 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +221 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/metrics.test.d.ts +2 -0
- package/dist/metrics/metrics.test.d.ts.map +1 -0
- package/dist/metrics/metrics.test.js +162 -0
- package/dist/metrics/metrics.test.js.map +1 -0
- package/dist/metrics/people.d.ts +46 -0
- package/dist/metrics/people.d.ts.map +1 -0
- package/dist/metrics/people.js +126 -0
- package/dist/metrics/people.js.map +1 -0
- package/dist/metrics/people.test.d.ts +2 -0
- package/dist/metrics/people.test.d.ts.map +1 -0
- package/dist/metrics/people.test.js +84 -0
- package/dist/metrics/people.test.js.map +1 -0
- package/dist/metrics/team.d.ts +41 -0
- package/dist/metrics/team.d.ts.map +1 -0
- package/dist/metrics/team.js +0 -0
- package/dist/metrics/team.js.map +1 -0
- package/dist/metrics/team.test.d.ts +2 -0
- package/dist/metrics/team.test.d.ts.map +1 -0
- package/dist/metrics/team.test.js +90 -0
- package/dist/metrics/team.test.js.map +1 -0
- package/dist/pricing/catalog.d.ts +39 -0
- package/dist/pricing/catalog.d.ts.map +1 -0
- package/dist/pricing/catalog.js +59 -0
- package/dist/pricing/catalog.js.map +1 -0
- package/dist/pricing/cost.d.ts +25 -0
- package/dist/pricing/cost.d.ts.map +1 -0
- package/dist/pricing/cost.js +50 -0
- package/dist/pricing/cost.js.map +1 -0
- package/dist/pricing/cost.test.d.ts +2 -0
- package/dist/pricing/cost.test.d.ts.map +1 -0
- package/dist/pricing/cost.test.js +74 -0
- package/dist/pricing/cost.test.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1141 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +70 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +223 -0
- package/dist/setup.js.map +1 -0
- package/dist/setup.test.d.ts +2 -0
- package/dist/setup.test.d.ts.map +1 -0
- package/dist/setup.test.js +131 -0
- package/dist/setup.test.js.map +1 -0
- package/dist/watchers/claude.d.ts +34 -0
- package/dist/watchers/claude.d.ts.map +1 -0
- package/dist/watchers/claude.js +235 -0
- package/dist/watchers/claude.js.map +1 -0
- package/dist/watchers/claude.test.d.ts +2 -0
- package/dist/watchers/claude.test.d.ts.map +1 -0
- package/dist/watchers/claude.test.js +180 -0
- package/dist/watchers/claude.test.js.map +1 -0
- package/dist/watchers/git.d.ts +42 -0
- package/dist/watchers/git.d.ts.map +1 -0
- package/dist/watchers/git.js +218 -0
- package/dist/watchers/git.js.map +1 -0
- package/dist/watchers/git.test.d.ts +2 -0
- package/dist/watchers/git.test.d.ts.map +1 -0
- package/dist/watchers/git.test.js +73 -0
- package/dist/watchers/git.test.js.map +1 -0
- package/dist/watchers/identity.d.ts +34 -0
- package/dist/watchers/identity.d.ts.map +1 -0
- package/dist/watchers/identity.js +85 -0
- package/dist/watchers/identity.js.map +1 -0
- package/dist/watchers/identity.test.d.ts +2 -0
- package/dist/watchers/identity.test.d.ts.map +1 -0
- package/dist/watchers/identity.test.js +48 -0
- package/dist/watchers/identity.test.js.map +1 -0
- package/dist/web-ui.test.d.ts +2 -0
- package/dist/web-ui.test.d.ts.map +1 -0
- package/dist/web-ui.test.js +312 -0
- package/dist/web-ui.test.js.map +1 -0
- package/node_modules/@estela/shared/dist/index.d.ts +3 -0
- package/node_modules/@estela/shared/dist/index.d.ts.map +1 -0
- package/node_modules/@estela/shared/dist/index.js +19 -0
- package/node_modules/@estela/shared/dist/index.js.map +1 -0
- package/node_modules/@estela/shared/dist/money.d.ts +71 -0
- package/node_modules/@estela/shared/dist/money.d.ts.map +1 -0
- package/node_modules/@estela/shared/dist/money.js +171 -0
- package/node_modules/@estela/shared/dist/money.js.map +1 -0
- package/node_modules/@estela/shared/dist/types.d.ts +365 -0
- package/node_modules/@estela/shared/dist/types.d.ts.map +1 -0
- package/node_modules/@estela/shared/dist/types.js +24 -0
- package/node_modules/@estela/shared/dist/types.js.map +1 -0
- package/node_modules/@estela/shared/package.json +5 -0
- package/package.json +41 -0
- package/web/app.css +749 -0
- package/web/app.js +1257 -0
- package/web/apple-touch-icon.png +0 -0
- package/web/favicon.png +0 -0
- package/web/index.html +132 -0
- package/web/lib.js +94 -0
- package/web/logo-mark.png +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_DB_PATH = void 0;
|
|
4
|
+
exports.openDatabase = openDatabase;
|
|
5
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
/**
|
|
10
|
+
* Esquema local. Todo vive en `~/.estela/estela.db`, en el disco del usuario.
|
|
11
|
+
*
|
|
12
|
+
* Del agente se guardan solo metadatos: duración, modelo y tokens. Nunca el
|
|
13
|
+
* contenido de los prompts ni del código. No hay ninguna columna donde quepan,
|
|
14
|
+
* y eso es deliberado: la garantía tiene que ser estructural, no una promesa.
|
|
15
|
+
*/
|
|
16
|
+
exports.DEFAULT_DB_PATH = (0, node_path_1.join)((0, node_os_1.homedir)(), ".estela", "estela.db");
|
|
17
|
+
const SCHEMA = `
|
|
18
|
+
PRAGMA journal_mode = WAL;
|
|
19
|
+
PRAGMA foreign_keys = ON;
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS clients (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
name TEXT NOT NULL,
|
|
26
|
+
currency TEXT NOT NULL,
|
|
27
|
+
tax_id TEXT,
|
|
28
|
+
email TEXT,
|
|
29
|
+
address TEXT
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
33
|
+
id TEXT PRIMARY KEY,
|
|
34
|
+
client_id TEXT NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
35
|
+
name TEXT NOT NULL,
|
|
36
|
+
billable INTEGER NOT NULL DEFAULT 1,
|
|
37
|
+
rounding_minutes INTEGER NOT NULL DEFAULT 0,
|
|
38
|
+
ai_cost_policy TEXT NOT NULL DEFAULT 'absorbed',
|
|
39
|
+
-- client | employment | internal. Un empleo no es un cliente sin tarifa:
|
|
40
|
+
-- ahí el importe no falta, es que no existe.
|
|
41
|
+
kind TEXT NOT NULL DEFAULT 'client',
|
|
42
|
+
-- Presupuesto mensual de IA en micro-USD. NULL es "no vigilar", que no es lo
|
|
43
|
+
-- mismo que un presupuesto de cero.
|
|
44
|
+
ai_budget_micro_usd INTEGER
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Con qué identidad commiteas en cada proyecto.
|
|
48
|
+
--
|
|
49
|
+
-- No se puede deducir de la configuración de git: en el repositorio de un
|
|
50
|
+
-- cliente sueles commitear con el correo que te dio el cliente, mientras que la
|
|
51
|
+
-- configuración global tiene el tuyo. Adivinarlo hace que se capturen 3 commits
|
|
52
|
+
-- de 1697, o peor, que se te imputen los de tus compañeros.
|
|
53
|
+
CREATE TABLE IF NOT EXISTS project_authors (
|
|
54
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
55
|
+
author_email TEXT NOT NULL,
|
|
56
|
+
PRIMARY KEY (project_id, author_email)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
-- Un proyecto puede abarcar varios repositorios (monorepo, front + back).
|
|
60
|
+
CREATE TABLE IF NOT EXISTS project_repos (
|
|
61
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
62
|
+
repo_path TEXT NOT NULL,
|
|
63
|
+
PRIMARY KEY (project_id, repo_path)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
-- Las tarifas se apilan por fecha en vez de sobrescribirse: subir el precio no
|
|
67
|
+
-- puede reescribir lo ya trabajado.
|
|
68
|
+
CREATE TABLE IF NOT EXISTS rate_periods (
|
|
69
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
71
|
+
hourly_minor INTEGER NOT NULL,
|
|
72
|
+
currency TEXT NOT NULL,
|
|
73
|
+
effective_from TEXT NOT NULL,
|
|
74
|
+
effective_to TEXT
|
|
75
|
+
);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_rates_project ON rate_periods(project_id, effective_from);
|
|
77
|
+
|
|
78
|
+
-- Turnos de agente ya deduplicados. turn_id es PRIMARY KEY: la deduplicación
|
|
79
|
+
-- que hace el parser queda además garantizada por la base de datos.
|
|
80
|
+
CREATE TABLE IF NOT EXISTS agent_turns (
|
|
81
|
+
turn_id TEXT PRIMARY KEY,
|
|
82
|
+
agent TEXT NOT NULL,
|
|
83
|
+
session_id TEXT NOT NULL,
|
|
84
|
+
at TEXT NOT NULL,
|
|
85
|
+
model TEXT NOT NULL,
|
|
86
|
+
repo_path TEXT,
|
|
87
|
+
branch TEXT,
|
|
88
|
+
tok_input INTEGER NOT NULL DEFAULT 0,
|
|
89
|
+
tok_output INTEGER NOT NULL DEFAULT 0,
|
|
90
|
+
tok_cache_read INTEGER NOT NULL DEFAULT 0,
|
|
91
|
+
tok_cache_w5m INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
tok_cache_w1h INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
cost_micro_usd INTEGER,
|
|
94
|
+
producer_version TEXT
|
|
95
|
+
);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_turns_at ON agent_turns(at);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_turns_repo ON agent_turns(repo_path, at);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS commits (
|
|
100
|
+
repo_path TEXT NOT NULL,
|
|
101
|
+
hash TEXT NOT NULL,
|
|
102
|
+
at TEXT NOT NULL,
|
|
103
|
+
author_email TEXT NOT NULL,
|
|
104
|
+
author_name TEXT NOT NULL DEFAULT '',
|
|
105
|
+
branch TEXT,
|
|
106
|
+
subject TEXT NOT NULL,
|
|
107
|
+
lines_added INTEGER NOT NULL DEFAULT 0,
|
|
108
|
+
lines_deleted INTEGER NOT NULL DEFAULT 0,
|
|
109
|
+
-- Ficheros tocados, separados por saltos de línea. Sin esto no se puede
|
|
110
|
+
-- medir reescritura, que es la métrica que más respeto gana ante un líder
|
|
111
|
+
-- técnico porque es diagnóstica y no autocomplaciente.
|
|
112
|
+
files TEXT NOT NULL DEFAULT '',
|
|
113
|
+
PRIMARY KEY (repo_path, hash)
|
|
114
|
+
);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_commits_at ON commits(at);
|
|
116
|
+
|
|
117
|
+
CREATE TABLE IF NOT EXISTS time_entries (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
120
|
+
started_at TEXT NOT NULL,
|
|
121
|
+
-- Fecha local de quien trabaja, no la UTC. Sin esta columna, en UTC-5 todo
|
|
122
|
+
-- lo trabajado después de las 19:00 caería en el día siguiente y partiría
|
|
123
|
+
-- la jornada en dos.
|
|
124
|
+
local_date TEXT NOT NULL DEFAULT '',
|
|
125
|
+
ended_at TEXT NOT NULL,
|
|
126
|
+
seconds INTEGER NOT NULL,
|
|
127
|
+
description TEXT NOT NULL,
|
|
128
|
+
billable INTEGER NOT NULL DEFAULT 1,
|
|
129
|
+
approved INTEGER NOT NULL DEFAULT 0,
|
|
130
|
+
invoice_id TEXT REFERENCES invoices(id),
|
|
131
|
+
ai_micro_usd INTEGER NOT NULL DEFAULT 0,
|
|
132
|
+
agent_seconds INTEGER NOT NULL DEFAULT 0,
|
|
133
|
+
commit_hashes TEXT NOT NULL DEFAULT '',
|
|
134
|
+
agents TEXT NOT NULL DEFAULT '',
|
|
135
|
+
-- 'agent' se rehace al reimportar; 'manual' nunca se toca.
|
|
136
|
+
source TEXT NOT NULL DEFAULT 'agent',
|
|
137
|
+
kind TEXT NOT NULL DEFAULT 'development',
|
|
138
|
+
-- Rama en la que se hizo el trabajo. Un gestor piensa en funcionalidades,
|
|
139
|
+
-- no en días: sin esta columna no se puede decir "18h en el informe de
|
|
140
|
+
-- servicios", que es la frase que él entiende.
|
|
141
|
+
branch TEXT,
|
|
142
|
+
-- Cuándo se escribió esta fila, no cuándo ocurrió el trabajo. Es lo que
|
|
143
|
+
-- decide si un panel publicado se ha quedado atrás: lo que envejece es que
|
|
144
|
+
-- los datos cambien, y una reunión anotada hoy lleva fecha de esta mañana.
|
|
145
|
+
updated_at TEXT NOT NULL DEFAULT ''
|
|
146
|
+
);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_entries_project ON time_entries(project_id, started_at);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_entries_day ON time_entries(local_date);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_entries_invoice ON time_entries(invoice_id);
|
|
150
|
+
|
|
151
|
+
-- Documento inmutable: guarda su propio tipo de cambio y sus propios importes.
|
|
152
|
+
CREATE TABLE IF NOT EXISTS invoices (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
number TEXT NOT NULL UNIQUE,
|
|
155
|
+
client_id TEXT NOT NULL REFERENCES clients(id),
|
|
156
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
157
|
+
issued_at TEXT NOT NULL,
|
|
158
|
+
cutoff_at TEXT NOT NULL,
|
|
159
|
+
period_start TEXT NOT NULL,
|
|
160
|
+
currency TEXT NOT NULL,
|
|
161
|
+
subtotal_minor INTEGER NOT NULL,
|
|
162
|
+
total_minor INTEGER NOT NULL,
|
|
163
|
+
total_seconds INTEGER NOT NULL,
|
|
164
|
+
ai_micro_usd INTEGER NOT NULL DEFAULT 0,
|
|
165
|
+
usd_fx_rate REAL,
|
|
166
|
+
ai_billed_minor INTEGER,
|
|
167
|
+
-- Cuota de IA imputada al periodo, congelada al emitir. Ver Invoice.aiAmortized.
|
|
168
|
+
ai_amort_minor INTEGER,
|
|
169
|
+
ai_amort_cur TEXT,
|
|
170
|
+
notes TEXT,
|
|
171
|
+
lines_json TEXT NOT NULL
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
-- Suscripciones de precio fijo. Con cuota fija, el gasto real no es la suma por
|
|
175
|
+
-- token: es esta cuota, repartida entre proyectos según lo que consumió cada uno.
|
|
176
|
+
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
name TEXT NOT NULL,
|
|
179
|
+
fee_minor INTEGER NOT NULL,
|
|
180
|
+
currency TEXT NOT NULL,
|
|
181
|
+
effective_from TEXT NOT NULL,
|
|
182
|
+
effective_to TEXT
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
-- Cuándo se publicó por última vez el panel de cada proyecto.
|
|
186
|
+
--
|
|
187
|
+
-- Sirve para saber si lo que ve el cliente sigue siendo cierto. Lo que importa
|
|
188
|
+
-- no son los días transcurridos: es que haya trabajo nuevo desde entonces. Un
|
|
189
|
+
-- panel de hace una semana está al día si esa semana no tocaste el proyecto.
|
|
190
|
+
CREATE TABLE IF NOT EXISTS publications (
|
|
191
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
192
|
+
token TEXT NOT NULL,
|
|
193
|
+
published_at TEXT NOT NULL,
|
|
194
|
+
base_url TEXT
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
-- Trazabilidad del parser: qué se leyó, cuántos duplicados cayeron y si el
|
|
198
|
+
-- canario de deriva de formato saltó.
|
|
199
|
+
CREATE TABLE IF NOT EXISTS scan_log (
|
|
200
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
201
|
+
ran_at TEXT NOT NULL,
|
|
202
|
+
source TEXT NOT NULL,
|
|
203
|
+
files_read INTEGER NOT NULL,
|
|
204
|
+
records_seen INTEGER NOT NULL,
|
|
205
|
+
turns_accepted INTEGER NOT NULL,
|
|
206
|
+
duplicates_dropped INTEGER NOT NULL,
|
|
207
|
+
unknown_records INTEGER NOT NULL,
|
|
208
|
+
malformed_records INTEGER NOT NULL,
|
|
209
|
+
producer_versions TEXT NOT NULL,
|
|
210
|
+
warnings TEXT NOT NULL
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
-- Si esta fila no existe, esta máquina es 100% local. Punto.
|
|
214
|
+
CREATE TABLE IF NOT EXISTS cloud_account (
|
|
215
|
+
id TEXT PRIMARY KEY,
|
|
216
|
+
account_id TEXT NOT NULL,
|
|
217
|
+
email TEXT NOT NULL,
|
|
218
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
219
|
+
device_token TEXT NOT NULL,
|
|
220
|
+
api_base_url TEXT NOT NULL DEFAULT 'https://api.getestela.dev',
|
|
221
|
+
linked_at TEXT NOT NULL
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
-- El único sitio que decide qué proyecto sale de esta máquina y con qué
|
|
225
|
+
-- alcance. Sin fila aquí, ese project_id nunca sincroniza, sea cual sea el
|
|
226
|
+
-- plan de la cuenta.
|
|
227
|
+
CREATE TABLE IF NOT EXISTS project_sync (
|
|
228
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
229
|
+
scope TEXT NOT NULL,
|
|
230
|
+
remote_project_id TEXT NOT NULL,
|
|
231
|
+
remote_org_id TEXT,
|
|
232
|
+
invite_token TEXT,
|
|
233
|
+
synced_up_to TEXT NOT NULL DEFAULT '',
|
|
234
|
+
last_synced_at TEXT
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
-- Tu consentimiento de compartir IA en un proyecto de equipo. Vive local
|
|
238
|
+
-- porque el demonio decide sin preguntarle al servidor si mete ai_micro_usd
|
|
239
|
+
-- al subir una fila.
|
|
240
|
+
CREATE TABLE IF NOT EXISTS ai_consent (
|
|
241
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
242
|
+
requested INTEGER NOT NULL DEFAULT 0,
|
|
243
|
+
decision TEXT NOT NULL DEFAULT 'pending',
|
|
244
|
+
decided_at TEXT
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
CREATE TABLE IF NOT EXISTS personal_sync_state (
|
|
248
|
+
entity_type TEXT PRIMARY KEY,
|
|
249
|
+
synced_up_to TEXT NOT NULL DEFAULT ''
|
|
250
|
+
);
|
|
251
|
+
`;
|
|
252
|
+
/**
|
|
253
|
+
* Migraciones.
|
|
254
|
+
*
|
|
255
|
+
* `CREATE TABLE IF NOT EXISTS` crea tablas nuevas pero **no añade columnas a una
|
|
256
|
+
* tabla que ya existe**: la sentencia no hace nada y la aplicación revienta al
|
|
257
|
+
* primer INSERT. Por eso todo cambio de forma sobre una tabla viva pasa por
|
|
258
|
+
* aquí, en orden y una sola vez.
|
|
259
|
+
*
|
|
260
|
+
* La base de datos guarda el trabajo de meses de alguien. Borrarla y volver a
|
|
261
|
+
* empezar no es una opción de mantenimiento.
|
|
262
|
+
*/
|
|
263
|
+
const MIGRATIONS = [
|
|
264
|
+
{
|
|
265
|
+
version: 11,
|
|
266
|
+
describe: "cuenta cloud, alcance de sincronización y consentimiento de IA",
|
|
267
|
+
run: (db) => {
|
|
268
|
+
// Si esta fila no existe, esta máquina es 100% local. Punto. El id fijo
|
|
269
|
+
// 'local' fuerza que solo pueda haber una.
|
|
270
|
+
db.exec(`CREATE TABLE IF NOT EXISTS cloud_account (
|
|
271
|
+
id TEXT PRIMARY KEY,
|
|
272
|
+
account_id TEXT NOT NULL,
|
|
273
|
+
email TEXT NOT NULL,
|
|
274
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
275
|
+
device_token TEXT NOT NULL,
|
|
276
|
+
api_base_url TEXT NOT NULL DEFAULT 'https://api.getestela.dev',
|
|
277
|
+
linked_at TEXT NOT NULL
|
|
278
|
+
)`);
|
|
279
|
+
// El único sitio que decide qué proyecto sale de esta máquina y con qué
|
|
280
|
+
// alcance. Sin fila aquí, ese project_id nunca sincroniza, sea cual sea
|
|
281
|
+
// el plan de la cuenta.
|
|
282
|
+
db.exec(`CREATE TABLE IF NOT EXISTS project_sync (
|
|
283
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
284
|
+
scope TEXT NOT NULL,
|
|
285
|
+
remote_project_id TEXT NOT NULL,
|
|
286
|
+
remote_org_id TEXT,
|
|
287
|
+
invite_token TEXT,
|
|
288
|
+
synced_up_to TEXT NOT NULL DEFAULT '',
|
|
289
|
+
last_synced_at TEXT
|
|
290
|
+
)`);
|
|
291
|
+
// Tu consentimiento de compartir IA en un proyecto de equipo. Vive local
|
|
292
|
+
// porque el demonio decide sin preguntarle al servidor si mete
|
|
293
|
+
// ai_micro_usd al subir una fila.
|
|
294
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ai_consent (
|
|
295
|
+
project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
|
|
296
|
+
requested INTEGER NOT NULL DEFAULT 0,
|
|
297
|
+
decision TEXT NOT NULL DEFAULT 'pending',
|
|
298
|
+
decided_at TEXT
|
|
299
|
+
)`);
|
|
300
|
+
db.exec(`CREATE TABLE IF NOT EXISTS personal_sync_state (
|
|
301
|
+
entity_type TEXT PRIMARY KEY,
|
|
302
|
+
synced_up_to TEXT NOT NULL DEFAULT ''
|
|
303
|
+
)`);
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
version: 10,
|
|
308
|
+
describe: "nombre del autor y presupuesto de IA",
|
|
309
|
+
run: (db) => {
|
|
310
|
+
// El nombre permite juntar los varios correos de una misma persona.
|
|
311
|
+
addColumn(db, "commits", "author_name", "TEXT NOT NULL DEFAULT ''");
|
|
312
|
+
// Sin presupuesto declarado no hay aviso: null significa "no vigilar",
|
|
313
|
+
// que no es lo mismo que un presupuesto de cero.
|
|
314
|
+
addColumn(db, "projects", "ai_budget_micro_usd", "INTEGER");
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
version: 9,
|
|
319
|
+
describe: "marca de escritura en las imputaciones",
|
|
320
|
+
run: (db) => {
|
|
321
|
+
addColumn(db, "time_entries", "updated_at", "TEXT NOT NULL DEFAULT ''");
|
|
322
|
+
db.exec("UPDATE time_entries SET updated_at = ended_at WHERE updated_at = ''");
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
version: 8,
|
|
327
|
+
describe: "registro de publicaciones del panel",
|
|
328
|
+
run: (db) => {
|
|
329
|
+
db.exec(`CREATE TABLE IF NOT EXISTS publications (
|
|
330
|
+
project_id TEXT PRIMARY KEY,
|
|
331
|
+
token TEXT NOT NULL,
|
|
332
|
+
published_at TEXT NOT NULL,
|
|
333
|
+
base_url TEXT
|
|
334
|
+
)`);
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
version: 7,
|
|
339
|
+
describe: "cuota de IA congelada en el informe",
|
|
340
|
+
run: (db) => {
|
|
341
|
+
addColumn(db, "invoices", "ai_amort_minor", "INTEGER");
|
|
342
|
+
addColumn(db, "invoices", "ai_amort_cur", "TEXT");
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
version: 6,
|
|
347
|
+
describe: "tipo de proyecto: cliente, nómina o interno",
|
|
348
|
+
run: (db) => addColumn(db, "projects", "kind", "TEXT NOT NULL DEFAULT 'client'"),
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
version: 5,
|
|
352
|
+
describe: "rama de trabajo en las imputaciones",
|
|
353
|
+
run: (db) => addColumn(db, "time_entries", "branch", "TEXT"),
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
version: 4,
|
|
357
|
+
describe: "ficheros tocados por commit",
|
|
358
|
+
run: (db) => addColumn(db, "commits", "files", "TEXT NOT NULL DEFAULT ''"),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
version: 3,
|
|
362
|
+
describe: "identidad de commit por proyecto",
|
|
363
|
+
run: (db) => {
|
|
364
|
+
db.exec(`CREATE TABLE IF NOT EXISTS project_authors (
|
|
365
|
+
project_id TEXT NOT NULL,
|
|
366
|
+
author_email TEXT NOT NULL,
|
|
367
|
+
PRIMARY KEY (project_id, author_email)
|
|
368
|
+
)`);
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
version: 2,
|
|
373
|
+
describe: "origen y tipo de trabajo en las imputaciones",
|
|
374
|
+
run: (db) => {
|
|
375
|
+
addColumn(db, "time_entries", "source", "TEXT NOT NULL DEFAULT 'agent'");
|
|
376
|
+
addColumn(db, "time_entries", "kind", "TEXT NOT NULL DEFAULT 'development'");
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
const CURRENT_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 1);
|
|
381
|
+
/** Añade una columna solo si falta. Repetirlo no debe romper nada. */
|
|
382
|
+
function addColumn(db, table, column, definition) {
|
|
383
|
+
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
384
|
+
if (columns.some((c) => c.name === column))
|
|
385
|
+
return;
|
|
386
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
387
|
+
}
|
|
388
|
+
function openDatabase(path = exports.DEFAULT_DB_PATH) {
|
|
389
|
+
if (path !== ":memory:")
|
|
390
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
391
|
+
const db = new node_sqlite_1.DatabaseSync(path);
|
|
392
|
+
db.exec(SCHEMA);
|
|
393
|
+
const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
394
|
+
if (!row) {
|
|
395
|
+
// Base nueva: SCHEMA ya la dejó al día.
|
|
396
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_VERSION);
|
|
397
|
+
return db;
|
|
398
|
+
}
|
|
399
|
+
if (row.version > CURRENT_VERSION) {
|
|
400
|
+
db.close();
|
|
401
|
+
throw new Error(`La base de datos usa el esquema v${row.version} y esta versión de Estela entiende hasta ` +
|
|
402
|
+
`v${CURRENT_VERSION}. Actualiza Estela antes de continuar.`);
|
|
403
|
+
}
|
|
404
|
+
// Cada migración va en su transacción: si una falla, la base queda en la
|
|
405
|
+
// última versión que sí se aplicó entera, nunca a medias.
|
|
406
|
+
for (const migration of MIGRATIONS) {
|
|
407
|
+
if (migration.version <= row.version)
|
|
408
|
+
continue;
|
|
409
|
+
db.exec("BEGIN");
|
|
410
|
+
try {
|
|
411
|
+
migration.run(db);
|
|
412
|
+
db.prepare("UPDATE schema_version SET version = ?").run(migration.version);
|
|
413
|
+
db.exec("COMMIT");
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
db.exec("ROLLBACK");
|
|
417
|
+
db.close();
|
|
418
|
+
throw new Error(`Falló la migración v${migration.version} (${migration.describe}): ` +
|
|
419
|
+
`${error instanceof Error ? error.message : String(error)}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return db;
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.test.d.ts","sourceRoot":"","sources":["../../src/db/schema.test.ts"],"names":[],"mappings":""}
|