jerkjs 2.5.0 → 2.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v2.5.2 - 7 de febrero de 2026
4
+
5
+ ### Fixed
6
+ - **Prioridad de enrutamiento corregida**: Se resolvió un problema crítico donde las rutas estáticas (prefijos) tenían prioridad sobre las rutas parametrizadas, lo que impedía que rutas como `/api/products/:id` funcionaran correctamente cuando existía una ruta estática como `/api`. El nuevo orden de prioridad es: 1) Rutas exactas → 2) Rutas parametrizadas → 3) Rutas estáticas.
7
+ - **Funcionamiento de rutas parametrizadas en modo directorio**: Se corrigió el problema donde las rutas parametrizadas no funcionaban correctamente cuando se utilizaba el modo de carga de rutas desde directorio (RouteDirectoryLoader), afectando especialmente el sistema de colas de Qwen.
8
+ - **Separación de responsabilidades en enrutamiento**: Se movió toda la lógica de enrutamiento a un componente especializado `RouteMatcher.js`, eliminando la duplicación de lógica entre `server.js` y `routeLoader.js`.
9
+
10
+ ### Changed
11
+ - **Arquitectura de enrutamiento mejorada**: Se implementó un componente especializado `RouteMatcher.js` para encapsular toda la lógica de enrutamiento, siguiendo principios de separación de responsabilidades y mejorando la mantenibilidad del código.
12
+ - **Compatibilidad mantenida**: Se mantuvo la compatibilidad hacia atrás con el modo archivo único, asegurando que no se introdujeran regresiones en funcionalidades existentes.
13
+
14
+ ## v2.5.1 - 7 de febrero de 2026
15
+
16
+ ### Fixed
17
+ - **Manejo de directorios en rutas estáticas**: Se corrigió un error crítico donde el sistema de rutas estáticas intentaba leer directorios como si fueran archivos, causando el error `EISDIR: illegal operation on a directory, read`. La solución implementa una verificación explícita para determinar si la ruta es un directorio y en tal caso busca el archivo índice correspondiente en lugar de intentar leerlo como archivo.
18
+
19
+ ### Changed
20
+ - **Mejora en el sistema de logging de errores**: Se actualizó el sistema de logging para registrar correctamente los detalles de los errores internos en el nivel adecuado, facilitando la depuración sin comprometer la seguridad
21
+
3
22
  ## v2.5.0 - 6 de febrero de 2026
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # JERK Framework v2.5.0
1
+ # JERK Framework v2.5.1
2
2
 
3
3
  ![JERK Framework Logo](jerk.webp)
4
4
 
@@ -106,6 +106,11 @@ server.addRoute('GET', '/assets', {
106
106
  });
