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.
Files changed (186) hide show
  1. package/README.md +138 -0
  2. package/dist/billing/amortize.d.ts +44 -0
  3. package/dist/billing/amortize.d.ts.map +1 -0
  4. package/dist/billing/amortize.js +83 -0
  5. package/dist/billing/amortize.js.map +1 -0
  6. package/dist/billing/amortize.test.d.ts +2 -0
  7. package/dist/billing/amortize.test.d.ts.map +1 -0
  8. package/dist/billing/amortize.test.js +121 -0
  9. package/dist/billing/amortize.test.js.map +1 -0
  10. package/dist/billing/format.test.d.ts +2 -0
  11. package/dist/billing/format.test.d.ts.map +1 -0
  12. package/dist/billing/format.test.js +110 -0
  13. package/dist/billing/format.test.js.map +1 -0
  14. package/dist/billing/invoice.d.ts +52 -0
  15. package/dist/billing/invoice.d.ts.map +1 -0
  16. package/dist/billing/invoice.js +148 -0
  17. package/dist/billing/invoice.js.map +1 -0
  18. package/dist/billing/invoice.test.d.ts +2 -0
  19. package/dist/billing/invoice.test.d.ts.map +1 -0
  20. package/dist/billing/invoice.test.js +200 -0
  21. package/dist/billing/invoice.test.js.map +1 -0
  22. package/dist/billing/sessionize.d.ts +104 -0
  23. package/dist/billing/sessionize.d.ts.map +1 -0
  24. package/dist/billing/sessionize.js +0 -0
  25. package/dist/billing/sessionize.js.map +1 -0
  26. package/dist/billing/sessionize.test.d.ts +2 -0
  27. package/dist/billing/sessionize.test.d.ts.map +1 -0
  28. package/dist/billing/sessionize.test.js +67 -0
  29. package/dist/billing/sessionize.test.js.map +1 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +1091 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/cloud/auth.d.ts +4 -0
  35. package/dist/cloud/auth.d.ts.map +1 -0
  36. package/dist/cloud/auth.js +98 -0
  37. package/dist/cloud/auth.js.map +1 -0
  38. package/dist/cloud/client.d.ts +12 -0
  39. package/dist/cloud/client.d.ts.map +1 -0
  40. package/dist/cloud/client.js +34 -0
  41. package/dist/cloud/client.js.map +1 -0
  42. package/dist/db/schema.d.ts +11 -0
  43. package/dist/db/schema.d.ts.map +1 -0
  44. package/dist/db/schema.js +424 -0
  45. package/dist/db/schema.js.map +1 -0
  46. package/dist/db/schema.test.d.ts +2 -0
  47. package/dist/db/schema.test.d.ts.map +1 -0
  48. package/dist/db/schema.test.js +335 -0
  49. package/dist/db/schema.test.js.map +1 -0
  50. package/dist/db/store.d.ts +113 -0
  51. package/dist/db/store.d.ts.map +1 -0
  52. package/dist/db/store.js +550 -0
  53. package/dist/db/store.js.map +1 -0
  54. package/dist/doctor.d.ts +24 -0
  55. package/dist/doctor.d.ts.map +1 -0
  56. package/dist/doctor.js +229 -0
  57. package/dist/doctor.js.map +1 -0
  58. package/dist/export/csv.d.ts +9 -0
  59. package/dist/export/csv.d.ts.map +1 -0
  60. package/dist/export/csv.js +81 -0
  61. package/dist/export/csv.js.map +1 -0
  62. package/dist/export/invoice-pdf.d.ts +21 -0
  63. package/dist/export/invoice-pdf.d.ts.map +1 -0
  64. package/dist/export/invoice-pdf.js +135 -0
  65. package/dist/export/invoice-pdf.js.map +1 -0
  66. package/dist/export/panel.d.ts +78 -0
  67. package/dist/export/panel.d.ts.map +1 -0
  68. package/dist/export/panel.js +693 -0
  69. package/dist/export/panel.js.map +1 -0
  70. package/dist/export/panel.test.d.ts +2 -0
  71. package/dist/export/panel.test.d.ts.map +1 -0
  72. package/dist/export/panel.test.js +273 -0
  73. package/dist/export/panel.test.js.map +1 -0
  74. package/dist/export/pdf.d.ts +32 -0
  75. package/dist/export/pdf.d.ts.map +1 -0
  76. package/dist/export/pdf.js +229 -0
  77. package/dist/export/pdf.js.map +1 -0
  78. package/dist/export/pdf.test.d.ts +2 -0
  79. package/dist/export/pdf.test.d.ts.map +1 -0
  80. package/dist/export/pdf.test.js +151 -0
  81. package/dist/export/pdf.test.js.map +1 -0
  82. package/dist/export/share.d.ts +42 -0
  83. package/dist/export/share.d.ts.map +1 -0
  84. package/dist/export/share.js +181 -0
  85. package/dist/export/share.js.map +1 -0
  86. package/dist/export/share.test.d.ts +2 -0
  87. package/dist/export/share.test.d.ts.map +1 -0
  88. package/dist/export/share.test.js +116 -0
  89. package/dist/export/share.test.js.map +1 -0
  90. package/dist/metrics/index.d.ts +101 -0
  91. package/dist/metrics/index.d.ts.map +1 -0
  92. package/dist/metrics/index.js +221 -0
  93. package/dist/metrics/index.js.map +1 -0
  94. package/dist/metrics/metrics.test.d.ts +2 -0
  95. package/dist/metrics/metrics.test.d.ts.map +1 -0
  96. package/dist/metrics/metrics.test.js +162 -0
  97. package/dist/metrics/metrics.test.js.map +1 -0
  98. package/dist/metrics/people.d.ts +46 -0
  99. package/dist/metrics/people.d.ts.map +1 -0
  100. package/dist/metrics/people.js +126 -0
  101. package/dist/metrics/people.js.map +1 -0
  102. package/dist/metrics/people.test.d.ts +2 -0
  103. package/dist/metrics/people.test.d.ts.map +1 -0
  104. package/dist/metrics/people.test.js +84 -0
  105. package/dist/metrics/people.test.js.map +1 -0
  106. package/dist/metrics/team.d.ts +41 -0
  107. package/dist/metrics/team.d.ts.map +1 -0
  108. package/dist/metrics/team.js +0 -0
  109. package/dist/metrics/team.js.map +1 -0
  110. package/dist/metrics/team.test.d.ts +2 -0
  111. package/dist/metrics/team.test.d.ts.map +1 -0
  112. package/dist/metrics/team.test.js +90 -0
  113. package/dist/metrics/team.test.js.map +1 -0
  114. package/dist/pricing/catalog.d.ts +39 -0
  115. package/dist/pricing/catalog.d.ts.map +1 -0
  116. package/dist/pricing/catalog.js +59 -0
  117. package/dist/pricing/catalog.js.map +1 -0
  118. package/dist/pricing/cost.d.ts +25 -0
  119. package/dist/pricing/cost.d.ts.map +1 -0
  120. package/dist/pricing/cost.js +50 -0
  121. package/dist/pricing/cost.js.map +1 -0
  122. package/dist/pricing/cost.test.d.ts +2 -0
  123. package/dist/pricing/cost.test.d.ts.map +1 -0
  124. package/dist/pricing/cost.test.js +74 -0
  125. package/dist/pricing/cost.test.js.map +1 -0
  126. package/dist/server.d.ts +15 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +1141 -0
  129. package/dist/server.js.map +1 -0
  130. package/dist/setup.d.ts +70 -0
  131. package/dist/setup.d.ts.map +1 -0
  132. package/dist/setup.js +223 -0
  133. package/dist/setup.js.map +1 -0
  134. package/dist/setup.test.d.ts +2 -0
  135. package/dist/setup.test.d.ts.map +1 -0
  136. package/dist/setup.test.js +131 -0
  137. package/dist/setup.test.js.map +1 -0
  138. package/dist/watchers/claude.d.ts +34 -0
  139. package/dist/watchers/claude.d.ts.map +1 -0
  140. package/dist/watchers/claude.js +235 -0
  141. package/dist/watchers/claude.js.map +1 -0
  142. package/dist/watchers/claude.test.d.ts +2 -0
  143. package/dist/watchers/claude.test.d.ts.map +1 -0
  144. package/dist/watchers/claude.test.js +180 -0
  145. package/dist/watchers/claude.test.js.map +1 -0
  146. package/dist/watchers/git.d.ts +42 -0
  147. package/dist/watchers/git.d.ts.map +1 -0
  148. package/dist/watchers/git.js +218 -0
  149. package/dist/watchers/git.js.map +1 -0
  150. package/dist/watchers/git.test.d.ts +2 -0
  151. package/dist/watchers/git.test.d.ts.map +1 -0
  152. package/dist/watchers/git.test.js +73 -0
  153. package/dist/watchers/git.test.js.map +1 -0
  154. package/dist/watchers/identity.d.ts +34 -0
  155. package/dist/watchers/identity.d.ts.map +1 -0
  156. package/dist/watchers/identity.js +85 -0
  157. package/dist/watchers/identity.js.map +1 -0
  158. package/dist/watchers/identity.test.d.ts +2 -0
  159. package/dist/watchers/identity.test.d.ts.map +1 -0
  160. package/dist/watchers/identity.test.js +48 -0
  161. package/dist/watchers/identity.test.js.map +1 -0
  162. package/dist/web-ui.test.d.ts +2 -0
  163. package/dist/web-ui.test.d.ts.map +1 -0
  164. package/dist/web-ui.test.js +312 -0
  165. package/dist/web-ui.test.js.map +1 -0
  166. package/node_modules/@estela/shared/dist/index.d.ts +3 -0
  167. package/node_modules/@estela/shared/dist/index.d.ts.map +1 -0
  168. package/node_modules/@estela/shared/dist/index.js +19 -0
  169. package/node_modules/@estela/shared/dist/index.js.map +1 -0
  170. package/node_modules/@estela/shared/dist/money.d.ts +71 -0
  171. package/node_modules/@estela/shared/dist/money.d.ts.map +1 -0
  172. package/node_modules/@estela/shared/dist/money.js +171 -0
  173. package/node_modules/@estela/shared/dist/money.js.map +1 -0
  174. package/node_modules/@estela/shared/dist/types.d.ts +365 -0
  175. package/node_modules/@estela/shared/dist/types.d.ts.map +1 -0
  176. package/node_modules/@estela/shared/dist/types.js +24 -0
  177. package/node_modules/@estela/shared/dist/types.js.map +1 -0
  178. package/node_modules/@estela/shared/package.json +5 -0
  179. package/package.json +41 -0
  180. package/web/app.css +749 -0
  181. package/web/app.js +1257 -0
  182. package/web/apple-touch-icon.png +0 -0
  183. package/web/favicon.png +0 -0
  184. package/web/index.html +132 -0
  185. package/web/lib.js +94 -0
  186. 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":";;;AAqYA,oCAyCC;AA9aD,6CAA2C;AAC3C,qCAAoC;AACpC,qCAAkC;AAClC,yCAA0C;AAE1C;;;;;;GAMG;AAEU,QAAA,eAAe,GAAG,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AAEvE,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Od,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAsF;IACpG;QACE,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,gEAAgE;QAC1E,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,wEAAwE;YACxE,2CAA2C;YAC3C,EAAE,CAAC,IAAI,CAAC;;;;;;;;QAQN,CAAC,CAAC;YACJ,wEAAwE;YACxE,wEAAwE;YACxE,wBAAwB;YACxB,EAAE,CAAC,IAAI,CAAC;;;;;;;;QAQN,CAAC,CAAC;YACJ,yEAAyE;YACzE,+DAA+D;YAC/D,kCAAkC;YAClC,EAAE,CAAC,IAAI,CAAC;;;;;QAKN,CAAC,CAAC;YACJ,EAAE,CAAC,IAAI,CAAC;;;QAGN,CAAC,CAAC;QACN,CAAC;KACF;IACD;QACE,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,sCAAsC;QAChD,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,oEAAoE;YACpE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,0BAA0B,CAAC,CAAC;YACpE,uEAAuE;YACvE,iDAAiD;YACjD,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,qBAAqB,EAAE,SAAS,CAAC,CAAC;QAC9D,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,wCAAwC;QAClD,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,0BAA0B,CAAC,CAAC;YACxE,EAAE,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QACjF,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,qCAAqC;QAC/C,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,EAAE,CAAC,IAAI,CAAC;;;;;QAKN,CAAC,CAAC;QACN,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,qCAAqC;QAC/C,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC;YACvD,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,6CAA6C;QACvD,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,gCAAgC,CAAC;KACjF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,qCAAqC;QAC/C,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC7D;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,6BAA6B;QACvC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,0BAA0B,CAAC;KAC3E;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,kCAAkC;QAC5C,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,EAAE,CAAC,IAAI,CAAC;;;;QAIN,CAAC,CAAC;QACN,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,8CAA8C;QACxD,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE;YACV,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,+BAA+B,CAAC,CAAC;YACzE,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,qCAAqC,CAAC,CAAC;QAC/E,CAAC;KACF;CACF,CAAC;AAEF,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AAEnF,sEAAsE;AACtE,SAAS,SAAS,CAAC,EAAgB,EAAE,KAAa,EAAE,MAAc,EAAE,UAAkB;IACpF,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,KAAK,GAAG,CAAC,CAAC,GAAG,EAAwB,CAAC;IACtF,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;QAAE,OAAO;IACnD,EAAE,CAAC,IAAI,CAAC,eAAe,KAAK,eAAe,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,SAAgB,YAAY,CAAC,OAAe,uBAAe;IACzD,IAAI,IAAI,KAAK,UAAU;QAAE,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvE,MAAM,EAAE,GAAG,IAAI,0BAAY,CAAC,IAAI,CAAC,CAAC;IAClC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAC,GAAG,EACvC,CAAC;IAElC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,wCAAwC;QACxC,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACnF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,GAAG,CAAC,OAAO,GAAG,eAAe,EAAE,CAAC;QAClC,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,oCAAoC,GAAG,CAAC,OAAO,2CAA2C;YAC1F,IAAI,eAAe,wCAAwC,CAAC,CAAC;IACjE,CAAC;IAED,yEAAyE;IACzE,0DAA0D;IAC1D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO;YAAE,SAAS;QAC/C,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjB,IAAI,CAAC;YACH,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAClB,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC3E,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACpB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,uBAAuB,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,QAAQ,KAAK;gBACpE,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.test.d.ts","sourceRoot":"","sources":["../../src/db/schema.test.ts"],"names":[],"mappings":""}