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
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.diagnose = diagnose;
|
|
37
|
+
exports.renderFindings = renderFindings;
|
|
38
|
+
const shared_1 = require("@estela/shared");
|
|
39
|
+
const store = __importStar(require("./db/store.js"));
|
|
40
|
+
function diagnose(db) {
|
|
41
|
+
const findings = [];
|
|
42
|
+
const projects = store.listProjects(db);
|
|
43
|
+
if (projects.length === 0) {
|
|
44
|
+
findings.push({
|
|
45
|
+
severity: "error",
|
|
46
|
+
title: "No hay ningún proyecto configurado",
|
|
47
|
+
detail: "Sin proyectos no se imputa nada, aunque se esté capturando trabajo.",
|
|
48
|
+
fix: "Abre la pestaña Proyectos y registra el primero.",
|
|
49
|
+
});
|
|
50
|
+
return findings;
|
|
51
|
+
}
|
|
52
|
+
for (const project of projects) {
|
|
53
|
+
// 1. Identidad de commit. El fallo real: 3 commits capturados de 1697.
|
|
54
|
+
if (project.repoPaths.length > 0 && store.getProjectAuthors(db, project.id).length === 0) {
|
|
55
|
+
findings.push({
|
|
56
|
+
severity: "error",
|
|
57
|
+
title: `"${project.name}" no sabe con qué correo commiteas`,
|
|
58
|
+
detail: "Se filtra por la configuración global de git, que en el repositorio de " +
|
|
59
|
+
"un cliente casi nunca es la que usas. Estarás perdiendo casi todos tus commits.",
|
|
60
|
+
fix: `estela author --project ${project.id}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// 2. Sin repositorio no se imputa nada automáticamente.
|
|
64
|
+
if (project.repoPaths.length === 0) {
|
|
65
|
+
findings.push({
|
|
66
|
+
severity: "warning",
|
|
67
|
+
title: `"${project.name}" no tiene repositorio asignado`,
|
|
68
|
+
detail: "Solo recibirá las horas que anotes a mano.",
|
|
69
|
+
fix: "Asígnale uno en la pestaña Proyectos.",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// 3. Sin tarifa no hay importes. Solo aplica a clientes: en un empleo por
|
|
73
|
+
// nómina el importe no falta, es que no existe.
|
|
74
|
+
if (project.kind === "client" && project.billable
|
|
75
|
+
&& store.getRates(db, project.id).length === 0) {
|
|
76
|
+
findings.push({
|
|
77
|
+
severity: "warning",
|
|
78
|
+
title: `"${project.name}" es facturable pero no tiene tarifa`,
|
|
79
|
+
detail: "Las horas se registran, pero no se puede calcular su valor.",
|
|
80
|
+
fix: `estela rate set --project ${project.id} --rate <importe>`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// 3b. Horas deducidas solo de commits en un proyecto que se factura.
|
|
84
|
+
// No son un error: es la única forma de registrar trabajo hecho sin
|
|
85
|
+
// agente. Pero el rato anterior al primer commit de cada tanda es una
|
|
86
|
+
// estimación, y estas horas van a acabar en un informe que alguien va a
|
|
87
|
+
// pagar. Quien firma ese informe tiene que saber cuáles son.
|
|
88
|
+
if (project.kind === "client" && project.billable) {
|
|
89
|
+
const row = db.prepare(`
|
|
90
|
+
SELECT COUNT(*) AS n, COALESCE(SUM(seconds), 0) AS secs
|
|
91
|
+
FROM time_entries WHERE project_id = ? AND source = 'commit'
|
|
92
|
+
`).get(project.id);
|
|
93
|
+
if (row.n > 0) {
|
|
94
|
+
findings.push({
|
|
95
|
+
severity: "warning",
|
|
96
|
+
title: `"${project.name}" tiene ${(0, shared_1.formatDuration)(row.secs)} deducidas solo de commits`,
|
|
97
|
+
detail: (row.n === 1 ? "1 bloque sin sesión de agente que lo respalde."
|
|
98
|
+
: `${row.n} bloques sin sesión de agente que los respalde.`) +
|
|
99
|
+
" El tiempo se estima a partir de la separación entre commits, así que es " +
|
|
100
|
+
"menos exacto que el resto del informe.",
|
|
101
|
+
fix: "Revísalos en Mi día y corrige los minutos antes de emitir el informe.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 4. La invariante de las fechas. El fallo real: el día contado dos veces
|
|
107
|
+
// porque la CLI generaba el id en UTC y el servidor en hora local.
|
|
108
|
+
const mismatched = db.prepare(`
|
|
109
|
+
SELECT COUNT(*) AS n FROM time_entries
|
|
110
|
+
WHERE source = 'agent' AND id NOT LIKE '%_' || local_date || '_%'
|
|
111
|
+
`).get();
|
|
112
|
+
if (mismatched.n > 0) {
|
|
113
|
+
findings.push({
|
|
114
|
+
severity: "error",
|
|
115
|
+
title: `${mismatched.n} imputaciones con la fecha descuadrada`,
|
|
116
|
+
detail: "Su identificador lleva una fecha distinta a la del día al que están " +
|
|
117
|
+
"asignadas. Suele significar que un día está contado dos veces.",
|
|
118
|
+
fix: "Bórralas y reimporta: DELETE FROM time_entries WHERE source='agent' " +
|
|
119
|
+
"AND invoice_id IS NULL AND id NOT LIKE '%_'||local_date||'_%';",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// 5. Modelos sin precio: suman cero en silencio, que es justo lo que la
|
|
123
|
+
// regla del proyecto prohíbe para un número de facturación.
|
|
124
|
+
const unpriced = db.prepare(`
|
|
125
|
+
SELECT model, COUNT(*) AS n FROM agent_turns
|
|
126
|
+
WHERE cost_micro_usd IS NULL GROUP BY model ORDER BY n DESC
|
|
127
|
+
`).all();
|
|
128
|
+
for (const row of unpriced) {
|
|
129
|
+
const internal = row.model.startsWith("<");
|
|
130
|
+
findings.push({
|
|
131
|
+
severity: internal ? "info" : "warning",
|
|
132
|
+
title: `${row.n} turnos con el modelo "${row.model}" sin precio`,
|
|
133
|
+
detail: internal
|
|
134
|
+
? "Es un valor interno de la herramienta, no un modelo real. Suman cero al coste."
|
|
135
|
+
: "No está en el catálogo de precios, así que su coste no se cuenta.",
|
|
136
|
+
fix: internal
|
|
137
|
+
? "No hace falta nada: no representa consumo facturable."
|
|
138
|
+
: "Añádelo a PRICE_CATALOG en packages/daemon/src/pricing/catalog.ts",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// 6. Trabajo capturado que no llega a ningún proyecto.
|
|
142
|
+
const orphans = db.prepare(`
|
|
143
|
+
SELECT COUNT(DISTINCT repo_path) AS repos, COUNT(*) AS turns
|
|
144
|
+
FROM agent_turns WHERE repo_path IS NOT NULL
|
|
145
|
+
`).get();
|
|
146
|
+
const configured = new Set(db.prepare("SELECT repo_path FROM project_repos").all()
|
|
147
|
+
.map((r) => r.repo_path));
|
|
148
|
+
const unassigned = db.prepare("SELECT DISTINCT repo_path FROM agent_turns WHERE repo_path IS NOT NULL").all()
|
|
149
|
+
.filter((r) => ![...configured].some((c) => r.repo_path.startsWith(c)));
|
|
150
|
+
if (unassigned.length > 0) {
|
|
151
|
+
findings.push({
|
|
152
|
+
severity: "warning",
|
|
153
|
+
title: `${unassigned.length} repositorios con trabajo sin proyecto`,
|
|
154
|
+
detail: `De ${orphans.repos} repositorios con actividad capturada, ${unassigned.length} ` +
|
|
155
|
+
"no pertenecen a ningún proyecto. Son horas que ya tienes y no puedes informar.",
|
|
156
|
+
fix: "Asígnalos en la pestaña Proyectos.",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// 7. Bloques sospechosamente largos: delante de un cliente, una jornada de
|
|
160
|
+
// catorce horas es una pregunta que conviene haberse hecho antes.
|
|
161
|
+
const long = db.prepare(`
|
|
162
|
+
SELECT COUNT(*) AS n, MAX(seconds) AS max_seconds
|
|
163
|
+
FROM time_entries WHERE seconds > 10 * 3600
|
|
164
|
+
`).get();
|
|
165
|
+
if (long.n > 0) {
|
|
166
|
+
findings.push({
|
|
167
|
+
severity: "warning",
|
|
168
|
+
title: `${long.n} bloques de más de 10 horas`,
|
|
169
|
+
detail: `El mayor es de ${(0, shared_1.formatDuration)(long.max_seconds ?? 0)}. Puede ser real, ` +
|
|
170
|
+
"pero también una sesión que quedó abierta.",
|
|
171
|
+
fix: "Revísalos en Mi día y corrige los minutos si no cuadran.",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// 8. Paneles publicados que se han quedado atrás.
|
|
175
|
+
//
|
|
176
|
+
// Enseñarle a un cliente datos de hace días da impresión de herramienta
|
|
177
|
+
// desatendida, y esa impresión pesa más que cualquier métrica bonita.
|
|
178
|
+
for (const pub of store.listPublications(db)) {
|
|
179
|
+
if (pub.staleBlocks === 0)
|
|
180
|
+
continue;
|
|
181
|
+
const project = projects.find((p) => p.id === pub.projectId);
|
|
182
|
+
findings.push({
|
|
183
|
+
severity: "warning",
|
|
184
|
+
title: `El panel de "${project?.name ?? pub.projectId}" se ha quedado atrás`,
|
|
185
|
+
detail: pub.staleBlocks === 1
|
|
186
|
+
? `Hay 1 bloque de trabajo posterior a la última publicación ` +
|
|
187
|
+
`(${pub.publishedAt.toISOString().slice(0, 10)}). Tu cliente está viendo datos viejos.`
|
|
188
|
+
: `Hay ${pub.staleBlocks} bloques de trabajo posteriores a la última publicación ` +
|
|
189
|
+
`(${pub.publishedAt.toISOString().slice(0, 10)}). Tu cliente está viendo datos viejos.`,
|
|
190
|
+
fix: `estela publish --project ${pub.projectId} --token ${pub.token}` +
|
|
191
|
+
(pub.baseUrl ? ` --base-url ${pub.baseUrl}` : ""),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// 9. El canario del parser.
|
|
195
|
+
const scan = db.prepare("SELECT warnings FROM scan_log ORDER BY id DESC LIMIT 1")
|
|
196
|
+
.get();
|
|
197
|
+
if (scan?.warnings) {
|
|
198
|
+
findings.push({
|
|
199
|
+
severity: "error",
|
|
200
|
+
title: "El lector de transcripts avisó de algo",
|
|
201
|
+
detail: scan.warnings,
|
|
202
|
+
fix: "Revisa los totales antes de publicar nada.",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const order = { error: 0, warning: 1, info: 2 };
|
|
206
|
+
return findings.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
207
|
+
}
|
|
208
|
+
function renderFindings(findings) {
|
|
209
|
+
if (findings.length === 0) {
|
|
210
|
+
return "\n Todo en orden. Puedes publicar con tranquilidad.\n";
|
|
211
|
+
}
|
|
212
|
+
const icon = { error: "✗", warning: "!", info: "·" };
|
|
213
|
+
const out = [""];
|
|
214
|
+
for (const f of findings) {
|
|
215
|
+
out.push(` ${icon[f.severity]} ${f.title}`);
|
|
216
|
+
out.push(` ${f.detail}`);
|
|
217
|
+
out.push(` → ${f.fix}`);
|
|
218
|
+
out.push("");
|
|
219
|
+
}
|
|
220
|
+
const errors = findings.filter((f) => f.severity === "error").length;
|
|
221
|
+
const warnings = findings.filter((f) => f.severity === "warning").length;
|
|
222
|
+
out.push(errors > 0
|
|
223
|
+
? ` ${errors} ${errors === 1 ? "problema" : "problemas"} que conviene arreglar antes de enseñar esto.`
|
|
224
|
+
: warnings > 0
|
|
225
|
+
? ` Nada grave: ${warnings} ${warnings === 1 ? "aviso" : "avisos"}.`
|
|
226
|
+
: " Solo notas informativas.");
|
|
227
|
+
return out.join("\n") + "\n";
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Client, Invoice, Project, TimeEntry } from "@estela/shared";
|
|
2
|
+
import { formatMoney } from "@estela/shared";
|
|
3
|
+
export declare function timeEntriesToCsv(entries: readonly TimeEntry[], project: Project, client: Client, hourlyRateOf: (at: Date) => {
|
|
4
|
+
amount: number;
|
|
5
|
+
currency: string;
|
|
6
|
+
} | null): string;
|
|
7
|
+
export declare function invoiceToCsv(invoice: Invoice, client: Client, project: Project): string;
|
|
8
|
+
export { formatMoney };
|
|
9
|
+
//# sourceMappingURL=csv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/export/csv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAkB,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAoB7D,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACtE,MAAM,CAiCR;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CA8BvF;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatMoney = void 0;
|
|
4
|
+
exports.timeEntriesToCsv = timeEntriesToCsv;
|
|
5
|
+
exports.invoiceToCsv = invoiceToCsv;
|
|
6
|
+
const shared_1 = require("@estela/shared");
|
|
7
|
+
Object.defineProperty(exports, "formatMoney", { enumerable: true, get: function () { return shared_1.formatMoney; } });
|
|
8
|
+
/**
|
|
9
|
+
* Export a CSV.
|
|
10
|
+
*
|
|
11
|
+
* Existe para que puedas llevarte tus datos a Excel, a tu gestoría o a otra
|
|
12
|
+
* herramienta. Poder salir es parte de merecer que te dejen entrar.
|
|
13
|
+
*/
|
|
14
|
+
/** Escapa un campo. Sin esto, un commit con una coma parte la fila en dos. */
|
|
15
|
+
function cell(value) {
|
|
16
|
+
const text = value === null || value === undefined ? "" : String(value);
|
|
17
|
+
return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
18
|
+
}
|
|
19
|
+
function rows(lines) {
|
|
20
|
+
// BOM para que Excel abra los acentos y el euro sin destrozarlos.
|
|
21
|
+
return "" + lines.map((line) => line.map(cell).join(",")).join("\r\n") + "\r\n";
|
|
22
|
+
}
|
|
23
|
+
function timeEntriesToCsv(entries, project, client, hourlyRateOf) {
|
|
24
|
+
const header = [
|
|
25
|
+
"fecha", "inicio", "fin", "horas", "descripcion", "proyecto", "cliente",
|
|
26
|
+
"facturable", "tarifa", "moneda", "importe", "coste_ia_usd", "factura", "commits",
|
|
27
|
+
];
|
|
28
|
+
const lines = [header];
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const rate = hourlyRateOf(e.startedAt);
|
|
31
|
+
const amount = rate
|
|
32
|
+
? (0, shared_1.billableAmount)(e.seconds, rate).amount / 100
|
|
33
|
+
: null;
|
|
34
|
+
lines.push([
|
|
35
|
+
e.startedAt.toISOString().slice(0, 10),
|
|
36
|
+
e.startedAt.toISOString().slice(11, 16),
|
|
37
|
+
e.endedAt.toISOString().slice(11, 16),
|
|
38
|
+
(e.seconds / 3600).toFixed(4),
|
|
39
|
+
e.description,
|
|
40
|
+
project.name,
|
|
41
|
+
client.name,
|
|
42
|
+
e.billable ? "si" : "no",
|
|
43
|
+
rate ? (rate.amount / 100).toFixed(2) : "",
|
|
44
|
+
rate?.currency ?? "",
|
|
45
|
+
amount === null ? "" : amount.toFixed(2),
|
|
46
|
+
(e.aiCost.microUsd / 1_000_000).toFixed(4),
|
|
47
|
+
e.invoiceId ?? "",
|
|
48
|
+
e.commitHashes.join(" "),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
return rows(lines);
|
|
52
|
+
}
|
|
53
|
+
function invoiceToCsv(invoice, client, project) {
|
|
54
|
+
const lines = [
|
|
55
|
+
["factura", invoice.number],
|
|
56
|
+
["cliente", client.name],
|
|
57
|
+
["proyecto", project.name],
|
|
58
|
+
["emitida", invoice.issuedAt.toISOString().slice(0, 10)],
|
|
59
|
+
["periodo", `${invoice.periodStart.toISOString().slice(0, 10)} a ${invoice.cutoffAt.toISOString().slice(0, 10)}`],
|
|
60
|
+
["moneda", invoice.currency],
|
|
61
|
+
[],
|
|
62
|
+
["concepto", "horas", "tarifa", "importe"],
|
|
63
|
+
];
|
|
64
|
+
for (const line of invoice.lines) {
|
|
65
|
+
lines.push([
|
|
66
|
+
line.description,
|
|
67
|
+
(line.seconds / 3600).toFixed(4),
|
|
68
|
+
(line.hourlyRate.amount / 100).toFixed(2),
|
|
69
|
+
(line.amount.amount / 100).toFixed(2),
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
lines.push([]);
|
|
73
|
+
lines.push(["subtotal", "", "", (invoice.subtotal.amount / 100).toFixed(2)]);
|
|
74
|
+
if (invoice.aiCostBilled) {
|
|
75
|
+
lines.push(["coste_ia_repercutido", "", "", (invoice.aiCostBilled.amount / 100).toFixed(2)]);
|
|
76
|
+
}
|
|
77
|
+
lines.push(["total", "", "", (invoice.total.amount / 100).toFixed(2)]);
|
|
78
|
+
lines.push(["consumo_ia_usd_equiv_api", "", "", (invoice.aiCost.microUsd / 1_000_000).toFixed(4)]);
|
|
79
|
+
return rows(lines);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=csv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../../src/export/csv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Client, Invoice, Money, Project } from "@estela/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Maqueta la factura en PDF.
|
|
4
|
+
*
|
|
5
|
+
* Las columnas se alinean por el borde derecho porque una columna de importes
|
|
6
|
+
* desalineada es lo primero que hace que una factura parezca improvisada.
|
|
7
|
+
*/
|
|
8
|
+
export interface InvoicePdfOptions {
|
|
9
|
+
/** Datos de quien emite. Van en la cabecera. */
|
|
10
|
+
readonly issuer?: {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly taxId?: string;
|
|
13
|
+
readonly email?: string;
|
|
14
|
+
readonly address?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Coste real de IA imputado al periodo (cuota amortizada). Solo informativo. */
|
|
17
|
+
readonly amortizedAiCost?: Money;
|
|
18
|
+
readonly footer?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function invoiceToPdf(invoice: Invoice, client: Client, project: Project, options?: InvoicePdfOptions): Buffer;
|
|
21
|
+
//# sourceMappingURL=invoice-pdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-pdf.d.ts","sourceRoot":"","sources":["../../src/export/invoice-pdf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAKtE;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,iFAAiF;IACjF,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAQD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CAkJR"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.invoiceToPdf = invoiceToPdf;
|
|
4
|
+
const shared_1 = require("@estela/shared");
|
|
5
|
+
const pdf_js_1 = require("./pdf.js");
|
|
6
|
+
const M = 56; // margen
|
|
7
|
+
const COL_TIME = 372; // bordes derechos de cada columna
|
|
8
|
+
const COL_RATE = 452;
|
|
9
|
+
const COL_AMOUNT = 539;
|
|
10
|
+
const BOTTOM = 780;
|
|
11
|
+
function invoiceToPdf(invoice, client, project, options = {}) {
|
|
12
|
+
const doc = new pdf_js_1.PdfDocument();
|
|
13
|
+
const date = (d) => d.toISOString().slice(0, 10);
|
|
14
|
+
let y = M;
|
|
15
|
+
// --- Cabecera -------------------------------------------------------------
|
|
16
|
+
doc.text(M, y + 4, "INFORME DE HORAS", "Helvetica-Bold", 20);
|
|
17
|
+
doc.textRight(COL_AMOUNT, y - 2, invoice.number, "Helvetica-Bold", 12);
|
|
18
|
+
doc.textRight(COL_AMOUNT, y + 12, `Emitido el ${date(invoice.issuedAt)}`, "Helvetica", 9, 0.4);
|
|
19
|
+
y += 30;
|
|
20
|
+
doc.line(M, y, COL_AMOUNT, y, 1.2, 0.15);
|
|
21
|
+
y += 24;
|
|
22
|
+
// --- Emisor y cliente, en dos columnas -----------------------------------
|
|
23
|
+
const rightCol = M + 260;
|
|
24
|
+
const issuer = options.issuer;
|
|
25
|
+
if (issuer) {
|
|
26
|
+
doc.text(M, y, "DE", "Helvetica-Bold", 8, 0.45);
|
|
27
|
+
doc.text(M, y + 15, issuer.name, "Helvetica-Bold", 11);
|
|
28
|
+
let sub = y + 29;
|
|
29
|
+
for (const line of [issuer.taxId, issuer.email, issuer.address]) {
|
|
30
|
+
if (!line)
|
|
31
|
+
continue;
|
|
32
|
+
doc.text(M, sub, line, "Helvetica", 9, 0.35);
|
|
33
|
+
sub += 12;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
doc.text(rightCol, y, "PARA", "Helvetica-Bold", 8, 0.45);
|
|
37
|
+
doc.text(rightCol, y + 15, client.name, "Helvetica-Bold", 11);
|
|
38
|
+
let clientY = y + 29;
|
|
39
|
+
for (const line of [client.taxId, client.email, client.address]) {
|
|
40
|
+
if (!line)
|
|
41
|
+
continue;
|
|
42
|
+
doc.text(rightCol, clientY, line, "Helvetica", 9, 0.35);
|
|
43
|
+
clientY += 12;
|
|
44
|
+
}
|
|
45
|
+
y = Math.max(clientY, y + 70) + 14;
|
|
46
|
+
// --- Proyecto y periodo ---------------------------------------------------
|
|
47
|
+
doc.rect(M, y - 12, COL_AMOUNT - M, 34, 0.96);
|
|
48
|
+
doc.text(M + 12, y, "PROYECTO", "Helvetica-Bold", 8, 0.45);
|
|
49
|
+
doc.text(M + 12, y + 13, project.name, "Helvetica", 10);
|
|
50
|
+
doc.text(rightCol, y, "PERIODO", "Helvetica-Bold", 8, 0.45);
|
|
51
|
+
doc.text(rightCol, y + 13, `${date(invoice.periodStart)} al ${date(invoice.cutoffAt)}`, "Helvetica", 10);
|
|
52
|
+
y += 48;
|
|
53
|
+
// --- Cabecera de tabla ----------------------------------------------------
|
|
54
|
+
const header = () => {
|
|
55
|
+
doc.text(M, y, "CONCEPTO", "Helvetica-Bold", 8, 0.45);
|
|
56
|
+
doc.textRight(COL_TIME, y, "TIEMPO", "Helvetica-Bold", 8, 0.45);
|
|
57
|
+
doc.textRight(COL_RATE, y, "TARIFA", "Helvetica-Bold", 8, 0.45);
|
|
58
|
+
doc.textRight(COL_AMOUNT, y, "IMPORTE", "Helvetica-Bold", 8, 0.45);
|
|
59
|
+
y += 7;
|
|
60
|
+
doc.line(M, y, COL_AMOUNT, y, 0.8, 0.3);
|
|
61
|
+
y += 15;
|
|
62
|
+
};
|
|
63
|
+
header();
|
|
64
|
+
// --- Conceptos ------------------------------------------------------------
|
|
65
|
+
// La columna TIEMPO se alinea a la derecha en COL_TIME, así que su texto crece
|
|
66
|
+
// hacia la izquierda. Hay que restar el más ancho de esos textos, no un hueco
|
|
67
|
+
// fijo, o la descripción se le mete encima.
|
|
68
|
+
const widestTime = invoice.lines.reduce((max, l) => Math.max(max, pdf_js_1.PdfDocument.measure((0, shared_1.formatDuration)(l.seconds), "Helvetica", 10)), 0);
|
|
69
|
+
const maxDescWidth = COL_TIME - M - widestTime - 14;
|
|
70
|
+
for (const line of invoice.lines) {
|
|
71
|
+
if (y > BOTTOM - 60) {
|
|
72
|
+
doc.newPage();
|
|
73
|
+
y = M;
|
|
74
|
+
header();
|
|
75
|
+
}
|
|
76
|
+
const text = pdf_js_1.PdfDocument.truncate(line.description, "Helvetica", 10, maxDescWidth);
|
|
77
|
+
doc.text(M, y, text, "Helvetica", 10);
|
|
78
|
+
doc.textRight(COL_TIME, y, (0, shared_1.formatDuration)(line.seconds), "Helvetica", 10, 0.2);
|
|
79
|
+
doc.textRight(COL_RATE, y, `${(0, shared_1.formatMoney)(line.hourlyRate)}/h`, "Helvetica", 10, 0.4);
|
|
80
|
+
doc.textRight(COL_AMOUNT, y, (0, shared_1.formatMoney)(line.amount), "Helvetica", 10);
|
|
81
|
+
y += 9;
|
|
82
|
+
doc.line(M, y, COL_AMOUNT, y, 0.4, 0.9);
|
|
83
|
+
y += 12;
|
|
84
|
+
}
|
|
85
|
+
// --- Totales --------------------------------------------------------------
|
|
86
|
+
if (y > BOTTOM - 110) {
|
|
87
|
+
doc.newPage();
|
|
88
|
+
y = M;
|
|
89
|
+
}
|
|
90
|
+
y += 8;
|
|
91
|
+
doc.line(M, y, COL_AMOUNT, y, 0.8, 0.3);
|
|
92
|
+
y += 18;
|
|
93
|
+
doc.text(M, y, `${invoice.lines.length} conceptos`, "Helvetica", 9, 0.4);
|
|
94
|
+
doc.textRight(COL_TIME, y, (0, shared_1.formatDuration)(invoice.totalSeconds), "Helvetica-Bold", 10);
|
|
95
|
+
doc.textRight(COL_AMOUNT, y, (0, shared_1.formatMoney)(invoice.subtotal), "Helvetica", 10);
|
|
96
|
+
y += 18;
|
|
97
|
+
if (invoice.aiCostBilled) {
|
|
98
|
+
doc.text(M, y, `Coste de IA repercutido (1 USD = ${invoice.usdFxRate} ${invoice.currency})`, "Helvetica", 9, 0.4);
|
|
99
|
+
doc.textRight(COL_AMOUNT, y, (0, shared_1.formatMoney)(invoice.aiCostBilled), "Helvetica", 10);
|
|
100
|
+
y += 18;
|
|
101
|
+
}
|
|
102
|
+
y += 4;
|
|
103
|
+
doc.rect(rightCol - 20, y - 14, COL_AMOUNT - rightCol + 20, 32, 0.94);
|
|
104
|
+
doc.text(rightCol - 8, y + 2, "VALOR", "Helvetica-Bold", 11);
|
|
105
|
+
doc.textRight(COL_AMOUNT - 8, y + 3, (0, shared_1.formatMoney)(invoice.total), "Helvetica-Bold", 14);
|
|
106
|
+
y += 46;
|
|
107
|
+
// --- Nota interna ---------------------------------------------------------
|
|
108
|
+
// Lo que costó la IA es tuyo, no del cliente: va en gris, al pie, y nunca
|
|
109
|
+
// dentro del bloque de totales.
|
|
110
|
+
if (invoice.aiCost.microUsd > 0 && !invoice.aiCostBilled) {
|
|
111
|
+
doc.line(M, y, COL_AMOUNT, y, 0.4, 0.85);
|
|
112
|
+
y += 14;
|
|
113
|
+
doc.text(M, y, "NOTA INTERNA (no se comparte)", "Helvetica-Bold", 7, 0.55);
|
|
114
|
+
y += 12;
|
|
115
|
+
const consumption = `Consumo de IA del periodo: ${(0, shared_1.formatAiCost)(invoice.aiCost)} en tarifa API equivalente.`;
|
|
116
|
+
doc.text(M, y, consumption, "Helvetica", 8, 0.5);
|
|
117
|
+
y += 11;
|
|
118
|
+
if (options.amortizedAiCost) {
|
|
119
|
+
doc.text(M, y, `Coste real imputado desde tu suscripción: ${(0, shared_1.formatMoney)(options.amortizedAiCost)}.`, "Helvetica", 8, 0.5);
|
|
120
|
+
y += 11;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (invoice.notes) {
|
|
124
|
+
y += 6;
|
|
125
|
+
for (const line of pdf_js_1.PdfDocument.wrap(invoice.notes, "Helvetica", 9, COL_AMOUNT - M)) {
|
|
126
|
+
doc.text(M, y, line, "Helvetica", 9, 0.35);
|
|
127
|
+
y += 12;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (options.footer) {
|
|
131
|
+
doc.text(M, BOTTOM + 20, options.footer, "Helvetica", 8, 0.55);
|
|
132
|
+
}
|
|
133
|
+
return doc.toBuffer();
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=invoice-pdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-pdf.js","sourceRoot":"","sources":["../../src/export/invoice-pdf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { AiPayer, Client, CommitRecord, Project, TimeEntry } from "@estela/shared";
|
|
2
|
+
import { type Money } from "@estela/shared";
|
|
3
|
+
/**
|
|
4
|
+
* Panel de solo lectura, publicable.
|
|
5
|
+
*
|
|
6
|
+
* A diferencia del informe —que es un documento— esto es **el producto**, en
|
|
7
|
+
* modo lectura: se filtra por periodo, se despliegan los días y se ven los
|
|
8
|
+
* commits. Quien lo abre no recibe un adjunto: usa la herramienta. Esa es la
|
|
9
|
+
* diferencia entre "qué informe tan majo" y "esto lo quiero para mi equipo".
|
|
10
|
+
*
|
|
11
|
+
* Sigue siendo un único fichero sin recursos externos, así que se sirve como
|
|
12
|
+
* estático desde cualquier sitio y no depende de ninguna máquina encendida.
|
|
13
|
+
*
|
|
14
|
+
* Reglas de lo que sale y lo que no, iguales que en el informe:
|
|
15
|
+
* - Nunca el consumo de IA cuando la paga quien publica (ver `AiPayer`).
|
|
16
|
+
* - Los importes solo si se piden expresamente.
|
|
17
|
+
*/
|
|
18
|
+
export interface PanelOptions {
|
|
19
|
+
readonly project: Project;
|
|
20
|
+
readonly client: Client;
|
|
21
|
+
readonly entries: readonly TimeEntry[];
|
|
22
|
+
readonly authorName?: string;
|
|
23
|
+
readonly withAmounts?: boolean;
|
|
24
|
+
readonly rateAt?: (at: Date) => Money | null;
|
|
25
|
+
readonly aiPayer?: AiPayer;
|
|
26
|
+
readonly commitsOf?: (hashes: readonly string[]) => readonly {
|
|
27
|
+
hash: string;
|
|
28
|
+
subject: string;
|
|
29
|
+
}[];
|
|
30
|
+
/** Momento de generación. Se enseña para que se sepa cómo de fresco es. */
|
|
31
|
+
readonly generatedAt?: Date;
|
|
32
|
+
/** Commits del proyecto. Sin ellos no hay métricas, solo un listado de días. */
|
|
33
|
+
readonly commits?: readonly CommitRecord[];
|
|
34
|
+
/** Ramas ya integradas, para separar trabajo abierto de entregado. */
|
|
35
|
+
readonly merged?: ReadonlySet<string>;
|
|
36
|
+
/**
|
|
37
|
+
* El equipo del proyecto, si se quiere enseñar.
|
|
38
|
+
*
|
|
39
|
+
* Quien recibe este panel es el líder técnico del cliente, y ese repositorio
|
|
40
|
+
* es suyo: los nombres, ramas y commits de su gente ya los tiene con `git
|
|
41
|
+
* log`. Enseñárselos no le regala nada, y demuestra que la herramienta
|
|
42
|
+
* funciona sobre sus datos de verdad.
|
|
43
|
+
*
|
|
44
|
+
* Lo que sí produce Estela y él no puede sacar solo son las **horas**. Por eso
|
|
45
|
+
* `seconds` solo viaja para quien publica el panel. Para los demás no es que
|
|
46
|
+
* se oculte al pintar: **no entra en el fichero**. Un candado de CSS con el
|
|
47
|
+
* dato debajo no es un candado, es una invitación a mirar el código fuente.
|
|
48
|
+
*/
|
|
49
|
+
readonly team?: readonly PanelTeamMember[];
|
|
50
|
+
/**
|
|
51
|
+
* A dónde lleva el candado. Por defecto, getestela.dev.
|
|
52
|
+
*
|
|
53
|
+
* Sigue siendo configurable aunque el dominio ya esté comprado: un panel
|
|
54
|
+
* publicado en un entorno de pruebas, o desde una máquina sin este valor
|
|
55
|
+
* actualizado, no debe depender de tener que recordar pasarlo siempre.
|
|
56
|
+
*/
|
|
57
|
+
readonly siteUrl?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface PanelTeamMember {
|
|
60
|
+
readonly name: string;
|
|
61
|
+
readonly isMe: boolean;
|
|
62
|
+
/** Solo para quien publica. Para el resto se omite, no se pone a cero. */
|
|
63
|
+
readonly seconds?: number;
|
|
64
|
+
readonly commits: number;
|
|
65
|
+
readonly branches: number;
|
|
66
|
+
readonly lastDay: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Token de acceso.
|
|
70
|
+
*
|
|
71
|
+
* 128 bits en la ruta: una URL publicada es pública para quien la tenga, así
|
|
72
|
+
* que lo único que la protege es que nadie pueda adivinarla. Revocar el acceso
|
|
73
|
+
* es borrar el fichero del servidor — no hay caducidad falsa en el cliente,
|
|
74
|
+
* porque una caducidad que comprueba el navegador no protege de nada.
|
|
75
|
+
*/
|
|
76
|
+
export declare function newPanelToken(): string;
|
|
77
|
+
export declare function buildPanel(options: PanelOptions): string;
|
|
78
|
+
//# sourceMappingURL=panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/export/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EACqD,KAAK,KAAK,EACrE,MAAM,gBAAgB,CAAC;AAGxB;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,KAAK,KAAK,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjG,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAiBD,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAsFxD"}
|