107
107
  ```
108
108
 
109
+ ## Novedades en v2.5.1
110
+
111
+ ### Corrección de rutas estáticas
112
+ - **Solución de error crítico con directorios**: Se corrigió un error donde el sistema de rutas estáticas intentaba leer directorios como si fueran archivos, causando el error `EISDIR: illegal operation on a directory, read`. Ahora el sistema verifica explícitamente si una ruta es un directorio y en tal caso busca el archivo índice correspondiente.
113
+
109
114
  ## Carga de Rutas desde Múltiples Archivos (v2.5.0)
110
115
 
111
116
  Desde la versión 2.5.0, JERK Framework incluye el componente `RouteDirectoryLoader` que permite cargar rutas desde múltiples archivos JSON ubicados en un directorio específico. Esta funcionalidad mejora la organización y mantenibilidad de aplicaciones grandes al permitir dividir las rutas en varios archivos en lugar de tener un único archivo `routes.json`.
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Punto de entrada del framework JERK
3
- * JERK Framework 2.5.0
3
+ * JERK Framework 2.5.1
4
4
  */
5
5
 
6
6
  // Mostrar mensaje de versión al iniciar
@@ -464,10 +464,10 @@ class SecurityEnhancedServer {
464
464
  // Disparar hook después de procesar la solicitud (pero antes de next)
465
465
  this.hooks.doAction('post_request_processing', req, res);
466
466
  } catch (error) {
467
- this.logger.error('Error en middleware de seguridad:', error.message);
467
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
468
+ const { ErrorHandler } = require('../utils/errorHandler');
468
469
  if (!res.headersSent) {
469
- res.writeHead(500, { 'Content-Type': 'application/json' });
470
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
470
+ ErrorHandler.handle(error, req, res, this.logger);
471
471
  }
472
472
  }
473
473
  };
@@ -12,6 +12,7 @@ const path = require('path');
12
12
  const { Logger } = require('../utils/logger');
13
13
  const { ErrorHandler } = require('../utils/errorHandler');
14
14
  const { getMimeType } = require('../utils/mimeType');
15
+ const RouteMatcher = require('../router/RouteMatcher');
15
16
 
16
17
  class APIServer {
17
18
  /**
@@ -47,8 +48,8 @@ class APIServer {
47
48
  this.logger = new Logger();
48
49
  this.server = null;
49
50
 
50
- // Cache de expresiones regulares para rutas parametrizadas
51
- this.routeRegexCache = new Map();
51
+ // Inicializar el componente de enrutamiento
52
+ this.routeMatcher = new RouteMatcher();
52
53
  }
53
54
 
54
55
  /**
@@ -117,15 +118,17 @@ class APIServer {
117
118
  const physicalPath = path.join(baseDir, normalizedPath);
118
119
 
119
120
  // Verificar si el archivo existe
121
+ let stats;
120
122
  try {
121
123
  await fs.promises.access(physicalPath);
124
+ stats = await fs.promises.stat(physicalPath);
122
125
  } catch (accessErr) {
123
126
  // Si no existe el archivo solicitado, verificar si es un directorio y buscar archivo índice
124
127
  const dirPath = path.join(baseDir, normalizedPath);
125
128
  try {
126
- const stats = await fs.promises.stat(dirPath);
129
+ const dirStats = await fs.promises.stat(dirPath);
127
130
 
128
- if (stats.isDirectory()) {
131
+ if (dirStats.isDirectory()) {
129
132
  // Buscar archivo índice
130
133
  for (const indexFile of staticConfig.index || ['index.html']) {
131
134
  const indexPath = path.join(dirPath, indexFile);
@@ -188,6 +191,51 @@ class APIServer {
188
191
  }
189
192
  }
190
193
 
194
+ // Si la ruta existe pero es un directorio, buscar archivo índice
195
+ if (stats && stats.isDirectory()) {
196
+ const dirPath = physicalPath;
197
+ // Buscar archivo índice
198
+ for (const indexFile of staticConfig.index || ['index.html']) {
199
+ const indexPath = path.join(dirPath, indexFile);
200
+ try {
201
+ await fs.promises.access(indexPath);
202
+ // Disparar hook antes de servir archivo índice
203
+ if (hooks) {
204
+ hooks.doAction('serving_index_file', indexPath, req, res);
205
+ }
206
+
207
+ const fileContent = await fs.promises.readFile(indexPath);
208
+ const mimeType = getMimeType(indexPath);
209
+
210
+ res.setHeader('Content-Type', mimeType);
211
+
212
+ if (staticConfig.cacheControl) {
213
+ res.setHeader('Cache-Control', staticConfig.cacheControl);
214
+ }
215
+
216
+ res.writeHead(200);
217
+ res.end(fileContent);
218
+
219
+ // Disparar hook después de servir archivo
220
+ if (hooks) {
221
+ hooks.doAction('static_file_served', indexPath, req, res);
222
+ }
223
+ return;
224
+ } catch (e) {
225
+ continue; // Probar siguiente archivo índice
226
+ }
227
+ }
228
+
229
+ // Si es directorio pero no hay archivo índice
230
+ res.writeHead(404, { 'Content-Type': 'application/json' });
231
+ res.end(JSON.stringify({ error: 'Directorio no encontrado' }));
232
+
233
+ if (hooks) {
234
+ hooks.doAction('static_directory_no_index', dirPath, req, res);
235
+ }
236
+ return;
237
+ }
238
+
191
239
  // Disparar hook antes de leer archivo
192
240
  if (hooks) {
193
241
  hooks.doAction('before_reading_static_file', physicalPath, req, res);
@@ -231,8 +279,9 @@ class APIServer {
231
279
  hooks.doAction('static_file_access_denied', error.path, req, res);
232
280
  }
233
281
  } else {
234
- res.writeHead(500, { 'Content-Type': 'application/json' });
235
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
282
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
283
+ const { ErrorHandler } = require('../utils/errorHandler');
284
+ ErrorHandler.handle(error, req, res, this.logger);
236
285
 
237
286
  const hooks = require('../../index.js').hooks;
238
287
  if (hooks) {
@@ -375,10 +424,10 @@ class APIServer {
375
424
  }
376
425
  }
377
426
  } catch (error) {
378
- console.error('Error en el manejo de autenticación de sesión:', error);
427
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
428
+ const { ErrorHandler } = require('../utils/errorHandler');
379
429
  if (!res.headersSent) {
380
- res.writeHead(500, { 'Content-Type': 'application/json' });
381
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
430
+ ErrorHandler.handle(error, req, res, this.logger);
382
431
  }
383
432
  }
384
433
  };
@@ -426,10 +475,10 @@ class APIServer {
426
475
  }
427
476
  }
428
477
  } catch (error) {
429
- console.error('Error en el manejo de autenticación:', error);
478
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
479
+ const { ErrorHandler } = require('../utils/errorHandler');
430
480
  if (!res.headersSent) {
431
- res.writeHead(500, { 'Content-Type': 'application/json' });
432
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
481
+ ErrorHandler.handle(error, req, res, this.logger);
433
482
  }
434
483
  }
435
484
  };
@@ -474,117 +523,7 @@ class APIServer {
474
523
  * @returns {Object|null} - Objeto de ruta encontrado o null
475
524
  */
476
525
  findRoute(method, pathname) {
477
- // Buscar ruta exacta primero
478
- const exactMatch = this.routes.find(route =>
479
- route.method === method && route.path === pathname
480
- );
481
-
482
- if (exactMatch) {
483
- return {
484
- route: exactMatch,
485
- params: {}
486
- };
487
- }
488
-
489
- // Buscar rutas estáticas (prefijos)
490
- for (const route of this.routes) {
491
- if (route.method !== method) continue;
492
-
493
- // Para rutas estáticas, verificar si la ruta solicitada comienza con el prefijo de la ruta estática
494
- if (route.isStatic && pathname.startsWith(route.path)) {
495
- // Verificar que sea exactamente el prefijo o que haya una barra después del prefijo
496
- const remainingPath = pathname.substring(route.path.length);
497
- if (route.path === '/' || remainingPath === '' || remainingPath.startsWith('/')) {
498
- return {
499
- route: route,
500
- params: {}
501
- };
502
- }
503
- }
504
- }
505
-
506
- // Buscar rutas parametrizadas
507
- for (const route of this.routes) {
508
- if (route.method !== method) continue;
509
-
510
- // Convertir ruta parametrizada a expresión regular
511
- const routeRegex = this.pathToRegex(route.path);
512
- const match = pathname.match(routeRegex);
513
-
514
- if (match) {
515
- const params = this.extractParams(route.path, pathname);
516
- return {
517
- route: route,
518
- params
519
- };
520
- }
521
- }
522
-
523
- return null;
524
- }
525
-
526
- /**
527
- * Convierte una ruta con parámetros a expresión regular
528
- * @param {string} path - Ruta con posibles parámetros
529
- * @returns {RegExp} - Expresión regular para la ruta
530
- */
531
- pathToRegex(path) {
532
- // Verificar si ya está en caché
533
- if (this.routeRegexCache.has(path)) {
534
- return this.routeRegexCache.get(path);
535
- }
536
-
537
- // Lógica de escape de caracteres especiales
538
- let escapedPath = '';
539
- for (let i = 0; i < path.length; i++) {
540
- const char = path[i];
541
- if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
542
- escapedPath += '\\' + char;
543
- } else {
544
- escapedPath += char;
545
- }
546
- }
547
- // Reemplazar parámetros :param con grupos de captura no greedy para evitar problemas de matching
548
- const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
549
- const regex = new RegExp(`^${regexPath}$`);
550
-
551
- // Almacenar en caché
552
- this.routeRegexCache.set(path, regex);
553
- return regex;
554
- }
555
-
556
- /**
557
- * Extrae los parámetros de una ruta parametrizada
558
- * @param {string} routePath - Ruta con parámetros (ej. /users/:id)
559
- * @param {string} actualPath - Ruta real solicitada
560
- * @returns {Object} - Objeto con los parámetros extraídos
561
- */
562
- extractParams(routePath, actualPath) {
563
- const params = {};
564
-
565
- // Expresión regular para encontrar parámetros en la ruta
566
- const paramNames = [];
567
- const paramNameRegex = /:([a-zA-Z0-9_]+)/g;
568
- let match;
569
-
570
- while ((match = paramNameRegex.exec(routePath)) !== null) {
571
- paramNames.push(match[1]);
572
- }
573
-
574
- // Crear expresión regular para extraer valores
575
- const routeRegex = this.pathToRegex(routePath);
576
- const values = actualPath.match(routeRegex);
577
-
578
- if (values) {
579
- // El primer elemento es la cadena completa, los demás son los valores capturados
580
- for (let i = 0; i < paramNames.length; i++) {
581
- if (values[i + 1]) {
582
- params[paramNames[i]] = values[i + 1];
583
- }
584
- }
585
- }
586
-
587
- return params;
526
+ return this.routeMatcher.findRoute(this.routes, method, pathname);
588
527
  }
589
528
 
590
529
  /**
@@ -725,9 +664,9 @@ class APIServer {
725
664
  hooks.doAction('view_rendered', viewName, data, req, res);
726
665
  }
727
666
  } catch (error) {
728
- console.error('Error renderizando vista:', error);
729
- res.writeHead(500, { 'Content-Type': 'application/json' });
730
- res.end(JSON.stringify({ error: 'Error interno del servidor', details: error.message }));
667
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
668
+ const { ErrorHandler } = require('../utils/errorHandler');
669
+ ErrorHandler.handle(error, req, res, this.logger);
731
670
 
732
671
  if (hooks) {
733
672
  hooks.doAction('view_render_error', viewName, data, req, res, error);
@@ -186,7 +186,7 @@ class RouteDirectoryLoader {
186
186
  // Verificar si ya existe una ruta con el mismo método y path
187
187
  if (this.routeMap.has(routeKey)) {
188
188
  const existingRouteInfo = this.routeMap.get(routeKey);
189
-
189
+
190
190
  // Disparar hook antes de sobrescribir ruta
191
191
  const hooks = require('../../index.js').hooks;
192
192
  if (hooks) {
@@ -196,18 +196,24 @@ class RouteDirectoryLoader {
196
196
  server
197
197
  });
198
198
  }
199
-
199
+
200
200
  // Mostrar mensaje de advertencia con colores
201
201
  console.log('\x1b[31m%s\x1b[0m', `[RUTA SOBREESCRITA] Archivo: ${existingRouteInfo.sourceFile}, Ruta: ${route.method.toUpperCase()} ${route.path}`);
202
202
  console.log('\x1b[33m%s\x1b[0m', `[RUTA ACTUAL] Archivo: ${sourceFile}, Ruta: ${route.method.toUpperCase()} ${route.path}`);
203
+
204
+ // Actualizar la ruta en el mapa con la nueva información
205
+ this.routeMap.set(routeKey, {
206
+ route: { ...route },
207
+ sourceFile
208
+ });
209
+ } else {
210
+ // Agregar la ruta al mapa con su archivo de origen
211
+ this.routeMap.set(routeKey, {
212
+ route: { ...route },
213
+ sourceFile
214
+ });
203
215
  }
204
216
 
205
- // Agregar la ruta al mapa con su archivo de origen
206
- this.routeMap.set(routeKey, {
207
- route: { ...route },
208
- sourceFile
209
- });
210
-
211
217
  // Cargar la ruta en el servidor
212
218
  const routeLoader = new RouteLoader();
213
219
  await routeLoader.loadSingleRoute(server, route);
@@ -6,6 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const RouteMatcher = require('../router/RouteMatcher');
9
10
 
10
11
  class RouteLoader {
11
12
  /**
@@ -13,6 +14,8 @@ class RouteLoader {
13
14
  */
14
15
  constructor() {
15
16
  this.loadedRoutes = [];
17
+ // Inicializar el componente de enrutamiento para uso en el loader si es necesario
18
+ this.routeMatcher = new RouteMatcher();
16
19
  }
17
20
 
18
21
  /**
@@ -201,10 +204,10 @@ class RouteLoader {
201
204
  }
202
205
  }
203
206
  } catch (error) {
204
- console.error('Error en el manejo de autenticación de sesión:', error);
207
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
208
+ const { ErrorHandler } = require('../utils/errorHandler');
205
209
  if (!res.headersSent) {
206
- res.writeHead(500, { 'Content-Type': 'application/json' });
207
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
210
+ ErrorHandler.handle(error, req, res, server.logger || { error: (msg) => console.error(msg) });
208
211
  }
209
212
  }
210
213
  };
@@ -244,10 +247,10 @@ class RouteLoader {
244
247
  }
245
248
  }
246
249
  } catch (error) {
247
- console.error('Error en el manejo de autenticación:', error);
250
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
251
+ const { ErrorHandler } = require('../utils/errorHandler');
248
252
  if (!res.headersSent) {
249
- res.writeHead(500, { 'Content-Type': 'application/json' });
250
- res.end(JSON.stringify({ error: 'Error interno del servidor' }));
253
+ ErrorHandler.handle(error, req, res, server.logger || { error: (msg) => console.error(msg) });
251
254
  }
252
255
  }
253
256
  };
@@ -296,27 +299,6 @@ class RouteLoader {
296
299
  }
297
300
  }
298
301
 
299
- /**
300
- * Convierte una ruta con parámetros a expresión regular
301
- * @param {string} path - Ruta con posibles parámetros
302
- * @returns {RegExp} - Expresión regular para la ruta
303
- */
304
- pathToRegex(path) {
305
- // Escapar caracteres especiales de la ruta, excepto los parámetros
306
- // Pero dejar : sin escapar ya que lo usaremos para identificar parámetros
307
- let escapedPath = '';
308
- for (let i = 0; i < path.length; i++) {
309
- const char = path[i];
310
- if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
311
- escapedPath += '\\' + char;
312
- } else {
313
- escapedPath += char;
314
- }
315
- }
316
- // Reemplazar parámetros :param con grupos de captura
317
- const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+)');
318
- return new RegExp(`^${regexPath}$`);
319
- }
320
302
 
321
303
  /**
322
304
  * Método para recargar rutas desde un archivo
@@ -70,9 +70,9 @@ class ControllerBase {
70
70
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
71
71
  res.end(renderedView);
72
72
  } catch (error) {
73
- console.error('Error renderizando vista:', error);
74
- res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
75
- res.end('Error interno del servidor');
73
+ // Usar el ErrorHandler para mostrar el stacktrace en color amarillo
74
+ const { ErrorHandler } = require('../utils/errorHandler');
75
+ ErrorHandler.handle(error, this.req, res, { error: (msg) => console.error(msg) });
76
76
  }
77
77
  }
78
78
 
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Componente especializado para la lógica de enrutado
3
+ * Implementación del componente router/RouteMatcher.js
4
+ * JERK Framework v2.1 - Separación de responsabilidades para enrutamiento
5
+ */
6
+
7
+ class RouteMatcher {
8
+ /**
9
+ * Constructor del matcher de rutas
10
+ */
11
+ constructor() {
12
+ // Cache de expresiones regulares para rutas parametrizadas
13
+ this.routeRegexCache = new Map();
14
+ }
15
+
16
+ /**
17
+ * Método para encontrar una ruta coincidente
18
+ * @param {Array} routes - Array de rutas registradas
19
+ * @param {string} method - Método HTTP
20
+ * @param {string} pathname - Ruta a buscar
21
+ * @returns {Object|null} - Objeto de ruta encontrado o null
22
+ */
23
+ findRoute(routes, method, pathname) {
24
+ // Buscar ruta exacta primero
25
+ const exactMatch = routes.find(route =>
26
+ route.method === method && route.path === pathname
27
+ );
28
+
29
+ if (exactMatch) {
30
+ return {
31
+ route: exactMatch,
32
+ params: {}
33
+ };
34
+ }
35
+
36
+ // Buscar rutas parametrizadas antes que rutas estáticas (prefijos)
37
+ // Esto evita que rutas estáticas como /api capturen rutas parametrizadas como /api/users/:id
38
+ for (const route of routes) {
39
+ if (route.method !== method) continue;
40
+
41
+ // Convertir ruta parametrizada a expresión regular
42
+ const routeRegex = this.pathToRegex(route.path);
43
+ const match = pathname.match(routeRegex);
44
+
45
+ if (match) {
46
+ const params = this.extractParams(route.path, pathname);
47
+ return {
48
+ route: route,
49
+ params
50
+ };
51
+ }
52
+ }
53
+
54
+ // Buscar rutas estáticas (prefijos) - solo después de buscar rutas parametrizadas
55
+ for (const route of routes) {
56
+ if (route.method !== method) continue;
57
+
58
+ // Para rutas estáticas, verificar si la ruta solicitada comienza con el prefijo de la ruta estática
59
+ if (route.isStatic && pathname.startsWith(route.path)) {
60
+ // Verificar que sea exactamente el prefijo o que haya una barra después del prefijo
61
+ const remainingPath = pathname.substring(route.path.length);
62
+ if (route.path === '/' || remainingPath === '' || remainingPath.startsWith('/')) {
63
+ return {
64
+ route: route,
65
+ params: {}
66
+ };
67
+ }
68
+ }
69
+ }
70
+
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Convierte una ruta con parámetros a expresión regular
76
+ * @param {string} path - Ruta con posibles parámetros
77
+ * @returns {RegExp} - Expresión regular para la ruta
78
+ */
79
+ pathToRegex(path) {
80
+ // Verificar si ya está en caché
81
+ if (this.routeRegexCache.has(path)) {
82
+ return this.routeRegexCache.get(path);
83
+ }
84
+
85
+ // Lógica de escape de caracteres especiales
86
+ let escapedPath = '';
87
+ for (let i = 0; i < path.length; i++) {
88
+ const char = path[i];
89
+ if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
90
+ escapedPath += '\\' + char;
91
+ } else {
92
+ escapedPath += char;
93
+ }
94
+ }
95
+ // Reemplazar parámetros :param con grupos de captura no greedy para evitar problemas de matching
96
+ const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
97
+ const regex = new RegExp(`^${regexPath}$`);
98
+
99
+ // Almacenar en caché
100
+ this.routeRegexCache.set(path, regex);
101
+ return regex;
102
+ }
103
+
104
+ /**
105
+ * Extrae los parámetros de una ruta parametrizada
106
+ * @param {string} routePath - Ruta con parámetros (ej. /users/:id)
107
+ * @param {string} actualPath - Ruta real solicitada
108
+ * @returns {Object} - Objeto con los parámetros extraídos
109
+ */
110
+ extractParams(routePath, actualPath) {
111
+ const params = {};
112
+
113
+ // Expresión regular para encontrar parámetros en la ruta
114
+ const paramNames = [];
115
+ const paramNameRegex = /:([a-zA-Z0-9_]+)/g;
116
+ let match;
117
+
118
+ while ((match = paramNameRegex.exec(routePath)) !== null) {
119
+ paramNames.push(match[1]);
120
+ }
121
+
122
+ // Crear expresión regular para extraer valores
123
+ const routeRegex = this.pathToRegex(routePath);
124
+ const values = actualPath.match(routeRegex);
125
+
126
+ if (values) {
127
+ // El primer elemento es la cadena completa, los demás son los valores capturados
128
+ for (let i = 0; i < paramNames.length; i++) {
129
+ if (values[i + 1]) {
130
+ params[paramNames[i]] = values[i + 1];
131
+ }
132
+ }
133
+ }
134
+
135
+ return params;
136
+ }
137
+ }
138
+
139
+ module.exports = RouteMatcher;
@@ -99,13 +99,18 @@ class ErrorHandler {
99
99
  timestamp: new Date().toISOString()
100
100
  });
101
101
  } else {
102
- console.error('Unknown Error:', {
103
- message: error.message,
104
- stack: error.stack,
105
- url: req?.url,
106
- method: req?.method,
107
- timestamp: new Date().toISOString()
108
- });
102
+ // Mostrar el stacktrace en color amarillo
103
+ const yellowColor = '\x1b[33m';
104
+ const resetColor = '\x1b[0m';
105
+
106
+ console.error(`${yellowColor}======= STACKTRACE ERROR =======${resetColor}`);
107
+ console.error(`${yellowColor}Message: ${error.message}${resetColor}`);
108
+ console.error(`${yellowColor}URL: ${req?.url}${resetColor}`);
109
+ console.error(`${yellowColor}Method: ${req?.method}${resetColor}`);
110
+ console.error(`${yellowColor}Timestamp: ${new Date().toISOString()}${resetColor}`);
111
+ console.error(`${yellowColor}Stack:${resetColor}`);
112
+ console.error(`${yellowColor}${error.stack || 'No stack trace available'}${resetColor}`);
113
+ console.error(`${yellowColor}==============================${resetColor}`);
109
114
  }
110
115
  }
111
116
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jerkjs",
3
- "version": "2.5.0",
4
- "description": "JERK Framework v2.5.0 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, and enhanced route loading from directory",
3
+ "version": "2.5.2",
4
+ "description": "JERK Framework v2.5.2 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, and enhanced route loading from directory",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -0,0 +1,93 @@
1
+ # Informe QA: Sistema BlackCoffee con Fix de Enrutamiento
2
+
3
+ ## Resumen Ejecutivo
4
+
5
+ El sistema BlackCoffee ha sido sometido a pruebas exhaustivas tras la implementación de un fix crítico para el enrutamiento de rutas parametrizadas. El problema que impedía el funcionamiento correcto de las rutas parametrizadas en modo directorio ha sido completamente resuelto.
6
+
7
+ ## Estado Anterior vs Actual
8
+
9
+ ### Antes del Fix
10
+ - **Modo directorio (predeterminado)**: ❌ Rutas parametrizadas no funcionaban
11
+ - **Modo archivo único**: ✅ Funcionaba correctamente
12
+ - **Impacto**: Sistema de colas de Qwen no operativo en modo directorio
13
+
14
+ ### Después del Fix
15
+ - **Modo directorio**: ✅ **TOTALMENTE OPERATIVO**
16
+ - **Modo archivo único**: ✅ **CONTINÚA FUNCIONANDO CORRECTAMENTE**
17
+ - **Impacto**: Sistema de colas de Qwen completamente funcional
18
+
19
+ ## Cambios Implementados
20
+
21
+ ### Arquitectura de Enrutamiento
22
+ - Se implementó un componente especializado `RouteMatcher.js` para encapsular toda la lógica de enrutamiento
23
+ - Se separaron claramente las responsabilidades entre componentes
24
+ - Se centralizó la lógica de matching de rutas
25
+
26
+ ### Prioridad de Enrutamiento (Fix Principal)
27
+ - **Antes**: Rutas estáticas (prefijos) tenían prioridad sobre rutas parametrizadas
28
+ - **Después**: Rutas parametrizadas tienen prioridad sobre rutas estáticas
29
+ - **Nuevo orden**: 1) Rutas exactas → 2) Rutas parametrizadas → 3) Rutas estáticas
30
+
31
+ ## Resultados de las Pruebas
32
+
33
+ ### Rutas Parametrizadas Funcionales
34
+ | Ruta | Método | Controlador | Estado |
35
+ |------|--------|-------------|---------|
36
+ | `/api/products/:id` | GET | `getProductById` | ✅ Funcional |
37
+ | `/api/qwen/result/:id` | GET | `getQwenResult` | ✅ Funcional |
38
+ | `/api/queue/status/:id` | GET | `getJobStatus` | ✅ Funcional |
39
+ | `/api/queue/jobs/:status` | GET | `getJobsByStatus` | ✅ Funcional |
40
+
41
+ ### Sistema de Colas de Qwen
42
+ | Endpoint | Funcionalidad | Estado |
43
+ |----------|---------------|---------|
44
+ | `POST /api/qwen/queue` | Creación de trabajos | ✅ Funcional |
45
+ | `GET /api/qwen/result/:id` | Consulta de resultados | ✅ Funcional |
46
+ | Procesamiento completo | Trabajo de extremo a extremo | ✅ Funcional |
47
+
48
+ ### Validación de Parámetros
49
+ - Extracción correcta de parámetros de rutas (ej: `:id`, `:status`)
50
+ - Valores de parámetros correctamente pasados a controladores
51
+ - Respuestas coherentes según valores de parámetros
52
+
53
+ ## Cobertura de Pruebas
54
+
55
+ ### Modo Directorio (Principal)
56
+ - ✅ Carga de rutas desde múltiples archivos JSON
57
+ - ✅ Registro correcto de todas las rutas
58
+ - ✅ Prioridad correcta de rutas parametrizadas
59
+ - ✅ Funcionamiento de rutas estáticas y dinámicas
60
+ - ✅ Sistema de colas de Qwen operativo
61
+
62
+ ### Modo Archivo Único (Regresión)
63
+ - ✅ Continuidad del funcionamiento previo
64
+ - ✅ No regresiones introducidas
65
+ - ✅ Compatibilidad hacia atrás mantenida
66
+
67
+ ## Métricas de Desempeño
68
+
69
+ - **Tiempo de respuesta**: Dentro de rangos normales
70
+ - **Consumo de memoria**: Estable
71
+ - **Procesamiento de rutas**: Correcto en ambos modos
72
+ - **Sistema de hooks**: Operativo sin interrupciones
73
+
74
+ ## Validación de Endpoints Registrados
75
+
76
+ Se verificó que los 19 endpoints se registran correctamente en modo directorio, incluyendo:
77
+ - 4 rutas parametrizadas funcionales
78
+ - 10 rutas estáticas funcionales
79
+ - 5 rutas estáticas funcionales
80
+
81
+ ## Conclusión
82
+
83
+ ### ✅ ACEPTACIÓN TOTAL
84
+
85
+ El fix implementado ha resuelto completamente el problema reportado. El sistema BlackCoffee ahora:
86
+
87
+ 1. **Opera correctamente en modo directorio** con todas las rutas parametrizadas funcionando
88
+ 2. **Mantiene compatibilidad** con el modo archivo único
89
+ 3. **Soporta completamente** el sistema de colas de Qwen
90
+ 4. **Sigue principios de arquitectura limpia** con separación de responsabilidades
91
+ 5. **No introduce regresiones** en funcionalidades existentes
92
+
93
+ **Recomendación**: Aprobar el despliegue del fix a producción.