@saulwade/swl-ses 2.5.1 → 2.5.3

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
- "generatedAt": "2026-07-09T05:48:39.413Z",
3
+ "generatedAt": "2026-07-09T21:51:05.079Z",
4
4
  "skillsCount": 181,
5
- "lockHash": "sha256:60e8d0c732526f1329974a4bae2894a493e92a6b506ce192ca7cd5b04f2609da",
5
+ "lockHash": "sha256:016f7b1362443918147698bca6428463dcc70770d3acb61b84bb65267b195cf4",
6
6
  "skills": [
7
7
  {
8
8
  "nombre": "accesibilidad-a11y",
@@ -602,9 +602,9 @@
602
602
  {
603
603
  "nombre": "harness-claude-code",
604
604
  "path": "habilidades/harness-claude-code/SKILL.md",
605
- "hash": "sha256:90a45cb0d932ccd38bbb2fc09ef86149d640dd073ab6d21a841db33a6baf41db",
606
- "bytes": 17714,
607
- "version": "\"1.0.4\""
605
+ "hash": "sha256:d4c7d4283a35fb5d1dcfcf18d2c0f45dca012bbc518fa2f5df8c3a88517069b0",
606
+ "bytes": 18370,
607
+ "version": "\"1.0.5\""
608
608
  },
609
609
  {
610
610
  "nombre": "iam-secretos",
@@ -1043,9 +1043,9 @@
1043
1043
  {
1044
1044
  "nombre": "release-semver",
1045
1045
  "path": "habilidades/release-semver/SKILL.md",
1046
- "hash": "sha256:37d4ef6228c83dfc5333188c85c7e910949d3bdcd3f49a5a2b8dd21a4835539d",
1047
- "bytes": 17761,
1048
- "version": "\"1.0.3\""
1046
+ "hash": "sha256:c77cc73cc1f6ef635f9f79d9d008c5ee05f5de557b5f94ffa85b67575b2e9a33",
1047
+ "bytes": 18514,
1048
+ "version": "\"1.0.4\""
1049
1049
  },
1050
1050
  {
1051
1051
  "nombre": "rust-experto",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saulwade/swl-ses",
3
- "version": "2.5.1",
3
+ "version": "2.5.3",
4
4
  "description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 61 agentes, 181 habilidades, 47 comandos, 77 reglas y 49 hooks. Soporta 11 lenguajes y 7 runtimes: Claude Code, OpenClaude, OpenCode, Gemini CLI, Cursor, Codex CLI (soporte completo); GitHub Copilot (soporte parcial). 100% en espanol (Mexico). Multi-target install (--target CSV / --all-runtimes), autoconfig MCP en Cursor/Codex con --with-mcp, agentes Codex en TOML, hooks Cursor (17 eventos) y Codex (6 eventos). Gateway bidireccional con relay Telegram y auditoria profunda Nemesis con loop evaluator-optimizer opt-in (ADR-0021) y 8 tools ejecutables. v1.8.0 unifica los directorios runtime de .planning/ al ingles (evolution/, auto-evolution/, user-profile/, archive/), manteniendo fases/ en espanol, con guard validar-planning-paths y allowlist canonica (ADR-0031). Hereda de v1.7.4: skill calidad-anti-patrones-universales + scripts/lib/pr-analyzer.js + 3 sub-secciones en estilo-sin-ai-isms.",
5
5
  "bin": {
6
6
  "swl-ses": "bin/swl-ses.js",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swl-ses",
3
- "version": "2.5.1",
3
+ "version": "2.5.3",
4
4
  "description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 61 agentes, 181 habilidades, 47 comandos, 77 reglas y 49 hooks. 76 librerias. 11 lenguajes. Soporta Claude Code, Copilot, OpenCode, Codex y Gemini CLI. Loop evaluator-optimizer en /swl:nemesis (ADR-0021). v1.8.0 unifica los directorios runtime de .planning/ al ingles (evolution/, auto-evolution/, user-profile/, archive/), manteniendo fases/ en espanol, con guard validar-planning-paths y allowlist canonica (ADR-0031). Hereda de v1.7.4: skill calidad-anti-patrones-universales + scripts/lib/pr-analyzer.js + 3 sub-secciones en estilo-sin-ai-isms.",
5
5
  "author": "Saul Wade Leon",
6
6
  "license": "MIT",
@@ -18,11 +18,14 @@
18
18
 
19
19
  const { panelEvidencia } = require('./lib/evidencia-valor.js');
20
20
 
21
- function main() {
22
- const argv = process.argv.slice(2);
23
- const json = argv.includes('--json');
24
- const idx = argv.indexOf('--dias');
25
- const ventanaDias = idx !== -1 ? parseInt(argv[idx + 1], 10) || 30 : 30;
21
+ /**
22
+ * Contrato del CLI (bin/swl-ses.js): exportar UNA función(opciones) sin
23
+ * efectos al require. Violarlo = "modulo is not a function" tras imprimir
24
+ * el panel (caso real: SIGM, 2026-07-09).
25
+ */
26
+ function valor(opciones = {}) {
27
+ const json = !!opciones.json;
28
+ const ventanaDias = parseInt(opciones.dias, 10) || 30;
26
29
 
27
30
  const p = panelEvidencia(process.cwd(), { ventanaDias });
28
31
 
@@ -90,4 +93,9 @@ function main() {
90
93
  console.log('Compartir agregados con el autor (opt-in): swl-ses field-report — ver docs/evidencia-valor.md');
91
94
  }
92
95
 
93
- main();
96
+ module.exports = valor;
97
+
98
+ if (require.main === module) {
99
+ const { parsearOpciones } = require('./lib/parsear-opciones.js');
100
+ valor(parsearOpciones(process.argv.slice(2)));
101
+ }
@@ -196,4 +196,20 @@ function generarReporte() {
196
196
  console.log(`\n> Generado por \`node scripts/field-report.js\``);
197
197
  }
198
198
 
199
- generarReporte();
199
+ function fieldReport(opciones = {}) {
200
+ DAYS = parseInt(opciones.days || opciones.dias, 10) || 0;
201
+ PROJECT = opciones.project || null;
202
+ OUTPUT = opciones.output || 'md';
203
+ generarReporte();
204
+ }
205
+
206
+ module.exports = fieldReport;
207
+
208
+ if (require.main === module) {
209
+ const args = process.argv.slice(2).reduce((acc, arg) => {
210
+ const [k, v] = arg.replace(/^--/, '').split('=');
211
+ acc[k] = v || true;
212
+ return acc;
213
+ }, {});
214
+ fieldReport(args);
215
+ }
@@ -84,6 +84,27 @@ function nombreLegibleEvolucion(rutaAbs) {
84
84
  * El comportamiento sin TUI no cambia.
85
85
  */
86
86
  async function install(opciones) {
87
+ // Camino "solo hooks" (DT-HOOKS-SOLO-PROYECTO): registra en el settings.json
88
+ // del PROYECTO los hooks del scope GLOBAL ya instalado, sin copiar ningún
89
+ // componente — evita duplicar agentes/comandos en la UI de Claude Code
90
+ // (que lee ambos scopes) solo para encender la telemetría.
91
+ if (opciones['solo-hooks'] || opciones.solo_hooks) {
92
+ const { activarHooksProyecto } = require('./lib/activar-hooks-proyecto');
93
+ const r = activarHooksProyecto({ target: opciones.target || 'claude' });
94
+ if (!r.ok) {
95
+ console.error(`\n✘ --solo-hooks: ${r.error}`);
96
+ process.exitCode = 1;
97
+ return r;
98
+ }
99
+ console.log('\n✔ Hooks activados a nivel proyecto SIN copiar componentes:');
100
+ console.log(` settings: ${r.settingsPath}`);
101
+ console.log(` hooks de: ${r.hooksDirGlobal} (scope global — update global los refresca aquí también)`);
102
+ console.log(` registrados: ${r.registrados} | externos preservados: ${r.preservados}`);
103
+ console.log('\n La telemetría (.planning/evolution/nudges.jsonl, skill-metrics.json)');
104
+ console.log(' empieza a acumularse desde la siguiente sesión de Claude en este proyecto.');
105
+ return r;
106
+ }
107
+
87
108
  // Leer manifest .swl-ses si existe — sus valores son defaults que el CLI puede sobreescribir
88
109
  const manifest = leerManifest(process.cwd());
89
110
  if (manifest && !opciones.target && !opciones.profile) {
@@ -119,4 +119,11 @@ function main() {
119
119
 
120
120
  if (require.main === module) main();
121
121
 
122
- module.exports = { instalar, MARCADOR, LINEA_CHECK };
122
+ // Contrato del CLI (bin/swl-ses.js): el export default es UNA función que el
123
+ // bin invoca como modulo(opciones); main re-lee process.argv, que en la
124
+ // invocación vía bin ya trae los flags. Los named exports se preservan como
125
+ // propiedades para los tests existentes.
126
+ module.exports = main;
127
+ module.exports.instalar = instalar;
128
+ module.exports.MARCADOR = MARCADOR;
129
+ module.exports.LINEA_CHECK = LINEA_CHECK;
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * activar-hooks-proyecto.js — Camino "solo hooks" (DT-HOOKS-SOLO-PROYECTO).
5
+ *
6
+ * Registra en el `.claude/settings.json` del PROYECTO los hooks del scope
7
+ * GLOBAL ya instalado, sin copiar ningún componente. Cierra el dilema del
8
+ * install local: los hooks solo funcionan registrados a nivel proyecto
9
+ * (ADR-0009), pero el install local completo duplica agentes/comandos en la
10
+ * UI de Claude Code (lee ambos scopes). Con este camino:
11
+ *
12
+ * - Los comandos del settings apuntan con ruta ABSOLUTA al hooks/ global
13
+ * (esGlobal: true en registrarHooks) → cero archivos copiados.
14
+ * - `swl-ses update` del global refresca los hooks de TODOS los proyectos
15
+ * activados así, gratis (apuntan a los mismos archivos).
16
+ * - Hooks externos (no-SWL) del settings del proyecto se preservan
17
+ * (garantía de registrarHooks).
18
+ *
19
+ * Uso: `swl-ses install --target claude --solo-hooks` (en la raíz del
20
+ * proyecto). Requiere instalación global previa del runtime.
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ const RUNTIMES_SOPORTADOS = new Set(['claude', 'openclaude']);
27
+
28
+ /**
29
+ * @param {object} opciones
30
+ * @param {string} [opciones.target='claude'] Runtime (claude|openclaude — formato de settings de Claude Code)
31
+ * @param {string} [opciones.dirProyecto=cwd] Raíz del proyecto destino
32
+ * @param {string} [opciones.hooksDirGlobal] Override del hooks/ global (tests)
33
+ * @param {object} [opciones.deps] Inyección para tests: { registrarHooks, registrarOptimizaciones, cargarHooksConfig, scopesReales, obtenerRuntime }
34
+ * @returns {{ok: boolean, error?: string, settingsPath?: string, hooksDirGlobal?: string, registrados?: number, preservados?: number}}
35
+ */
36
+ function activarHooksProyecto(opciones = {}) {
37
+ const target = opciones.target || 'claude';
38
+ if (!RUNTIMES_SOPORTADOS.has(target)) {
39
+ return { ok: false, error: `--solo-hooks soporta por ahora targets con settings formato Claude Code (claude, openclaude); recibido: "${target}".` };
40
+ }
41
+
42
+ const deps = opciones.deps || {};
43
+ const hs = require('./hooks-settings');
44
+ const registrarHooks = deps.registrarHooks || hs.registrarHooks;
45
+ const registrarOptimizaciones = deps.registrarOptimizaciones || hs.registrarOptimizaciones;
46
+ const cargarHooksConfig = deps.cargarHooksConfig || hs.cargarHooksConfig;
47
+
48
+ // 1. Localizar el hooks/ del scope GLOBAL del runtime.
49
+ let hooksDirGlobal = opciones.hooksDirGlobal;
50
+ if (!hooksDirGlobal) {
51
+ try {
52
+ const dr = require('./detectar-runtime');
53
+ const obtenerRuntime = deps.obtenerRuntime || dr.obtenerRuntime;
54
+ const scopesReales = deps.scopesReales || dr.scopesReales;
55
+ const runtime = obtenerRuntime(target);
56
+ const global = scopesReales(runtime).find(s => s.esGlobal);
57
+ if (!global) return { ok: false, error: `No se pudo resolver el scope global del runtime "${target}".` };
58
+ hooksDirGlobal = path.join(global.dir, 'hooks');
59
+ } catch (err) {
60
+ return { ok: false, error: `No se pudo detectar el runtime "${target}": ${err.message}` };
61
+ }
62
+ }
63
+ if (!fs.existsSync(hooksDirGlobal)) {
64
+ return { ok: false, error: `No hay instalación global con hooks en ${hooksDirGlobal}. Instala primero el global: npx -y @saulwade/swl-ses@latest install --target ${target} --global --perfil completo` };
65
+ }
66
+
67
+ // 2. Hooks disponibles = archivos del global ∩ hooks-config del paquete.
68
+ const hooksConfig = cargarHooksConfig();
69
+ const hookFiles = fs.readdirSync(hooksDirGlobal)
70
+ .filter(f => f.endsWith('.js') && hooksConfig[f]);
71
+ if (hookFiles.length === 0) {
72
+ return { ok: false, error: `El hooks/ global (${hooksDirGlobal}) no contiene hooks reconocidos por hooks-config.json — instalación global corrupta o muy antigua; corre update primero.` };
73
+ }
74
+
75
+ // 3. Registrar en el settings.json del PROYECTO, apuntando al global.
76
+ const dirProyecto = opciones.dirProyecto || process.cwd();
77
+ const dirClaude = path.join(dirProyecto, '.claude');
78
+ fs.mkdirSync(dirClaude, { recursive: true });
79
+ const settingsPath = path.join(dirClaude, 'settings.json');
80
+
81
+ const r = registrarHooks({
82
+ settingsPath,
83
+ hooksDir: hooksDirGlobal,
84
+ esGlobal: true, // semántica: comandos con ruta ABSOLUTA al hooksDir (que es el global)
85
+ hookFiles,
86
+ });
87
+
88
+ let optimizaciones = 0;
89
+ try {
90
+ const opt = registrarOptimizaciones(settingsPath);
91
+ optimizaciones = (opt && opt.registradas) || 0;
92
+ } catch { /* opcional — no bloquea la activación */ }
93
+
94
+ return {
95
+ ok: true,
96
+ settingsPath,
97
+ hooksDirGlobal,
98
+ registrados: r.registrados,
99
+ preservados: r.preservados,
100
+ optimizaciones,
101
+ };
102
+ }
103
+
104
+ module.exports = { activarHooksProyecto, RUNTIMES_SOPORTADOS };
@@ -44,9 +44,31 @@ const TIPOS_INTERVENCION = new Set([
44
44
 
45
45
  // ─── 1. Intercepciones (nudges.jsonl) ────────────────────────────────────────
46
46
 
47
+ /**
48
+ * Diagnóstico de POR QUÉ no hay telemetría: los hooks solo se registran a
49
+ * nivel PROYECTO (ADR-0009 — un install --global copia los archivos pero NO
50
+ * los registra en ~/.claude/settings.json, para no correr 49 hooks en cada
51
+ * sesión de cualquier proyecto). Sin registro local, nudges.jsonl y
52
+ * skill-metrics.json jamás aparecerán — el panel debe decir CÓMO activarlo,
53
+ * no solo "no disponible" (reporte de campo SIGM, 2026-07-09).
54
+ */
55
+ function hooksRegistradosEnProyecto(raiz) {
56
+ try {
57
+ const s = JSON.parse(fs.readFileSync(path.join(raiz, '.claude', 'settings.json'), 'utf8'));
58
+ return Object.values(s.hooks || {}).some(m => Array.isArray(m) && m.length > 0);
59
+ } catch { return false; }
60
+ }
61
+
62
+ const HINT_ACTIVACION = 'hooks sin registrar a nivel proyecto (el install --global no los registra por diseño, ADR-0009) — activar SIN duplicar componentes con: npx -y @saulwade/swl-ses@latest install --target claude --solo-hooks (en la raíz del proyecto)';
63
+
47
64
  function leerNudges(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
48
65
  const ruta = path.join(raiz, '.planning', 'evolution', 'nudges.jsonl');
49
- if (!fs.existsSync(ruta)) return { disponible: false, razon: 'sin nudges.jsonl (proyecto sin actividad de hooks o telemetría desactivada)', eventos: [] };
66
+ if (!fs.existsSync(ruta)) {
67
+ const razon = hooksRegistradosEnProyecto(raiz)
68
+ ? 'sin nudges.jsonl aún (hooks registrados; se acumula con el uso de sesiones)'
69
+ : HINT_ACTIVACION;
70
+ return { disponible: false, razon, eventos: [] };
71
+ }
50
72
  const corte = hoy.getTime() - ventanaDias * DIA_MS;
51
73
  const eventos = [];
52
74
  for (const linea of fs.readFileSync(ruta, 'utf8').split(/\r?\n/)) {
@@ -102,6 +124,10 @@ function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecu
102
124
 
103
125
  // Escape aproximado: fixes con fecha POSTERIOR a la última verificación de
104
126
  // fase (VERIFICACION.md más reciente). Sin fases verificadas → no aplica.
127
+ // La fecha se toma de GIT (autoría del último commit que tocó el archivo),
128
+ // no del mtime del filesystem: tras un clone/checkout todos los archivos
129
+ // comparten el mtime de la operación y el proxy daba falso negativo
130
+ // (reporte de campo SIGM, 2026-07-09). Fallback a mtime fuera de git.
105
131
  let escapes = { disponible: false, razon: 'sin VERIFICACION.md en .planning/fases/ (proyecto sin ciclo GSD verificado)' };
106
132
  try {
107
133
  const dirFases = path.join(raiz, '.planning', 'fases');
@@ -109,7 +135,15 @@ function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecu
109
135
  if (fs.existsSync(dirFases)) {
110
136
  for (const fase of fs.readdirSync(dirFases)) {
111
137
  const v = path.join(dirFases, fase, 'VERIFICACION.md');
112
- if (fs.existsSync(v)) ultimaVerificacion = Math.max(ultimaVerificacion, fs.statSync(v).mtimeMs);
138
+ if (!fs.existsSync(v)) continue;
139
+ let fecha = 0;
140
+ try {
141
+ const rel = path.relative(raiz, v);
142
+ const ct = parseInt(git(raiz, ['log', '-1', '--format=%ct', '--', rel]).trim(), 10);
143
+ if (Number.isFinite(ct) && ct > 0) fecha = ct * 1000;
144
+ } catch { /* fuera de git o archivo sin commits → fallback mtime */ }
145
+ if (fecha === 0) fecha = fs.statSync(v).mtimeMs;
146
+ ultimaVerificacion = Math.max(ultimaVerificacion, fecha);
113
147
  }
114
148
  }
115
149
  if (ultimaVerificacion > 0) {
@@ -152,7 +186,12 @@ function doraCi(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
152
186
 
153
187
  function usoSistema(raiz) {
154
188
  const ruta = path.join(raiz, '.planning', 'skill-metrics.json');
155
- if (!fs.existsSync(ruta)) return { disponible: false, razon: 'sin skill-metrics.json (telemetría de routing sin datos)' };
189
+ if (!fs.existsSync(ruta)) {
190
+ const razon = hooksRegistradosEnProyecto(raiz)
191
+ ? 'sin skill-metrics.json aún (hooks registrados; se acumula con el uso de skills)'
192
+ : HINT_ACTIVACION;
193
+ return { disponible: false, razon };
194
+ }
156
195
  try {
157
196
  // Forma real: { version, entries: [{skill, estado, timestamp}, ...] }
158
197
  // (eventos individuales del hook telemetria-skill-routing) — agregamos aquí.
@@ -118,7 +118,16 @@ async function _flujoInstall() {
118
118
  }
119
119
  return { ok: false };
120
120
  }
121
- return ejecutarInstall(w.opciones);
121
+ // Multi-runtime: una corrida del instalador por runtime marcado en el paso 1.
122
+ const targets = w.opciones.targets && w.opciones.targets.length > 0
123
+ ? w.opciones.targets
124
+ : [w.opciones.target];
125
+ let ultimo = { ok: true };
126
+ for (const target of targets) {
127
+ ultimo = await ejecutarInstall({ ...w.opciones, target });
128
+ if (!ultimo.ok) return ultimo; // reporta el runtime que falló, sin continuar a ciegas
129
+ }
130
+ return ultimo;
122
131
  }
123
132
 
124
133
  async function _flujoUpdate() {
@@ -23,7 +23,7 @@ const teclas = require('../lib/teclas');
23
23
  const { colores, semantico, iconos } = require('../lib/colores');
24
24
 
25
25
  function _cargarDatosInstalaciones() {
26
- let detectarRuntimes, cargarEstado;
26
+ let detectarRuntimes, scopesReales, cargarEstado;
27
27
  try {
28
28
  ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
29
29
  ({ cargarEstado } = require('../../lib/estado'));
@@ -170,7 +170,7 @@ function pantallaPreview(opciones, perfiles) {
170
170
  render.escribirEn(3, 3, colores.dim('Revisa antes de confirmar:'));
171
171
 
172
172
  const filas = [
173
- ['Runtime', opciones.target],
173
+ ['Runtime(s)', (opciones.targets || [opciones.target]).join(', ')],
174
174
  ['Perfil', opciones.profile],
175
175
  ['Scope', opciones.global ? 'global (~/.claude)' : 'proyecto (./.claude)'],
176
176
  ['With MCP', opciones.with_mcp ? 'sí' : 'no'],
@@ -243,13 +243,25 @@ async function ejecutarWizardInstall() {
243
243
  return { exito: false, error: 'No se pudo cargar manifiestos/perfiles.json' };
244
244
  }
245
245
 
246
- // 1. Runtime
247
- const target = await selectorUnico({
248
- titulo: 'Paso 1/6 ¿En qué runtime instalas SWL?',
249
- items: RUNTIMES,
250
- indiceDefault: 0,
251
- });
252
- if (!target) return { exito: false, cancelado: true };
246
+ // 1. Runtime(s) — multiselección: instala en varios runtimes en una corrida
247
+ // (fix 2026-07-09: era selector único; el flujo multi-runtime obligaba a
248
+ // repetir el wizard completo por cada runtime).
249
+ let targets = null;
250
+ let tituloPaso1 = 'Paso 1/6 — ¿En qué runtime(s) instalas SWL? (espacio para marcar)';
251
+ for (;;) {
252
+ targets = await selectorMulti({ titulo: tituloPaso1, items: RUNTIMES });
253
+ if (targets === null) return { exito: false, cancelado: true };
254
+ if (targets.length > 0) break;
255
+ // no-TTY: selectorMulti resuelve [] al instante — re-preguntar sería un
256
+ // spin infinito en caliente (colgó la suite 2.7h el 2026-07-09). Default:
257
+ // primer runtime, igual que hacía el selector único.
258
+ if (!render.ES_TTY || !process.stdin.isTTY) {
259
+ targets = [RUNTIMES[0].valor];
260
+ break;
261
+ }
262
+ tituloPaso1 = 'Paso 1/6 — Marca AL MENOS un runtime con espacio (Esc para cancelar)';
263
+ }
264
+ const target = targets[0]; // compat: preview/ejecutores leen .target
253
265
 
254
266
  // 2. Perfil
255
267
  const profile = await selectorUnico({
@@ -287,6 +299,7 @@ async function ejecutarWizardInstall() {
287
299
 
288
300
  const opciones = {
289
301
  target,
302
+ targets,
290
303
  profile,
291
304
  global: scope === 'global',
292
305
  with_mcp: flagsSeleccionados.includes('with-mcp'),
@@ -21,7 +21,7 @@ const teclas = require('../lib/teclas');
21
21
  const { colores, semantico, iconos } = require('../lib/colores');
22
22
 
23
23
  function _cargarInstalaciones() {
24
- let detectarRuntimes, cargarEstado;
24
+ let detectarRuntimes, scopesReales, cargarEstado;
25
25
  try {
26
26
  ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
27
27
  ({ cargarEstado } = require('../../lib/estado'));
@@ -40,7 +40,7 @@ const CATEGORIAS = [
40
40
  // ---------------------------------------------------------------------------
41
41
 
42
42
  function _detectarInstalaciones() {
43
- let detectarRuntimes, cargarEstado;
43
+ let detectarRuntimes, scopesReales, cargarEstado;
44
44
  try {
45
45
  ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
46
46
  ({ cargarEstado } = require('../../lib/estado'));
@@ -33,8 +33,7 @@ const SUBTITULO = 'Sistema de ingeniería de software auto-evolutivo multi-runti
33
33
  * No falla si la lib no está disponible — devuelve array vacío.
34
34
  */
35
35
  function detectarInstalaciones() {
36
- let detectarRuntimes;
37
- let cargarEstado;
36
+ let detectarRuntimes, scopesReales, cargarEstado;
38
37
  try {
39
38
  ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
40
39
  ({ cargarEstado } = require('../../lib/estado'));