jerkjs 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,566 @@
1
+ # Guía de Uso de Modelos en JERK Framework
2
+
3
+ ## Tabla de Contenidos
4
+ 1. [Introducción](#introducción)
5
+ 2. [Conceptos Básicos](#conceptos-básicos)
6
+ 3. [Creación de Modelos](#creación-de-modelos)
7
+ 4. [Operaciones CRUD](#operaciones-crud)
8
+ 5. [Validación de Datos](#validación-de-datos)
9
+ 6. [Comunicación con Controladores](#comunicación-con-controladores)
10
+ 7. [Uso de Adaptadores](#uso-de-adaptadores)
11
+ 8. [Hooks en Modelos](#hooks-en-modelos)
12
+ 9. [Ejemplos Prácticos](#ejemplos-prácticos)
13
+
14
+ ## Introducción
15
+
16
+ Los modelos en JERK Framework representan la capa de datos de tu aplicación y forman parte integral del patrón MVC (Modelo-Vista-Controlador). Proporcionan una interfaz para interactuar con diferentes fuentes de datos, ya sea memoria, bases de datos SQL o NoSQL.
17
+
18
+ ## Conceptos Básicos
19
+
20
+ ### Componentes Principales
21
+
22
+ - **ModelBase**: Clase base para todos los modelos
23
+ - **ModelManager**: Gestor centralizado para administrar instancias de modelos
24
+ - **Adaptadores**: Interfaces para diferentes tipos de almacenamiento
25
+ - **Hooks**: Sistema de extensibilidad para modelos
26
+
27
+ ### Características
28
+
29
+ - Comunicación bidireccional con controladores
30
+ - Soporte para múltiples adaptadores de almacenamiento
31
+ - Validación de datos integrada
32
+ - Sistema de hooks para extensibilidad
33
+ - Integración con el sistema de autenticación y autorización
34
+
35
+ ## Creación de Modelos
36
+
37
+ ### Extender ModelBase
38
+
39
+ Para crear un modelo personalizado, extiende la clase `ModelBase`:
40
+
41
+ ```javascript
42
+ const ModelBase = require('jerkjs').ModelBase;
43
+
44
+ class ProductoModel extends ModelBase {
45
+ constructor(options = {}) {
46
+ super({
47
+ ...options,
48
+ tableName: options.tableName || 'productos'
49
+ });
50
+
51
+ // Definir campos del modelo
52
+ this.fields = {
53
+ id: { type: 'integer', primaryKey: true, autoIncrement: true },
54
+ nombre: { type: 'string', required: true },
55
+ descripcion: { type: 'text' },
56
+ precio: { type: 'decimal', required: true },
57
+ categoria: { type: 'string' },
58
+ createdAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' },
59
+ updatedAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' }
60
+ };
61
+ }
62
+
63
+ // Métodos personalizados
64
+ async getProductosPorCategoria(categoria) {
65
+ return await this.find({ categoria });
66
+ }
67
+
68
+ async getProductosConPrecioMayor(precioMinimo) {
69
+ // Implementación personalizada según el adaptador
70
+ return await this.find({ precio: { $gte: precioMinimo } });
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Uso de ModelManager
76
+
77
+ El `ModelManager` te permite administrar tus modelos y sus adaptadores:
78
+
79
+ ```javascript
80
+ const { ModelManager, MemoryAdapter } = require('jerkjs');
81
+
82
+ // Crear instancia del gestor de modelos
83
+ const modelManager = new ModelManager();
84
+
85
+ // Registrar un adaptador
86
+ const memoryAdapter = new MemoryAdapter();
87
+ modelManager.registerAdapter('memory', memoryAdapter);
88
+
89
+ // Crear instancia de modelo
90
+ const productoModel = modelManager.createModel('Producto', {
91
+ adapterName: 'memory',
92
+ modelOptions: { tableName: 'productos' }
93
+ });
94
+ ```
95
+
96
+ ## Operaciones CRUD
97
+
98
+ Los modelos proporcionan métodos estándar para operaciones CRUD:
99
+
100
+ ### Crear Registros
101
+
102
+ ```javascript
103
+ // Crear un nuevo registro
104
+ const nuevoProducto = await productoModel.create({
105
+ nombre: 'Laptop',
106
+ descripcion: 'Laptop de alta gama',
107
+ precio: 1200.00,
108
+ categoria: 'Electrónica'
109
+ });
110
+ ```
111
+
112
+ ### Leer Registros
113
+
114
+ ```javascript
115
+ // Encontrar todos los registros
116
+ const productos = await productoModel.find({});
117
+
118
+ // Encontrar un registro específico
119
+ const producto = await productoModel.findOne({ id: 1 });
120
+
121
+ // Encontrar con condiciones
122
+ const electronicos = await productoModel.find({ categoria: 'Electrónica' });
123
+
124
+ // Encontrar con opciones (orden, límites)
125
+ const productosOrdenados = await productoModel.find(
126
+ { categoria: 'Electrónica' },
127
+ { orderBy: 'precio DESC', limit: 10 }
128
+ );
129
+ ```
130
+
131
+ ### Actualizar Registros
132
+
133
+ ```javascript
134
+ // Actualizar un registro
135
+ const filasAfectadas = await productoModel.update(
136
+ { id: 1 }, // condiciones
137
+ { precio: 1100.00 } // datos a actualizar
138
+ );
139
+
140
+ // Actualizar con validación personalizada
141
+ const resultado = await productoModel.updateUser(
142
+ { id: 1 },
143
+ { nombre: 'Laptop Actualizada' }
144
+ );
145
+ ```
146
+
147
+ ### Eliminar Registros
148
+
149
+ ```javascript
150
+ // Eliminar registros que coincidan con condiciones
151
+ const filasEliminadas = await productoModel.delete({ categoria: 'Obsoleto' });
152
+
153
+ // Eliminar un registro específico
154
+ const filasEliminadas = await productoModel.delete({ id: 1 });
155
+ ```
156
+
157
+ ## Validación de Datos
158
+
159
+ Los modelos incluyen un sistema de validación flexible:
160
+
161
+ ### Validación Personalizada
162
+
163
+ ```javascript
164
+ class ProductoModel extends ModelBase {
165
+ validate(operation, data) {
166
+ const result = { isValid: true, errors: [] };
167
+
168
+ // Validaciones específicas para creación
169
+ if (operation === 'create') {
170
+ if (!data.nombre) {
171
+ result.isValid = false;
172
+ result.errors.push('Nombre es requerido');
173
+ }
174
+
175
+ if (!data.precio || data.precio <= 0) {
176
+ result.isValid = false;
177
+ result.errors.push('Precio debe ser positivo');
178
+ }
179
+ }
180
+
181
+ // Validaciones para actualización
182
+ if (operation === 'update') {
183
+ if (data.hasOwnProperty('nombre') && !data.nombre) {
184
+ result.isValid = false;
185
+ result.errors.push('Nombre no puede estar vacío si se proporciona');
186
+ }
187
+
188
+ if (data.hasOwnProperty('precio') && (data.precio <= 0)) {
189
+ result.isValid = false;
190
+ result.errors.push('Precio debe ser positivo');
191
+ }
192
+ }
193
+
194
+ return result;
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Validación Automática
200
+
201
+ Los modelos realizan validaciones automáticas según las reglas definidas en el método `validate`.
202
+
203
+ ## Comunicación con Controladores
204
+
205
+ ### Registro de Controladores
206
+
207
+ Los modelos pueden comunicarse con controladores registrándolos:
208
+
209
+ ```javascript
210
+ // En el modelo
211
+ class ProductoModel extends ModelBase {
212
+ // ...
213
+
214
+ async notifyControllerOnCreate(producto) {
215
+ // Notificar a todos los controladores registrados
216
+ for (const controller of this.getControllers()) {
217
+ if (typeof controller.onProductoCreated === 'function') {
218
+ controller.onProductoCreated(producto);
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ // En el controlador
225
+ class ProductoController extends ControllerBase {
226
+ constructor() {
227
+ super();
228
+
229
+ // Registrar este controlador con el modelo
230
+ productoModel.registerController(this);
231
+ }
232
+
233
+ onProductoCreated(producto) {
234
+ console.log('Nuevo producto creado:', producto);
235
+ // Lógica adicional cuando se crea un producto
236
+ }
237
+
238
+ async crearProducto(req, res) {
239
+ try {
240
+ const producto = await productoModel.create(req.body);
241
+ res.status(201).json({ success: true, data: producto });
242
+ } catch (error) {
243
+ res.status(500).json({ success: false, error: error.message });
244
+ }
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### Comunicación Bidireccional
250
+
251
+ La comunicación es bidireccional, permitiendo que tanto modelos como controladores se comuniquen entre sí.
252
+
253
+ ## Uso de Adaptadores
254
+
255
+ ### Adaptador de Memoria
256
+
257
+ Útil para pruebas o aplicaciones pequeñas:
258
+
259
+ ```javascript
260
+ const { MemoryAdapter } = require('jerkjs');
261
+
262
+ const memoryAdapter = new MemoryAdapter();
263
+ productoModel.setAdapter(memoryAdapter);
264
+ ```
265
+
266
+ ### Adaptadores Personalizados
267
+
268
+ Puedes crear adaptadores personalizados extendiendo `GenericAdapter`:
269
+
270
+ ```javascript
271
+ const { GenericAdapter } = require('jerkjs');
272
+
273
+ class MiAdaptadorPersonalizado extends GenericAdapter {
274
+ constructor(options = {}) {
275
+ super({ ...options, type: 'mi_adaptador' });
276
+ // Inicializar conexión
277
+ }
278
+
279
+ async create(tableName, data) {
280
+ // Implementar lógica de creación
281
+ return { id: 1, ...data }; // Ejemplo
282
+ }
283
+
284
+ async find(tableName, conditions, options) {
285
+ // Implementar lógica de búsqueda
286
+ return []; // Ejemplo
287
+ }
288
+
289
+ // Implementar otros métodos...
290
+ }
291
+ ```
292
+
293
+ ## Hooks en Modelos
294
+
295
+ Los modelos pueden participar en el sistema de hooks del framework:
296
+
297
+ ### Hooks Disponibles
298
+
299
+ - `model_before_create`: Antes de crear un registro
300
+ - `model_after_create`: Después de crear un registro
301
+ - `model_before_find`: Antes de buscar registros
302
+ - `model_after_find`: Después de buscar registros
303
+ - `model_before_update`: Antes de actualizar registros
304
+ - `model_after_update`: Después de actualizar registros
305
+ - `model_before_delete`: Antes de eliminar registros
306
+ - `model_after_delete`: Después de eliminar registros
307
+
308
+ ### Uso de Hooks
309
+
310
+ ```javascript
311
+ // Registrar un hook
312
+ hooks.addAction('model_after_create', (result, modelName) => {
313
+ console.log(`Registro creado en modelo ${modelName}:`, result);
314
+ });
315
+
316
+ // Filtrar datos antes de crear
317
+ hooks.addFilter('model_before_create', (data, modelName) => {
318
+ // Agregar marca de tiempo
319
+ return {
320
+ ...data,
321
+ createdAt: new Date(),
322
+ updatedAt: new Date()
323
+ };
324
+ });
325
+ ```
326
+
327
+ ## Ejemplos Prácticos
328
+
329
+ ### Ejemplo Completo de Modelo de Usuario
330
+
331
+ ```javascript
332
+ const { ModelBase } = require('jerkjs');
333
+
334
+ class UsuarioModel extends ModelBase {
335
+ constructor(options = {}) {
336
+ super({
337
+ ...options,
338
+ tableName: options.tableName || 'usuarios'
339
+ });
340
+
341
+ this.fields = {
342
+ id: { type: 'integer', primaryKey: true, autoIncrement: true },
343
+ username: { type: 'string', required: true, unique: true },
344
+ email: { type: 'string', required: true, unique: true },
345
+ password: { type: 'string', required: true },
346
+ rol: { type: 'string', default: 'usuario' },
347
+ activo: { type: 'boolean', default: true },
348
+ createdAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' },
349
+ updatedAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' }
350
+ };
351
+ }
352
+
353
+ validate(operation, data) {
354
+ const result = { isValid: true, errors: [] };
355
+
356
+ if (operation === 'create' || operation === 'update') {
357
+ if (operation === 'create' && !data.username) {
358
+ result.isValid = false;
359
+ result.errors.push('Username es requerido');
360
+ }
361
+
362
+ if (operation === 'create' && !data.email) {
363
+ result.isValid = false;
364
+ result.errors.push('Email es requerido');
365
+ }
366
+
367
+ if (operation === 'create' && !data.password) {
368
+ result.isValid = false;
369
+ result.errors.push('Password es requerido');
370
+ }
371
+
372
+ // Validar formato de email
373
+ if (data.email && !this.isValidEmail(data.email)) {
374
+ result.isValid = false;
375
+ result.errors.push('Formato de email inválido');
376
+ }
377
+ }
378
+
379
+ return result;
380
+ }
381
+
382
+ isValidEmail(email) {
383
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
384
+ return emailRegex.test(email);
385
+ }
386
+
387
+ async findByUsername(username) {
388
+ return await this.findOne({ username });
389
+ }
390
+
391
+ async findByEmail(email) {
392
+ return await this.findOne({ email });
393
+ }
394
+
395
+ async crearUsuario(datos) {
396
+ // Validar datos
397
+ const validacion = this.validate('create', datos);
398
+ if (!validacion.isValid) {
399
+ throw new Error(`Validación fallida: ${validacion.errors.join(', ')}`);
400
+ }
401
+
402
+ // Encriptar contraseña
403
+ if (datos.password) {
404
+ datos.password = await this.encriptarPassword(datos.password);
405
+ }
406
+
407
+ return await this.create(datos);
408
+ }
409
+
410
+ async encriptarPassword(password) {
411
+ const bcrypt = require('bcrypt');
412
+ const saltRounds = 10;
413
+ return await bcrypt.hash(password, saltRounds);
414
+ }
415
+
416
+ async verificarPassword(password, hashedPassword) {
417
+ const bcrypt = require('bcrypt');
418
+ return await bcrypt.compare(password, hashedPassword);
419
+ }
420
+ }
421
+ ```
422
+
423
+ ### Uso en un Controlador
424
+
425
+ ```javascript
426
+ const ControllerBase = require('jerkjs').ControllerBase;
427
+ const UsuarioModel = require('./models/UsuarioModel');
428
+ const { ModelManager, MemoryAdapter } = require('jerkjs');
429
+
430
+ class UsuarioController extends ControllerBase {
431
+ constructor() {
432
+ super();
433
+
434
+ // Configurar modelo de usuario
435
+ this.modelManager = new ModelManager();
436
+ const memoryAdapter = new MemoryAdapter();
437
+ this.modelManager.registerAdapter('memory', memoryAdapter);
438
+
439
+ this.usuarioModel = new UsuarioModel({
440
+ adapter: memoryAdapter,
441
+ tableName: 'usuarios'
442
+ });
443
+
444
+ // Registrar este controlador con el modelo
445
+ this.usuarioModel.registerController(this);
446
+ }
447
+
448
+ async crearUsuario(req, res) {
449
+ try {
450
+ const usuario = await this.usuarioModel.crearUsuario(req.body);
451
+ res.status(201).json({ success: true, data: usuario });
452
+ } catch (error) {
453
+ res.status(400).json({ success: false, error: error.message });
454
+ }
455
+ }
456
+
457
+ async obtenerUsuarioPorUsername(req, res) {
458
+ try {
459
+ const username = req.params.username;
460
+ const usuario = await this.usuarioModel.findByUsername(username);
461
+
462
+ if (!usuario) {
463
+ res.status(404).json({ success: false, error: 'Usuario no encontrado' });
464
+ return;
465
+ }
466
+
467
+ res.status(200).json({ success: true, data: usuario });
468
+ } catch (error) {
469
+ res.status(500).json({ success: false, error: error.message });
470
+ }
471
+ }
472
+
473
+ async listarUsuarios(req, res) {
474
+ try {
475
+ const usuarios = await this.usuarioModel.find({});
476
+ res.status(200).json({
477
+ success: true,
478
+ data: usuarios,
479
+ count: usuarios.length
480
+ });
481
+ } catch (error) {
482
+ res.status(500).json({ success: false, error: error.message });
483
+ }
484
+ }
485
+ }
486
+ ```
487
+
488
+ ## Uso de Modelos en Controladores con loadModel
489
+
490
+ JERK Framework proporciona un helper `loadModel` en el ControllerBase para facilitar la carga y uso de modelos en los controladores:
491
+
492
+ ```javascript
493
+ const ControllerBase = require('jerkjs').ControllerBase;
494
+
495
+ class ProductoController extends ControllerBase {
496
+ constructor() {
497
+ super();
498
+
499
+ // Cargar el modelo de producto
500
+ this.productoModel = this.loadModel('ProductoModel', {
501
+ // Opciones del modelo
502
+ });
503
+ }
504
+
505
+ async listarProductos(req, res) {
506
+ try {
507
+ // Usar el modelo para obtener productos
508
+ const productos = await this.productoModel.find({});
509
+
510
+ res.status(200).json({ success: true, data: productos });
511
+ } catch (error) {
512
+ res.status(500).json({ success: false, error: error.message });
513
+ }
514
+ }
515
+
516
+ async obtenerProductoPorId(req, res) {
517
+ try {
518
+ const productId = req.params.id;
519
+
520
+ // Usar el modelo para encontrar un producto específico
521
+ const producto = await this.productoModel.findOne({ id: productId });
522
+
523
+ if (!producto) {
524
+ res.status(404).json({ success: false, error: 'Producto no encontrado' });
525
+ return;
526
+ }
527
+
528
+ res.status(200).json({ success: true, data: producto });
529
+ } catch (error) {
530
+ res.status(500).json({ success: false, error: error.message });
531
+ }
532
+ }
533
+
534
+ async crearProducto(req, res) {
535
+ try {
536
+ // Usar el modelo para crear un nuevo producto
537
+ const nuevoProducto = await this.productoModel.create(req.body);
538
+
539
+ res.status(201).json({ success: true, data: nuevoProducto });
540
+ } catch (error) {
541
+ res.status(400).json({ success: false, error: error.message });
542
+ }
543
+ }
544
+ }
545
+
546
+ module.exports = new ProductoController();
547
+ ```
548
+
549
+ ### Métodos Disponibles
550
+
551
+ - `loadModel(modelName, options)`: Carga un modelo para su uso en el controlador
552
+ - `getModel(modelName)`: Obtiene un modelo previamente cargado
553
+
554
+ ### Rutas de Carga de Modelos
555
+
556
+ El helper `loadModel` busca modelos en las siguientes ubicaciones (en orden):
557
+
558
+ 1. `../../../models/${modelName}` (relativo a la carpeta actual)
559
+ 2. `../models/${modelName}` (relativo al controlador)
560
+ 3. `../../models/${modelName}` (relativo a la raíz del proyecto)
561
+
562
+ ## Conclusión
563
+
564
+ Los modelos en JERK Framework proporcionan una capa de abstracción potente y flexible para interactuar con datos. Con soporte para múltiples adaptadores, validación integrada, comunicación bidireccional con controladores y hooks para extensibilidad, los modelos permiten construir aplicaciones robustas y escalables.
565
+
566
+ La arquitectura modular permite adaptar los modelos a diferentes necesidades de almacenamiento y lógica de negocio, manteniendo al mismo tiempo una interfaz consistente y fácil de usar.