trackops 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui/index.html CHANGED
@@ -1,356 +1,96 @@
1
1
  <!doctype html>
2
2
  <html lang="es">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Ops Control | Multi-project Dashboard</title>
7
- <link rel="stylesheet" href="/styles.css" />
8
- </head>
9
- <body>
10
- <div class="app-shell">
11
- <header class="topbar">
12
- <div class="brand-block">
13
- <p class="eyebrow">Project Operations Mesh</p>
14
- <h1>Ops Control Center</h1>
15
- <p id="focusSummary" class="subtle">Cargando entorno operativo...</p>
16
- </div>
17
-
18
- <div class="topbar-actions">
19
- <div class="project-switcher">
20
- <label class="sr-only" for="projectSelect">Proyecto activo</label>
21
- <select id="projectSelect"></select>
22
- </div>
23
- <div id="runtimeBadge" class="runtime-badge">Runtime</div>
24
- <button id="refreshButton" class="ghost-button" type="button">Refrescar</button>
25
- <button id="syncButton" class="primary-button" type="button">Sincronizar</button>
26
- </div>
27
- </header>
28
-
29
- <nav class="tabs" aria-label="Navegacion del dashboard">
30
- <button class="tab-button is-active" data-tab="overview" type="button">Overview</button>
31
- <button class="tab-button" data-tab="board" type="button">Board</button>
32
- <button class="tab-button" data-tab="execution" type="button">Execution</button>
33
- <button class="tab-button" data-tab="insights" type="button">Insights</button>
34
- </nav>
35
-
36
- <main class="tab-shell">
37
- <section id="tab-overview" class="tab-panel is-active">
38
- <details class="panel panel-details" open>
39
- <summary class="details-summary">
40
- <div>
41
- <p class="eyebrow">Workspace</p>
42
- <h2 id="projectName">Proyecto</h2>
43
- </div>
44
- <span id="phaseBadge" class="phase-badge">Fase</span>
45
- </summary>
46
- <div class="details-body">
47
- <div class="summary-grid" id="summaryGrid"></div>
48
- <div class="overview-grid">
49
- <article class="panel inset-panel">
50
- <div class="panel-head compact">
51
- <div>
52
- <p class="eyebrow">Entrega</p>
53
- <h3>Foco y siguiente paso</h3>
54
- </div>
55
- </div>
56
- <div class="stack-list">
57
- <div class="task-snippet">
58
- <p class="label">Objetivo de entrega</p>
59
- <p id="deliveryTarget" class="value-text">Cargando...</p>
60
- </div>
61
- <div>
62
- <p class="label">Proximo movimiento</p>
63
- <div id="nextTaskCard"></div>
64
- </div>
65
- </div>
66
- </article>
67
-
68
- <article class="panel inset-panel">
69
- <div class="panel-head compact">
70
- <div>
71
- <p class="eyebrow">Portfolio</p>
72
- <h3>Proyectos registrados</h3>
73
- </div>
74
- </div>
75
- <div class="stack-list">
76
- <div id="projectList" class="stack-list"></div>
77
- <form id="registerProjectForm" class="stack-form">
78
- <label for="registerRootInput">Registrar un proyecto ya instalado</label>
79
- <div class="inline-form">
80
- <input id="registerRootInput" type="text" placeholder="C:\\Ruta\\Proyecto" />
81
- <button class="ghost-button" type="submit">Registrar</button>
82
- </div>
83
- </form>
84
- <form id="installProjectForm" class="stack-form">
85
- <label for="installRootInput">Instalar sistema ops en otro proyecto</label>
86
- <div class="inline-form">
87
- <input id="installRootInput" type="text" placeholder="C:\\Ruta\\NuevoProyecto" />
88
- <button class="primary-button" type="submit">Instalar</button>
89
- </div>
90
- </form>
91
- </div>
92
- </article>
93
- </div>
94
- </div>
95
- </details>
96
-
97
- <div class="overview-grid">
98
- <details class="panel panel-details" open>
99
- <summary class="details-summary">
100
- <div>
101
- <p class="eyebrow">Charts</p>
102
- <h2>Evolucion y desempeno</h2>
103
- </div>
104
- </summary>
105
- <div class="details-body charts-grid">
106
- <div class="chart-card">
107
- <p class="label">Progreso por fase</p>
108
- <div id="phaseChart"></div>
109
- </div>
110
- <div class="chart-card">
111
- <p class="label">Distribucion por estado</p>
112
- <div id="statusChart"></div>
113
- </div>
114
- <div class="chart-card">
115
- <p class="label">Actividad reciente</p>
116
- <div id="activityChart"></div>
117
- </div>
118
- </div>
119
- </details>
120
-
121
- <details class="panel panel-details" open>
122
- <summary class="details-summary">
123
- <div>
124
- <p class="eyebrow">Repo</p>
125
- <h2>Salud operativa</h2>
126
- </div>
127
- <span id="branchBadge" class="secondary-badge">branch</span>
128
- </summary>
129
- <div class="details-body">
130
- <div id="repoOverview" class="stack-list"></div>
131
- <div>
132
- <p class="label">Documentacion derivada</p>
133
- <div id="docsList" class="tag-row"></div>
134
- </div>
135
- </div>
136
- </details>
137
-
138
- <details class="panel panel-details" open>
139
- <summary class="details-summary">
140
- <div>
141
- <p class="eyebrow">Decisions</p>
142
- <h2>Externos y bloqueos</h2>
143
- </div>
144
- </summary>
145
- <div class="details-body">
146
- <div id="decisionList" class="stack-list"></div>
147
- </div>
148
- </details>
149
- </div>
150
- </section>
151
-
152
- <section id="tab-board" class="tab-panel" hidden>
153
- <div class="split-grid">
154
- <details class="panel panel-details" open>
155
- <summary class="details-summary">
156
- <div>
157
- <p class="eyebrow">Task Board</p>
158
- <h2>Tablero operativo</h2>
159
- </div>
160
- <button id="newTaskButton" class="ghost-button" type="button">Nueva tarea</button>
161
- </summary>
162
- <div class="details-body">
163
- <div id="board" class="board-grid" aria-label="Tablero de tareas"></div>
164
- </div>
165
- </details>
166
-
167
- <details class="panel panel-details" open>
168
- <summary class="details-summary">
169
- <div>
170
- <p class="eyebrow">Task Studio</p>
171
- <h2 id="editorTitle">Nueva tarea</h2>
172
- </div>
173
- <button id="resetTaskButton" class="ghost-button" type="button">Limpiar</button>
174
- </summary>
175
- <div class="details-body">
176
- <div class="action-strip" id="taskActionStrip" aria-label="Acciones rapidas de tarea">
177
- <button type="button" class="chip-button" data-task-action="start">Iniciar</button>
178
- <button type="button" class="chip-button" data-task-action="review">Revision</button>
179
- <button type="button" class="chip-button" data-task-action="complete">Completar</button>
180
- <button type="button" class="chip-button" data-task-action="block">Bloquear</button>
181
- <button type="button" class="chip-button" data-task-action="pending">Pendiente</button>
182
- </div>
183
-
184
- <form id="taskForm" class="task-form">
185
- <div class="field">
186
- <label for="taskTitle">Titulo</label>
187
- <input id="taskTitle" name="title" type="text" required />
188
- </div>
189
-
190
- <div class="form-grid two-up">
191
- <div class="field">
192
- <label for="taskPhase">Fase</label>
193
- <select id="taskPhase" name="phase">
194
- <!-- Populated dynamically from API -->
195
- </select>
196
- </div>
197
-
198
- <div class="field">
199
- <label for="taskPriority">Prioridad</label>
200
- <select id="taskPriority" name="priority">
201
- <option value="P0">P0</option>
202
- <option value="P1">P1</option>
203
- <option value="P2">P2</option>
204
- <option value="P3">P3</option>
205
- </select>
206
- </div>
207
- </div>
208
-
209
- <div class="form-grid two-up">
210
- <div class="field">
211
- <label for="taskStatus">Estado</label>
212
- <select id="taskStatus" name="status">
213
- <option value="pending">Pendiente</option>
214
- <option value="in_progress">En progreso</option>
215
- <option value="in_review">En revision</option>
216
- <option value="blocked">Bloqueada</option>
217
- <option value="completed">Completada</option>
218
- <option value="cancelled">Cancelada</option>
219
- </select>
220
- </div>
221
-
222
- <div class="field">
223
- <label for="taskStream">Stream</label>
224
- <input id="taskStream" name="stream" type="text" placeholder="Operations" />
225
- </div>
226
- </div>
227
-
228
- <div class="field checkbox-field">
229
- <label for="taskRequired">
230
- <input id="taskRequired" name="required" type="checkbox" checked />
231
- Tarea requerida para entrega
232
- </label>
233
- </div>
234
-
235
- <div class="field">
236
- <label for="taskSummary">Resumen</label>
237
- <textarea id="taskSummary" name="summary" rows="3"></textarea>
238
- </div>
239
-
240
- <div class="field">
241
- <label for="taskDependsOn">Dependencias</label>
242
- <textarea id="taskDependsOn" name="dependsOn" rows="2"></textarea>
243
- </div>
244
-
245
- <div class="field">
246
- <label for="taskAcceptance">Criterios de aceptacion</label>
247
- <textarea id="taskAcceptance" name="acceptance" rows="3"></textarea>
248
- </div>
249
-
250
- <div class="field">
251
- <label for="taskBlocker">Bloqueador</label>
252
- <textarea id="taskBlocker" name="blocker" rows="2"></textarea>
253
- </div>
254
-
255
- <div class="field">
256
- <label for="taskNote">Nota de actualizacion</label>
257
- <textarea id="taskNote" name="note" rows="2"></textarea>
258
- </div>
259
-
260
- <div class="form-actions">
261
- <button class="primary-button" type="submit">Guardar tarea</button>
262
- <button id="duplicateTaskButton" class="ghost-button" type="button">Duplicar base</button>
263
- </div>
264
- </form>
265
- </div>
266
- </details>
267
- </div>
268
- </section>
269
-
270
- <section id="tab-execution" class="tab-panel" hidden>
271
- <div class="split-grid">
272
- <details class="panel panel-details" open>
273
- <summary class="details-summary">
274
- <div>
275
- <p class="eyebrow">Command Deck</p>
276
- <h2>Consola integrada</h2>
277
- </div>
278
- <span id="terminalStatus" class="secondary-badge">Lista</span>
279
- </summary>
280
- <div class="details-body">
281
- <form id="commandForm" class="command-form">
282
- <label class="sr-only" for="commandInput">Comando</label>
283
- <input id="commandInput" name="command" type="text" spellcheck="false" autocomplete="off" placeholder="npm run ops:status" />
284
- <button class="primary-button" type="submit">Ejecutar</button>
285
- </form>
286
-
287
- <div id="commandPresets" class="preset-row" aria-label="Comandos rapidos"></div>
288
- <div id="sessionList" class="session-list"></div>
289
-
290
- <div class="terminal-surface">
291
- <pre id="terminalOutput">Selecciona o ejecuta un comando para ver la salida.</pre>
292
- </div>
293
- </div>
294
- </details>
295
-
296
- <details class="panel panel-details" open>
297
- <summary class="details-summary">
298
- <div>
299
- <p class="eyebrow">Execution</p>
300
- <h2>Resumen del runtime</h2>
301
- </div>
302
- </summary>
303
- <div class="details-body">
304
- <div id="executionMetrics" class="stack-list"></div>
305
- </div>
306
- </details>
307
- </div>
308
- </section>
309
-
310
- <section id="tab-insights" class="tab-panel" hidden>
311
- <div class="overview-grid">
312
- <details class="panel panel-details" open>
313
- <summary class="details-summary">
314
- <div>
315
- <p class="eyebrow">Activity</p>
316
- <h2>Actividad reciente</h2>
317
- </div>
318
- </summary>
319
- <div class="details-body">
320
- <div id="activityList" class="stack-list"></div>
321
- </div>
322
- </details>
323
-
324
- <details class="panel panel-details" open>
325
- <summary class="details-summary">
326
- <div>
327
- <p class="eyebrow">Findings</p>
328
- <h2>Hallazgos abiertos</h2>
329
- </div>
330
- </summary>
331
- <div class="details-body">
332
- <div id="findingList" class="stack-list"></div>
333
- </div>
334
- </details>
335
-
336
- <details class="panel panel-details" open>
337
- <summary class="details-summary">
338
- <div>
339
- <p class="eyebrow">Health</p>
340
- <h2>KPIs del proyecto</h2>
341
- </div>
342
- </summary>
343
- <div class="details-body">
344
- <div id="healthRail" class="stack-list"></div>
345
- </div>
346
- </details>
347
- </div>
348
- </section>
349
- </main>
350
-
351
- <div id="flash" class="flash" aria-live="polite"></div>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>TrackOps Beta Centro de control</title>
7
+ <meta name="description" content="Panel local beta de TrackOps: gestión de proyectos, tareas, analíticas y seguimiento de tiempo para desarrolladores." />
8
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
9
+
10
+ <!-- Google Fonts (coherente con web corporativa) -->
11
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Outfit:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@700;800&display=swap" rel="stylesheet" />
14
+
15
+ <!-- Anti-FOUC: aplica el tema antes del primer paint -->
16
+ <script>
17
+ (function(){
18
+ var t = localStorage.getItem('trackops-theme');
19
+ if (!t) t = 'light';
20
+ if (t === 'light') document.documentElement.setAttribute('data-theme','light');
21
+ })();
22
+ </script>
23
+
24
+ <!-- CSS Modular -->
25
+ <link rel="stylesheet" href="/css/tokens.css" />
26
+ <link rel="stylesheet" href="/css/base.css" />
27
+ <link rel="stylesheet" href="/css/components.css" />
28
+ <link rel="stylesheet" href="/css/panels.css" />
29
+ <link rel="stylesheet" href="/css/charts.css" />
30
+ </head>
31
+ <body>
32
+
33
+ <!-- Skip to content (WCAG 2.2 AA) -->
34
+ <a class="skip-link" href="#view-container">Saltar al contenido principal</a>
35
+
36
+ <!-- App shell -->
37
+ <div class="app-shell">
38
+
39
+ <!-- ══ SIDEBAR ══ -->
40
+ <aside id="sidebar" role="navigation" aria-label="Navegación principal">
41
+ <!-- Rendered by sidebar.js -->
42
+ </aside>
43
+
44
+ <!-- ══ TOPBAR ══ -->
45
+ <header id="topbar" role="banner">
46
+ <!-- Rendered by topbar.js -->
47
+ </header>
48
+
49
+ <!-- ══ MAIN CONTENT ══ -->
50
+ <main id="view-container" role="main" tabindex="-1">
51
+ <!-- Rendered by router.js / views -->
52
+ </main>
53
+
54
+ </div><!-- /.app-shell -->
55
+
56
+ <!-- Flash / Toast container (aria-live para a11y) -->
57
+ <div id="flash-container" aria-live="polite" aria-atomic="false"></div>
58
+
59
+ <!-- Panel de registros -->
60
+ <div id="console-panel" class="console-panel" role="log" aria-label="Consola de errores" aria-live="off">
61
+ <div class="console-panel-header">
62
+ <div class="console-panel-title" id="console-panel-title">
63
+ <span>·</span> Registros
64
+ <span id="console-error-count" class="badge badge-danger" style="display:none">0</span>
65
+ </div>
66
+ <div class="console-panel-actions">
67
+ <button class="btn btn-ghost btn-sm" id="console-clear-btn" type="button" aria-label="Limpiar logs">Limpiar</button>
68
+ <button class="btn btn-ghost btn-sm" id="console-close-btn" type="button" aria-label="Cerrar consola">✕</button>
69
+ </div>
70
+ </div>
71
+ <div class="console-logs" id="console-logs" role="list"></div>
72
+ </div>
73
+
74
+ <!-- Onboarding Spotlight -->
75
+ <div id="onboarding-spotlight" class="onboarding-spotlight is-hidden" aria-hidden="true"></div>
76
+
77
+ <div id="onboarding-tooltip" class="onboarding-tooltip is-hidden" role="dialog" aria-modal="true" aria-labelledby="ob-title">
78
+ <div class="ob-step-label" id="ob-step-label">Paso 1 de X</div>
79
+ <h2 class="ob-title" id="ob-title"></h2>
80
+ <p class="ob-desc" id="ob-desc"></p>
81
+
82
+ <div class="ob-nav">
83
+ <div class="ob-dots" id="ob-dots" aria-hidden="true"></div>
84
+ <div class="ob-actions">
85
+ <button class="btn btn-ghost btn-sm" id="ob-skip" type="button">Saltar</button>
86
+ <button class="btn btn-ghost btn-sm" id="ob-prev" type="button">Anterior</button>
87
+ <button class="btn btn-primary btn-sm" id="ob-next" type="button">Siguiente</button>
88
+ </div>
352
89
  </div>
