jerkjs 2.5.2 → 2.5.6

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.
@@ -0,0 +1,934 @@
1
+ # Manual Completo: Desarrollo de Aplicaciones MVC en JERK Framework v2.5.3
2
+
3
+ ## Tabla de Contenidos
4
+ 1. [Desarrollo MVC](#desarrollo-mvc)
5
+ - [Crear Controlador](#crear-controlador)
6
+ - [Crear Modelo](#crear-modelo)
7
+ - [Crear Vista](#crear-vista)
8
+ - [Registrar Rutas](#registrar-rutas)
9
+ 2. [Sistema de Hooks y Filters](#sistema-de-hooks-y-filters)
10
+ - [Registrar Hooks](#registrar-hooks)
11
+ - [Registrar Filters](#registrar-filters)
12
+ 3. [Creación de Extensiones](#creación-de-extensiones)
13
+
14
+ ---
15
+
16
+ ## Desarrollo MVC
17
+
18
+ ### Crear Controlador
19
+
20
+ Para crear un controlador en JERK Framework, debes extender la clase `ControllerBase`:
21
+
22
+ ```javascript
23
+ // controllers/EjemploController.js
24
+ const { ControllerBase } = require('jerkjs');
25
+
26
+ class EjemploController extends ControllerBase {
27
+ constructor() {
28
+ super();
29
+ }
30
+
31
+ // Método para mostrar la lista de elementos
32
+ async index(req, res) {
33
+ try {
34
+ // Cargar el modelo con el adaptador desde la solicitud
35
+ const adapter = req.modelManager?.getAdapter('memory') || null;
36
+ const modelo = await this.loadModel('EjemploModel', { adapter });
37
+
38
+ // Obtener datos del modelo
39
+ const elementos = await modelo.getAll();
40
+
41
+ // Renderizar vista con datos
42
+ res.render('ejemplo/index', {
43
+ title: 'Lista de Elementos',
44
+ elementos: elementos
45
+ });
46
+ } catch (error) {
47
+ console.error('Error en el controlador:', error);
48
+ res.writeHead(500, { 'Content-Type': 'application/json' });
49
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
50
+ }
51
+ }
52
+
53
+ // Método para mostrar un elemento específico
54
+ async show(req, res) {
55
+ try {
56
+ const adapter = req.modelManager?.getAdapter('memory') || null;
57
+ const modelo = await this.loadModel('EjemploModel', { adapter });
58
+
59
+ const id = req.params.id;
60
+ const elemento = await modelo.getById(id);
61
+
62
+ if (!elemento) {
63
+ res.writeHead(404, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify({ error: 'Elemento no encontrado' }));
65
+ return;
66
+ }
67
+
68
+ res.render('ejemplo/show', {
69
+ title: `Elemento #${id}`,
70
+ elemento: elemento
71
+ });
72
+ } catch (error) {
73
+ console.error('Error en el controlador:', error);
74
+ res.writeHead(500, { 'Content-Type': 'application/json' });
75
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
76
+ }
77
+ }
78
+
79
+ // Método para crear un nuevo elemento
80
+ async create(req, res) {
81
+ try {
82
+ const adapter = req.modelManager?.getAdapter('memory') || null;
83
+ const modelo = await this.loadModel('EjemploModel', { adapter });
84
+
85
+ const datos = req.body;
86
+ const id = await modelo.create(datos);
87
+
88
+ res.writeHead(201, { 'Content-Type': 'application/json' });
89
+ res.end(JSON.stringify({
90
+ success: true,
91
+ id: id,
92
+ message: 'Elemento creado exitosamente'
93
+ }));
94
+ } catch (error) {
95
+ console.error('Error en el controlador:', error);
96
+ res.writeHead(500, { 'Content-Type': 'application/json' });
97
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
98
+ }
99
+ }
100
+ }
101
+
102
+ // Crear instancia y preservar contexto
103
+ const ejemploController = new EjemploController();
104
+ ejemploController.index = ejemploController.index.bind(ejemploController);
105
+ ejemploController.show = ejemploController.show.bind(ejemploController);
106
+ ejemploController.create = ejemploController.create.bind(ejemploController);
107
+
108
+ module.exports = ejemploController;
109
+ ```
110
+
111
+ #### Características del Controlador:
112
+ - Extiende `ControllerBase` para acceder a funcionalidades MVC
113
+ - Preserva el contexto `this` usando `bind()`
114
+ - Accede al modelManager a través de `req.modelManager`
115
+ - Utiliza `loadModel()` para cargar modelos con adaptadores
116
+ - Usa `res.render()` para mostrar vistas
117
+
118
+ ### Uso de loadModel
119
+
120
+ El método `loadModel()` es fundamental para cargar modelos en los controladores. Su uso correcto es esencial para el funcionamiento del sistema MVC:
121
+
122
+ ```javascript
123
+ // Cargar un modelo sin opciones adicionales
124
+ const modelo = await this.loadModel('NombreDelModelo');
125
+
126
+ // Cargar un modelo con un adaptador específico
127
+ const adapter = req.modelManager?.getAdapter('memory') || null;
128
+ const modelo = await this.loadModel('NombreDelModelo', {
129
+ adapter: adapter
130
+ });
131
+
132
+ // Cargar un modelo con opciones adicionales
133
+ const modelo = await this.loadModel('NombreDelModelo', {
134
+ adapter: adapter,
135
+ tableName: 'nombre_tabla',
136
+ logger: console
137
+ });
138
+
139
+ // Cargar un modelo desde un directorio personalizado
140
+ const modelo = await this.loadModel('NombreDelModelo', {
141
+ adapter: adapter,
142
+ modelDir: './mi-estructura-especial/models/usuarios'
143
+ });
144
+ ```
145
+
146
+ #### Características de loadModel:
147
+ - **Búsqueda inteligente**: Busca el modelo en múltiples ubicaciones posibles (`./models/`, `../models/`, etc.)
148
+ - **Reutilización de instancias**: Si ya existe una instancia del modelo en el controlador, la reutiliza
149
+ - **Gestión de adaptadores**: Permite asignar un adaptador de base de datos al modelo
150
+ - **Caché interno**: Mantiene una caché de instancias para mejorar el rendimiento
151
+
152
+ #### Ejemplo completo de uso en un controlador:
153
+ ```javascript
154
+ async index(req, res) {
155
+ try {
156
+ // Obtener el adaptador desde el modelManager en la solicitud
157
+ const adapter = req.modelManager?.getAdapter('memory') || null;
158
+
159
+ // Cargar el modelo con el adaptador
160
+ const userModel = await this.loadModel('UserModel', {
161
+ adapter: adapter
162
+ });
163
+
164
+ // Utilizar el modelo para obtener datos
165
+ const users = await userModel.getAll();
166
+
167
+ // Renderizar la vista con los datos
168
+ res.render('users/index', {
169
+ title: 'Lista de Usuarios',
170
+ users: users
171
+ });
172
+ } catch (error) {
173
+ console.error('Error en el controlador:', error);
174
+ res.writeHead(500, { 'Content-Type': 'application/json' });
175
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
176
+ }
177
+ }
178
+ ```
179
+
180
+ #### Convenciones de nomenclatura:
181
+ - El nombre del modelo en `loadModel()` debe coincidir con el nombre del archivo del modelo
182
+ - Si el archivo es `UserModel.js`, usar `'UserModel'` como parámetro
183
+ - El sistema buscará en `./models/UserModel.js`, `../models/UserModel.js`, etc.
184
+
185
+ #### Carga de modelos en subdirectorios o ubicaciones personalizadas:
186
+
187
+ Cuando los modelos están en subdirectorios o ubicaciones no estándar, puedes usar las siguientes estrategias:
188
+
189
+ 1. **Importar directamente y pasar la clase al controlador**:
190
+ ```javascript
191
+ // En el controlador
192
+ const UserModel = require('../path/a/tu/modelo/UserModel');
193
+ const modelo = new UserModel(adapter);
194
+ // Usar el modelo directamente sin loadModel()
195
+ ```
196
+
197
+ 2. **Modificar el sistema de búsqueda de loadModel** (requiere extensión):
198
+ ```javascript
199
+ // Puedes extender ControllerBase para personalizar la búsqueda
200
+ class MiControllerBase extends ControllerBase {
201
+ async loadModel(modelName, options = {}) {
202
+ // Lógica personalizada de búsqueda
203
+ // Buscar en ubicaciones personalizadas
204
+ try {
205
+ // Intentar rutas personalizadas
206
+ const customPath = `./mis-modelos/especializados/${modelName}`;
207
+ const ModelClass = require(customPath);
208
+ return new ModelClass(options);
209
+ } catch (error) {
210
+ // Si falla, usar la lógica estándar
211
+ return super.loadModel(modelName, options);
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ 3. **Usar rutas absolutas o relativas específicas**:
218
+ ```javascript
219
+ // En lugar de usar loadModel, importar directamente
220
+ const { UserModel } = require('../../estructura-compleja/usuarios/UserModel');
221
+ // Crear instancia manualmente
222
+ const userModel = new UserModel({ adapter: req.modelManager?.getAdapter('memory') });
223
+ ```
224
+
225
+ 5. **Usar la nueva funcionalidad de directorio personalizado**:
226
+ ```javascript
227
+ // Cargar un modelo desde un directorio personalizado
228
+ const adapter = req.modelManager?.getAdapter('memory') || null;
229
+ const modelo = await this.loadModel('MiModelo', {
230
+ adapter: adapter,
231
+ modelDir: './estructura-personalizada/modelos/usuarios'
232
+ });
233
+
234
+ // Esto buscará en: ./estructura-personalizada/modelos/usuarios/MiModelo
235
+ // y en: ./estructura-personalizada/modelos/usuarios/MiModelo.js
236
+ ```
237
+
238
+ 6. **Estructura recomendada para proyectos complejos**:
239
+ ```
240
+ mi-proyecto/
241
+ ├── controllers/
242
+ │ └── usuarios/
243
+ │ └── UsuarioController.js
244
+ ├── models/
245
+ │ └── usuarios/
246
+ │ └── UsuarioModel.js
247
+ ├── views/
248
+ │ └── usuarios/
249
+ │ └── index.html
250
+ └── routes/
251
+ └── usuarios-routes.json
252
+ ```
253
+
254
+ En este caso, el sistema de búsqueda de `loadModel` debería encontrar el modelo si está en `./models/usuarios/UsuarioModel.js` cuando se llama desde un controlador en `./controllers/usuarios/`.
255
+
256
+ ### Crear Modelo
257
+
258
+ Para crear un modelo en JERK Framework, debes extender la clase `ModelBase`:
259
+
260
+ ```javascript
261
+ // models/EjemploModel.js
262
+ const { ModelBase } = require('jerkjs');
263
+
264
+ class EjemploModel extends ModelBase {
265
+ constructor(options = {}) {
266
+ super(options);
267
+ this.tableName = 'ejemplos'; // Nombre de la tabla en la base de datos
268
+ }
269
+
270
+ // Método para obtener todos los registros
271
+ async getAll() {
272
+ return await this.find({}, { orderBy: 'id', orderDirection: 'ASC' });
273
+ }
274
+
275
+ // Método para obtener un registro por ID
276
+ async getById(id) {
277
+ return await this.findOne({ id: parseInt(id) });
278
+ }
279
+
280
+ // Método para crear un nuevo registro
281
+ async create(datos) {
282
+ return await super.create(datos);
283
+ }
284
+
285
+ // Método para actualizar un registro
286
+ async update(id, datos) {
287
+ return await super.update({ id: parseInt(id) }, datos);
288
+ }
289
+
290
+ // Método para eliminar un registro
291
+ async delete(id) {
292
+ return await super.delete({ id: parseInt(id) });
293
+ }
294
+
295
+ // Método personalizado para búsqueda
296
+ async findByField(fieldName, value) {
297
+ const conditions = {};
298
+ conditions[fieldName] = value;
299
+ return await this.find(conditions);
300
+ }
301
+ }
302
+
303
+ module.exports = EjemploModel;
304
+ ```
305
+
306
+ #### Características del Modelo:
307
+ - Extiende `ModelBase` para acceder a funcionalidades CRUD
308
+ - Define el nombre de la tabla con `this.tableName`
309
+ - Utiliza métodos heredados como `find()`, `findOne()`, `create()`, etc.
310
+ - Puede incluir métodos personalizados para operaciones específicas
311
+ - Se integra con adaptadores de base de datos
312
+
313
+ ### Crear Vista
314
+
315
+ Las vistas en JERK Framework utilizan un sistema de plantillas con soporte para variables dinámicas:
316
+
317
+ ```html
318
+ <!-- views/ejemplo/index.html -->
319
+ <!DOCTYPE html>
320
+ <html lang="es">
321
+ <head>
322
+ <meta charset="UTF-8">
323
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
324
+ <title>{{title}}</title>
325
+ <style>
326
+ body { font-family: Arial, sans-serif; margin: 40px; }
327
+ .item { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
328
+ .btn { padding: 8px 16px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 5px; display: inline-block; }
329
+ </style>
330
+ </head>
331
+ <body>
332
+ <h1>{{title}}</h1>
333
+
334
+ <div class="actions">
335
+ <a href="/api/ejemplos/create" class="btn">Crear Nuevo</a>
336
+ </div>
337
+
338
+ <div class="items-list">
339
+ {{#each elementos}}
340
+ <div class="item">
341
+ <h3>{{this.nombre}}</h3>
342
+ <p>ID: {{this.id}}</p>
343
+ <p>Descripción: {{this.descripcion}}</p>
344
+ <a href="/api/ejemplos/{{this.id}}" class="btn">Ver Detalles</a>
345
+ </div>
346
+ {{/each}}
347
+ </div>
348
+
349
+ {{#unless elementos}}
350
+ <p>No hay elementos disponibles.</p>
351
+ {{/unless}}
352
+ </body>
353
+ </html>
354
+ ```
355
+
356
+ #### Características de las Vistas:
357
+ - Soporte para variables dinámicas con sintaxis `{{variable}}`
358
+ - Soporte para estructuras condicionales y bucles con Handlebars
359
+ - Integración con layouts para mantener consistencia visual
360
+ - Pueden recibir datos desde los controladores
361
+
362
+ ### Registrar Rutas
363
+
364
+ Existen tres formas de registrar rutas en JERK Framework:
365
+
366
+ #### 1. Programáticamente
367
+
368
+ ```javascript
369
+ // server.js
370
+ const { APIServer } = require('jerkjs');
371
+
372
+ const server = new APIServer({
373
+ port: 3000,
374
+ host: 'localhost'
375
+ });
376
+
377
+ // Importar controladores
378
+ const ejemploController = require('./controllers/EjemploController');
379
+
380
+ // Registrar rutas programáticamente
381
+ server.addRoute('GET', '/api/ejemplos', ejemploController.index);
382
+ server.addRoute('GET', '/api/ejemplos/:id', ejemploController.show);
383
+ server.addRoute('POST', '/api/ejemplos', ejemploController.create);
384
+
385
+ server.start();
386
+ ```
387
+
388
+ #### 2. Usando routes.json
389
+
390
+ ```json
391
+ // routes.json
392
+ [
393
+ {
394
+ "path": "/api/ejemplos",
395
+ "method": "GET",
396
+ "controller": "./controllers/EjemploController.js",
397
+ "handler": "index",
398
+ "contentType": "text/html"
399
+ },
400
+ {
401
+ "path": "/api/ejemplos/:id",
402
+ "method": "GET",
403
+ "controller": "./controllers/EjemploController.js",
404
+ "handler": "show",
405
+ "contentType": "text/html"
406
+ },
407
+ {
408
+ "path": "/api/ejemplos",
409
+ "method": "POST",
410
+ "controller": "./controllers/EjemploController.js",
411
+ "handler": "create",
412
+ "contentType": "application/json"
413
+ },
414
+ {
415
+ "path": "/api/ejemplos/static",
416
+ "method": "GET",
417
+ "static": {
418
+ "dir": "./public",
419
+ "index": ["index.html"],
420
+ "cacheControl": "public, max-age=3600"
421
+ }
422
+ }
423
+ ]
424
+ ```
425
+
426
+ ```javascript
427
+ // Cargar rutas desde archivo JSON
428
+ const { APIServer, RouteLoader } = require('jerkjs');
429
+
430
+ const server = new APIServer({ port: 3000 });
431
+ const routeLoader = new RouteLoader();
432
+
433
+ routeLoader.loadRoutes(server, './routes.json')
434
+ .then(() => server.start())
435
+ .catch(error => console.error('Error:', error.message));
436
+ ```
437
+
438
+ #### 3. Usando Múltiples Ficheros de Ruta
439
+
440
+ ```javascript
441
+ // Cargar rutas desde directorio
442
+ const { APIServer, RouteDirectoryLoader } = require('jerkjs');
443
+
444
+ const server = new APIServer({ port: 3000 });
445
+ const routeDirectoryLoader = new RouteDirectoryLoader();
446
+
447
+ routeDirectoryLoader.loadRoutesFromDirectory(server, './routes')
448
+ .then(routes => {
449
+ console.log(`${routes.length} rutas cargadas`);
450
+ server.start();
451
+ })
452
+ .catch(error => console.error('Error:', error.message));
453
+ ```
454
+
455
+ ```json
456
+ // routes/ejemplos-routes.json
457
+ [
458
+ {
459
+ "path": "/api/ejemplos",
460
+ "method": "GET",
461
+ "controller": "./controllers/EjemploController.js",
462
+ "handler": "index",
463
+ "contentType": "text/html"
464
+ },
465
+ {
466
+ "path": "/api/ejemplos/:id",
467
+ "method": "GET",
468
+ "controller": "./controllers/EjemploController.js",
469
+ "handler": "show",
470
+ "contentType": "text/html"
471
+ }
472
+ ]
473
+ ```
474
+
475
+ ```json
476
+ // routes/otros-routes.json
477
+ [
478
+ {
479
+ "path": "/api/otros",
480
+ "method": "GET",
481
+ "controller": "./controllers/OtroController.js",
482
+ "handler": "index",
483
+ "contentType": "application/json"
484
+ }
485
+ ]
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Sistema de Hooks y Filters
491
+
492
+ ### Registrar Hooks
493
+
494
+ Los hooks permiten extender la funcionalidad del framework en puntos específicos del ciclo de vida de la aplicación:
495
+
496
+ ```javascript
497
+ // hooks/personalizados.js
498
+ const { hooks } = require('jerkjs');
499
+
500
+ // Registrar un hook para antes de procesar una solicitud
501
+ hooks.addAction('request_received', (req, res) => {
502
+ console.log(`Solicitud recibida: ${req.method} ${req.url}`);
503
+ });
504
+
505
+ // Registrar un hook para después de encontrar una ruta
506
+ hooks.addAction('route_matched', (matchedRoute, req, res) => {
507
+ console.log(`Ruta encontrada: ${req.method} ${req.url}`);
508
+ console.log(`Handler: ${matchedRoute.route.handler.name}`);
509
+ });
510
+
511
+ // Registrar un hook para antes de ejecutar un handler de ruta
512
+ hooks.addAction('before_route_handler', async (req, res, next) => {
513
+ // Puedes modificar req/res o detener la ejecución
514
+ console.log('Antes de ejecutar handler de ruta');
515
+ next(); // Continuar con la ejecución
516
+ });
517
+
518
+ // Registrar un hook para después de ejecutar un handler de ruta
519
+ hooks.addAction('after_route_handler', (req, res) => {
520
+ console.log('Después de ejecutar handler de ruta');
521
+ });
522
+
523
+ // Registrar un hook personalizado
524
+ hooks.addAction('mi_hook_personalizado', (datos) => {
525
+ console.log('Hook personalizado ejecutado con:', datos);
526
+ });
527
+
528
+ // Disparar el hook personalizado
529
+ hooks.doAction('mi_hook_personalizado', { mensaje: 'Hola Mundo' });
530
+ ```
531
+
532
+ ### Registrar Filters
533
+
534
+ Los filters permiten modificar datos en diferentes puntos del flujo de la aplicación:
535
+
536
+ ```javascript
537
+ // filters/personalizados.js
538
+ const { hooks } = require('jerkjs');
539
+
540
+ // Registrar un filter para modificar el cuerpo de la solicitud
541
+ hooks.addFilter('request_body', (body, req) => {
542
+ // Agregar timestamp al cuerpo de la solicitud
543
+ if (typeof body === 'object') {
544
+ body.timestamp = new Date().toISOString();
545
+ }
546
+ return body;
547
+ });
548
+
549
+ // Registrar un filter para modificar la respuesta
550
+ hooks.addFilter('response_data', (data, res) => {
551
+ // Agregar información de metadata a la respuesta
552
+ if (typeof data === 'object') {
553
+ data.meta = {
554
+ timestamp: new Date().toISOString(),
555
+ version: '2.5.3'
556
+ };
557
+ }
558
+ return data;
559
+ });
560
+
561
+ // Registrar un filter para transformar datos del modelo antes de guardar
562
+ hooks.addFilter('model_before_save', (data, modelName) => {
563
+ // Normalizar datos antes de guardar
564
+ if (data.nombre) {
565
+ data.nombre = data.nombre.trim().toUpperCase();
566
+ }
567
+ return data;
568
+ });
569
+
570
+ // Registrar un filter para transformar datos del modelo después de leer
571
+ hooks.addFilter('model_after_read', (data, modelName) => {
572
+ // Transformar datos después de leer
573
+ if (data.fecha_creacion) {
574
+ data.fecha_formateada = new Date(data.fecha_creacion).toLocaleDateString();
575
+ }
576
+ return data;
577
+ });
578
+
579
+ // Ejemplo de uso de filter
580
+ const datosOriginales = { nombre: ' juan perez ' };
581
+ const datosFiltrados = hooks.applyFilters('model_before_save', datosOriginales, 'UsuarioModel');
582
+ console.log(datosFiltrados); // { nombre: 'JUAN PEREZ' }
583
+ ```
584
+
585
+ ---
586
+
587
+ ## Creación de Extensiones
588
+
589
+ Las extensiones permiten extender la funcionalidad del framework con componentes personalizados:
590
+
591
+ ### 1. Extensiones de Middleware
592
+
593
+ ```javascript
594
+ // extensions/ejemplo-middleware.js
595
+ class EjemploMiddleware {
596
+ /**
597
+ * Middleware para autenticación personalizada
598
+ */
599
+ static authMiddleware(opciones = {}) {
600
+ return (req, res, next) => {
601
+ // Verificar token de autenticación
602
+ const token = req.headers.authorization?.replace('Bearer ', '');
603
+
604
+ if (!token) {
605
+ res.writeHead(401, { 'Content-Type': 'application/json' });
606
+ res.end(JSON.stringify({ error: 'Token de autenticación requerido' }));
607
+ return;
608
+ }
609
+
610
+ // Validar token (implementación simulada)
611
+ if (token === 'valid-token') {
612
+ req.usuarioAutenticado = { id: 1, nombre: 'Usuario' };
613
+ next();
614
+ } else {
615
+ res.writeHead(401, { 'Content-Type': 'application/json' });
616
+ res.end(JSON.stringify({ error: 'Token inválido' }));
617
+ }
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Middleware para logging de solicitudes
623
+ */
624
+ static loggingMiddleware(logger) {
625
+ return (req, res, next) => {
626
+ const startTime = Date.now();
627
+
628
+ // Interceptamos res.end para medir tiempo
629
+ const originalEnd = res.end;
630
+ res.end = function(chunk, encoding) {
631
+ const duration = Date.now() - startTime;
632
+ logger.info(`${req.method} ${req.url} - ${duration}ms`);
633
+ originalEnd.call(this, chunk, encoding);
634
+ };
635
+
636
+ next();
637
+ };
638
+ }
639
+ }
640
+
641
+ module.exports = EjemploMiddleware;
642
+ ```
643
+
644
+ ### 2. Extensiones de Adaptador de Base de Datos
645
+
646
+ ```javascript
647
+ // extensions/custom-db-adapter.js
648
+ class CustomDBAdapter {
649
+ constructor(configuracion) {
650
+ this.config = configuracion;
651
+ // Inicializar conexión a base de datos personalizada
652
+ }
653
+
654
+ async connect() {
655
+ // Lógica para conectar a la base de datos
656
+ console.log('Conectando a base de datos personalizada...');
657
+ }
658
+
659
+ async disconnect() {
660
+ // Lógica para desconectar de la base de datos
661
+ console.log('Desconectando de base de datos personalizada...');
662
+ }
663
+
664
+ async select(tabla, condiciones = {}, opciones = {}) {
665
+ // Lógica para selección de datos
666
+ console.log(`SELECT * FROM ${tabla}`, condiciones);
667
+ return [];
668
+ }
669
+
670
+ async insert(tabla, datos) {
671
+ // Lógica para inserción de datos
672
+ console.log(`INSERT INTO ${tabla}`, datos);
673
+ return { id: 1 };
674
+ }
675
+
676
+ async update(tabla, condiciones, datos) {
677
+ // Lógica para actualización de datos
678
+ console.log(`UPDATE ${tabla} SET`, datos, 'WHERE', condiciones);
679
+ return 1;
680
+ }
681
+
682
+ async delete(tabla, condiciones) {
683
+ // Lógica para eliminación de datos
684
+ console.log(`DELETE FROM ${tabla} WHERE`, condiciones);
685
+ return 1;
686
+ }
687
+
688
+ async query(sql, params = []) {
689
+ // Lógica para consultas personalizadas
690
+ console.log('QUERY:', sql, params);
691
+ return [];
692
+ }
693
+ }
694
+
695
+ module.exports = CustomDBAdapter;
696
+ ```
697
+
698
+ ### 3. Extensiones de Motor de Plantillas
699
+
700
+ ```javascript
701
+ // extensions/custom-template-engine.js
702
+ const fs = require('fs');
703
+ const path = require('path');
704
+
705
+ class CustomTemplateEngine {
706
+ constructor(opciones = {}) {
707
+ this.viewsPath = opciones.viewsPath || './views';
708
+ this.cacheEnabled = opciones.cacheEnabled || false;
709
+ this.cache = new Map();
710
+ }
711
+
712
+ /**
713
+ * Renderiza una plantilla con datos
714
+ */
715
+ render(nombrePlantilla, datos = {}) {
716
+ const rutaPlantilla = path.join(this.viewsPath, nombrePlantilla + '.html');
717
+
718
+ // Verificar cache si está habilitado
719
+ if (this.cacheEnabled && this.cache.has(rutaPlantilla)) {
720
+ const cachedTemplate = this.cache.get(rutaPlantilla);
721
+ return this.procesarPlantilla(cachedTemplate, datos);
722
+ }
723
+
724
+ // Leer archivo de plantilla
725
+ let contenidoPlantilla = fs.readFileSync(rutaPlantilla, 'utf8');
726
+
727
+ // Almacenar en cache si está habilitado
728
+ if (this.cacheEnabled) {
729
+ this.cache.set(rutaPlantilla, contenidoPlantilla);
730
+ }
731
+
732
+ return this.procesarPlantilla(contenidoPlantilla, datos);
733
+ }
734
+
735
+ /**
736
+ * Procesa la plantilla reemplazando variables
737
+ */
738
+ procesarPlantilla(plantilla, datos) {
739
+ let resultado = plantilla;
740
+
741
+ // Reemplazar variables simples: {{variable}}
742
+ for (const [clave, valor] of Object.entries(datos)) {
743
+ const regex = new RegExp(`{{\\s*${clave}\\s*}}`, 'g');
744
+ resultado = resultado.replace(regex, valor);
745
+ }
746
+
747
+ // Procesar bucles: {{#each arreglo}}...{{/each}}
748
+ const eachRegex = /{{#each\s+(\w+)\s*}}([\s\S]*?){{\/each}}/g;
749
+ resultado = resultado.replace(eachRegex, (match, arregloNombre, contenido) => {
750
+ const arreglo = datos[arregloNombre];
751
+ if (!Array.isArray(arreglo)) return '';
752
+
753
+ let items = '';
754
+ for (const item of arreglo) {
755
+ let itemTemplate = contenido;
756
+ for (const [clave, valor] of Object.entries(item)) {
757
+ const regex = new RegExp(`{{\\s*${clave}\\s*}}`, 'g');
758
+ itemTemplate = itemTemplate.replace(regex, valor);
759
+ }
760
+ items += itemTemplate;
761
+ }
762
+ return items;
763
+ });
764
+
765
+ return resultado;
766
+ }
767
+
768
+ /**
769
+ * Limpia el cache de plantillas
770
+ */
771
+ clearCache() {
772
+ this.cache.clear();
773
+ }
774
+ }
775
+
776
+ module.exports = CustomTemplateEngine;
777
+ ```
778
+
779
+ ### 4. Extensiones de Componente del Framework
780
+
781
+ ```javascript
782
+ // extensions/custom-component.js
783
+ const { hooks } = require('jerkjs');
784
+
785
+ class CustomComponent {
786
+ constructor(servidor, opciones = {}) {
787
+ this.servidor = servidor;
788
+ this.opciones = opciones;
789
+ this.inicializado = false;
790
+
791
+ // Registrar hooks para este componente
792
+ this.registrarHooks();
793
+ }
794
+
795
+ /**
796
+ * Inicializa el componente
797
+ */
798
+ async inicializar() {
799
+ if (this.inicializado) return;
800
+
801
+ console.log('Inicializando componente personalizado...');
802
+
803
+ // Registrar rutas personalizadas
804
+ this.servidor.addRoute('GET', '/api/custom/status', this.getStatus.bind(this));
805
+ this.servidor.addRoute('POST', '/api/custom/action', this.performAction.bind(this));
806
+
807
+ this.inicializado = true;
808
+ }
809
+
810
+ /**
811
+ * Hook para registrar hooks del componente
812
+ */
813
+ registrarHooks() {
814
+ hooks.addAction('server_started', (servidor) => {
815
+ console.log('Componente personalizado activo');
816
+ });
817
+
818
+ hooks.addAction('request_received', (req, res) => {
819
+ // Agregar encabezado personalizado a todas las respuestas
820
+ res.customHeaderAdded = true;
821
+ });
822
+ }
823
+
824
+ /**
825
+ * Endpoint para obtener estado del componente
826
+ */
827
+ async getStatus(req, res) {
828
+ res.writeHead(200, { 'Content-Type': 'application/json' });
829
+ res.end(JSON.stringify({
830
+ status: 'activo',
831
+ componente: 'CustomComponent',
832
+ inicializado: this.inicializado,
833
+ timestamp: new Date().toISOString()
834
+ }));
835
+ }
836
+
837
+ /**
838
+ * Endpoint para realizar acciones personalizadas
839
+ */
840
+ async performAction(req, res) {
841
+ try {
842
+ const datos = req.body;
843
+
844
+ // Realizar acción personalizada
845
+ const resultado = await this.ejecutarAccion(datos);
846
+
847
+ res.writeHead(200, { 'Content-Type': 'application/json' });
848
+ res.end(JSON.stringify({
849
+ success: true,
850
+ resultado: resultado
851
+ }));
852
+ } catch (error) {
853
+ res.writeHead(500, { 'Content-Type': 'application/json' });
854
+ res.end(JSON.stringify({
855
+ error: error.message
856
+ }));
857
+ }
858
+ }
859
+
860
+ /**
861
+ * Método para ejecutar acciones personalizadas
862
+ */
863
+ async ejecutarAccion(datos) {
864
+ // Lógica personalizada aquí
865
+ return {
866
+ mensaje: 'Acción ejecutada exitosamente',
867
+ datos: datos,
868
+ timestamp: new Date().toISOString()
869
+ };
870
+ }
871
+ }
872
+
873
+ module.exports = CustomComponent;
874
+ ```
875
+
876
+ ### 5. Uso de Extensiones en la Aplicación
877
+
878
+ ```javascript
879
+ // app.js - Ejemplo de uso de extensiones
880
+ const { APIServer, RouteLoader, ModelManager, MemoryAdapter } = require('jerkjs');
881
+ const EjemploMiddleware = require('./extensions/ejemplo-middleware');
882
+ const CustomDBAdapter = require('./extensions/custom-db-adapter');
883
+ const CustomTemplateEngine = require('./extensions/custom-template-engine');
884
+ const CustomComponent = require('./extensions/custom-component');
885
+
886
+ // Crear servidor
887
+ const server = new APIServer({
888
+ port: 3000,
889
+ host: 'localhost'
890
+ });
891
+
892
+ // Configurar motor de vistas personalizado
893
+ server.viewEngine = new CustomTemplateEngine({
894
+ viewsPath: './views',
895
+ cacheEnabled: true
896
+ });
897
+
898
+ // Configurar sistema de modelos
899
+ const modelManager = new ModelManager();
900
+ const memoryAdapter = new MemoryAdapter();
901
+ modelManager.registerAdapter('memory', memoryAdapter);
902
+ server.modelManager = modelManager;
903
+
904
+ // Aplicar middleware personalizado
905
+ server.use(EjemploMiddleware.loggingMiddleware(console));
906
+ server.use(EjemploMiddleware.authMiddleware());
907
+
908
+ // Inicializar componente personalizado
909
+ const customComponent = new CustomComponent(server);
910
+ customComponent.inicializar();
911
+
912
+ // Cargar rutas
913
+ const routeLoader = new RouteLoader();
914
+ routeLoader.loadRoutes(server, './routes.json')
915
+ .then(() => {
916
+ server.start();
917
+ console.log('Aplicación iniciada con extensiones personalizadas');
918
+ })
919
+ .catch(error => {
920
+ console.error('Error iniciando aplicación:', error.message);
921
+ });
922
+ ```
923
+
924
+ ---
925
+
926
+ ## Buenas Prácticas
927
+
928
+ 1. **Organización de Código**: Mantener una estructura de directorios clara (controllers, models, views, extensions)
929
+ 2. **Reutilización**: Aprovechar la herencia de `ControllerBase` y `ModelBase`
930
+ 3. **Seguridad**: Validar entradas y usar middleware de autenticación
931
+ 4. **Rendimiento**: Usar cacheo de vistas y conexiones a base de datos
932
+ 5. **Mantenibilidad**: Documentar extensiones y seguir convenciones de nomenclatura
933
+
934
+ Este manual proporciona una guía completa para desarrollar aplicaciones MVC en JERK Framework, desde la creación de componentes básicos hasta la implementación de extensiones avanzadas.