90
+ </div>
91
+
92
+ <!-- JS Módulos (ES modules) -->
93
+ <script type="module" src="/js/app.js"></script>
353
94
 
354
- <script src="/app.js"></script>
355
- </body>
95
+ </body>
356
96
  </html>
package/ui/js/api.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * api.js — Capa de comunicación con el backend TrackOps
3
+ * Wrapper sobre fetch con gestión de errores, project-awareness
4
+ * y tipado de los endpoints disponibles en lib/server.js
5
+ */
6
+
7
+ import * as state from './state.js';
8
+
9
+ /**
10
+ * Llamada base a la API
11
+ * @param {string} url
12
+ * @param {RequestInit & { projectAware?: boolean }} options
13
+ * @returns {Promise<Object>}
14
+ */
15
+ async function call(url, options = {}) {
16
+ const target = new URL(url, window.location.origin);
17
+ const currentId = state.get('currentProjectId');
18
+ if (options.projectAware !== false && currentId && !target.searchParams.has('project')) {
19
+ target.searchParams.set('project', currentId);
20
+ }
21
+
22
+ const response = await fetch(target, {
23
+ ...options,
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ ...(options.headers || {}),
27
+ },
28
+ });
29
+
30
+ const text = await response.text();
31
+ const json = text ? JSON.parse(text) : {};
32
+
33
+ if (!response.ok || json.ok === false) {
34
+ const err = new Error(json.error || `HTTP ${response.status}: ${response.statusText}`);
35
+ err.status = response.status;
36
+ throw err;
37
+ }
38
+
39
+ return json;
40
+ }
41
+
42
+ // ─────────────────────────────── PROYECTOS ──────────────────────────────────
43
+
44
+ /**
45
+ * Lista todos los proyectos del portfolio
46
+ */
47
+ export async function getProjects() {
48
+ return call('/api/projects', { projectAware: false });
49
+ }
50
+
51
+ /**
52
+ * Registra un proyecto existente en el portfolio
53
+ * @param {string} root - ruta del directorio del proyecto
54
+ */
55
+ export async function registerProject(root) {
56
+ return call('/api/projects/register', {
57
+ method: 'POST',
58
+ projectAware: false,
59
+ body: JSON.stringify({ root }),
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Instala trackops en un nuevo proyecto
65
+ * @param {string} root
66
+ */
67
+ export async function installProject(root) {
68
+ return call('/api/projects/install', {
69
+ method: 'POST',
70
+ projectAware: false,
71
+ body: JSON.stringify({ root }),
72
+ });
73
+ }
74
+
75
+ // ─────────────────────────────── ESTADO ─────────────────────────────────────
76
+
77
+ /**
78
+ * Obtiene el estado completo del proyecto activo
79
+ */
80
+ export async function getState() {
81
+ return call('/api/state');
82
+ }
83
+
84
+ // ─────────────────────────────── TAREAS ─────────────────────────────────────
85
+
86
+ /**
87
+ * Crea una nueva tarea
88
+ * @param {Object} payload
89
+ */
90
+ export async function createTask(payload) {
91
+ return call('/api/tasks', {
92
+ method: 'POST',
93
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), ...payload }),
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Actualiza una tarea existente (edición completa)
99
+ * @param {string} taskId
100
+ * @param {Object} payload
101
+ */
102
+ export async function updateTask(taskId, payload) {
103
+ return call(`/api/tasks/${encodeURIComponent(taskId)}`, {
104
+ method: 'PUT',
105
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), ...payload }),
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Ejecuta una acción sobre una tarea (start, review, complete, block, pending, cancel)
111
+ * @param {string} taskId
112
+ * @param {string} action
113
+ * @param {string} [note]
114
+ */
115
+ export async function taskAction(taskId, action, note = '') {
116
+ return call(`/api/tasks/${encodeURIComponent(taskId)}/action`, {
117
+ method: 'POST',
118
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), action, note }),
119
+ });
120
+ }
121
+
122
+ // ─────────────────────────────── SYNC ───────────────────────────────────────
123
+
124
+ /**
125
+ * Sincroniza los docs del proyecto (task_plan.md, progress.md, findings.md)
126
+ */
127
+ export async function syncDocs() {
128
+ return call('/api/sync', {
129
+ method: 'POST',
130
+ body: JSON.stringify({ projectId: state.get('currentProjectId') }),
131
+ });
132
+ }
133
+
134
+ // ─────────────────────────────── COMANDOS ───────────────────────────────────
135
+
136
+ /**
137
+ * Ejecuta un comando en el shell del proyecto
138
+ * @param {string} command
139
+ */
140
+ export async function runCommand(command) {
141
+ return call('/api/commands', {
142
+ method: 'POST',
143
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), command }),
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Crea un EventSource para hacer streaming de salida de una sesión
149
+ * @param {string} sessionId
150
+ * @returns {EventSource}
151
+ */
152
+ export function streamSession(sessionId) {
153
+ return new EventSource(`/api/commands/${encodeURIComponent(sessionId)}/stream`);
154
+ }
155
+
156
+ // ─────────────────────────────── TIME TRACKING ──────────────────────────────
157
+
158
+ /**
159
+ * Inicia un time entry para una tarea
160
+ * @param {string} taskId
161
+ * @param {string} taskTitle
162
+ */
163
+ export async function startTimeEntry(taskId, taskTitle) {
164
+ return call('/api/time/start', {
165
+ method: 'POST',
166
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), taskId, taskTitle }),
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Detiene el time entry activo
172
+ * @param {string} entryId
173
+ */
174
+ export async function stopTimeEntry(entryId) {
175
+ return call('/api/time/stop', {
176
+ method: 'POST',
177
+ body: JSON.stringify({ projectId: state.get('currentProjectId'), entryId }),
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Obtiene los time entries del proyecto activo
183
+ */
184
+ export async function getTimeEntries() {
185
+ return call('/api/time');
186
+ }
187
+
188
+ // ─────────────────────────────── SKILLS HUB ────────────────────────────────
189
+
190
+ export async function fetchSkillsLocal() {
191
+ return call('/api/skills/local');
192
+ }
193
+
194
+ export async function fetchSkillsDiscover() {
195
+ return call('/api/skills/discover');
196
+ }
197
+
198
+ export async function installSkill(skillId) {
199
+ return call('/api/skills/install', {
200
+ method: 'POST',
201
+ body: JSON.stringify({ skillId })
202
+ });
203
+ }