@mp-front/common 0.0.1 → 0.0.2-next.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.
- package/README.md +1006 -1
- package/dist/cache-providers/redis/redis-cache.d.ts +4 -4
- package/dist/cache-providers/redis/redis-cache.d.ts.map +1 -1
- package/dist/cache-providers/redis/types/index.d.ts +4 -4
- package/dist/cache-providers/redis/types/index.d.ts.map +1 -1
- package/dist/{client-Bv9OflsW.cjs → client-BVAQF7Tb.cjs} +1 -1
- package/dist/{client-Btua9wfS.js → client-DKI6Ntcl.js} +32 -28
- package/dist/mp-front-common-all.cjs +1 -1
- package/dist/mp-front-common-all.js +1 -1
- package/dist/mp-front-common-cache.cjs +1 -1
- package/dist/mp-front-common-cache.js +20 -20
- package/dist/mp-front-common-cacheProviders.cjs +1 -1
- package/dist/mp-front-common-cacheProviders.js +1 -1
- package/dist/mp-front-common-http.cjs +1 -1
- package/dist/mp-front-common-http.js +1 -1
- package/dist/mp-front-common-rxjs.cjs +1 -1
- package/dist/mp-front-common-rxjs.js +4 -2
- package/dist/{redis-cache-BoKAFYQi.js → redis-cache-B_SyXbUI.js} +10 -10
- package/dist/redis-cache-D9CzfYgO.cjs +1 -0
- package/dist/rxjs/index.d.ts +1 -0
- package/dist/rxjs/index.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/redis-cache-DjKTqllz.cjs +0 -1
package/README.md
CHANGED
|
@@ -1 +1,1006 @@
|
|
|
1
|
-
# front-
|
|
1
|
+
# @mp-front/common - Documentación Técnica
|
|
2
|
+
|
|
3
|
+
## 📚 Tabla de Contenidos
|
|
4
|
+
|
|
5
|
+
1. [Arquitectura General](#arquitectura-general)
|
|
6
|
+
2. [Módulos y Componentes](#módulos-y-componentes)
|
|
7
|
+
3. [Guías de Implementación](#guías-de-implementación)
|
|
8
|
+
4. [Configuración](#configuración)
|
|
9
|
+
5. [Casos de Uso Avanzados](#casos-de-uso-avanzados)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🏗️ Arquitectura General
|
|
14
|
+
|
|
15
|
+
La biblioteca `@mp-front/common` está diseñada con una arquitectura modular que permite importar solo los componentes necesarios, optimizando el tamaño del bundle final.
|
|
16
|
+
|
|
17
|
+
### Estructura de Directorios
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
├── adapters/ # Adaptadores NextAuth (Redis)
|
|
22
|
+
├── auth/ # Servicios de autenticación
|
|
23
|
+
├── cache/ # Gestión de caché
|
|
24
|
+
│ ├── session-handler/ # Manejo de sesiones
|
|
25
|
+
│ └── terminal-handler/ # Manejo de terminales
|
|
26
|
+
├── cache-providers/ # Proveedores de caché
|
|
27
|
+
│ └── redis/ # Implementación Redis
|
|
28
|
+
├── engine/ # Motor de autenticación Azure
|
|
29
|
+
├── errors/ # Sistema de errores
|
|
30
|
+
├── helpers/ # Utilidades generales
|
|
31
|
+
├── http/ # Cliente HTTP
|
|
32
|
+
├── middleware/ # Middlewares API
|
|
33
|
+
└── rxjs/ # Utilidades RxJS
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Principios de Diseño
|
|
37
|
+
|
|
38
|
+
- **Modularidad**: Cada módulo es independiente y puede importarse por separado
|
|
39
|
+
- **Programación Reactiva**: Uso extensivo de RxJS para operaciones asíncronas
|
|
40
|
+
- **Type Safety**: TypeScript completo con tipos estrictos
|
|
41
|
+
- **Singleton Pattern**: Para gestores globales (ErrorHandler, LoadingHandler)
|
|
42
|
+
- **Encriptación**: Seguridad en datos sensibles con node-jose
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📦 Módulos y Componentes
|
|
47
|
+
|
|
48
|
+
### 1. Auth Module (`@mp-front/common/auth`)
|
|
49
|
+
|
|
50
|
+
#### AuthorizationService
|
|
51
|
+
|
|
52
|
+
Servicio para obtener tokens de autenticación desde un backend.
|
|
53
|
+
|
|
54
|
+
**Características:**
|
|
55
|
+
|
|
56
|
+
- Autenticación mediante credenciales
|
|
57
|
+
- Retorna Observable con el token
|
|
58
|
+
- Logging integrado de requests/responses
|
|
59
|
+
|
|
60
|
+
**Implementación:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { AuthorizationService } from "@mp-front/common/auth"
|
|
64
|
+
|
|
65
|
+
const authService = new AuthorizationService()
|
|
66
|
+
|
|
67
|
+
// Obtener token
|
|
68
|
+
authService.get().subscribe({
|
|
69
|
+
next: token => {
|
|
70
|
+
console.log("Token obtenido:", token)
|
|
71
|
+
// Usar token en headers
|
|
72
|
+
},
|
|
73
|
+
error: error => {
|
|
74
|
+
console.error("Error de autenticación:", error)
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Variables de entorno requeridas:**
|
|
80
|
+
|
|
81
|
+
```env
|
|
82
|
+
API_AUTH_BACK_URL=https://api.example.com/auth
|
|
83
|
+
API_AUTH_BACK_USERNAME_AUTH=your_username
|
|
84
|
+
API_AUTH_BACK_PASSWORD_AUTH=your_password
|
|
85
|
+
ID_FRONT=frontend-app-id
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 2. Engine Module (`@mp-front/common/engine`)
|
|
91
|
+
|
|
92
|
+
#### AuthEngine
|
|
93
|
+
|
|
94
|
+
Motor de autenticación para Azure Entra ID (anteriormente Azure AD).
|
|
95
|
+
|
|
96
|
+
**Características:**
|
|
97
|
+
|
|
98
|
+
- Verificación de roles de usuario
|
|
99
|
+
- Verificación de grupos de usuario
|
|
100
|
+
- Integración con Microsoft Graph API
|
|
101
|
+
|
|
102
|
+
**Métodos principales:**
|
|
103
|
+
|
|
104
|
+
##### `getRoles(email: string, idObjApp: string)`
|
|
105
|
+
|
|
106
|
+
Obtiene los roles asignados a un usuario en una aplicación.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { AuthEngine } from "@mp-front/common/engine"
|
|
110
|
+
|
|
111
|
+
const authEngine = new AuthEngine()
|
|
112
|
+
|
|
113
|
+
authEngine.getRoles("user@example.com", "app-object-id").subscribe({
|
|
114
|
+
next: ({ roles }) => {
|
|
115
|
+
console.log("Roles del usuario:", roles)
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
##### `inRole(email: string, idRole: string, idObjApp: string)`
|
|
121
|
+
|
|
122
|
+
Verifica si un usuario tiene un rol específico.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
authEngine.inRole("user@example.com", "role-id", "app-object-id").subscribe({
|
|
126
|
+
next: hasRole => {
|
|
127
|
+
if (hasRole) {
|
|
128
|
+
console.log("Usuario tiene el rol")
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
##### `inGroup(email: string, idGroup: string)`
|
|
135
|
+
|
|
136
|
+
Verifica si un usuario pertenece a un grupo.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
authEngine.inGroup("user@example.com", "group-id").subscribe({
|
|
140
|
+
next: inGroup => {
|
|
141
|
+
if (inGroup) {
|
|
142
|
+
console.log("Usuario pertenece al grupo")
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Variables de entorno requeridas:**
|
|
149
|
+
|
|
150
|
+
```env
|
|
151
|
+
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
|
|
152
|
+
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
|
|
153
|
+
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOf
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### 3. HTTP Module (`@mp-front/common/http`)
|
|
159
|
+
|
|
160
|
+
#### HttpClient
|
|
161
|
+
|
|
162
|
+
Cliente HTTP basado en RxJS con características avanzadas.
|
|
163
|
+
|
|
164
|
+
**Características:**
|
|
165
|
+
|
|
166
|
+
- Métodos HTTP: GET, POST, PUT, PATCH, DELETE
|
|
167
|
+
- Encoding/Decoding automático de datos
|
|
168
|
+
- Manejo de loading state global
|
|
169
|
+
- Manejo de errores centralizado
|
|
170
|
+
- Logging de requests/responses
|
|
171
|
+
|
|
172
|
+
**Métodos:**
|
|
173
|
+
|
|
174
|
+
##### GET Request
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { HttpClient } from "@mp-front/common/http"
|
|
178
|
+
|
|
179
|
+
const client = new HttpClient()
|
|
180
|
+
|
|
181
|
+
interface User {
|
|
182
|
+
id: number
|
|
183
|
+
name: string
|
|
184
|
+
email: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
client.get<User>("/api/users/1").subscribe({
|
|
188
|
+
next: user => {
|
|
189
|
+
console.log("Usuario:", user)
|
|
190
|
+
},
|
|
191
|
+
error: error => {
|
|
192
|
+
console.error("Error:", error)
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Con parámetros
|
|
197
|
+
client
|
|
198
|
+
.get<User[]>("/api/users", {
|
|
199
|
+
params: { page: 1, limit: 10 },
|
|
200
|
+
})
|
|
201
|
+
.subscribe(users => {
|
|
202
|
+
console.log("Usuarios:", users)
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
##### POST Request
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
client
|
|
210
|
+
.post<User>("/api/users", {
|
|
211
|
+
name: "John Doe",
|
|
212
|
+
email: "john@example.com",
|
|
213
|
+
})
|
|
214
|
+
.subscribe(newUser => {
|
|
215
|
+
console.log("Usuario creado:", newUser)
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
##### PUT Request
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
client
|
|
223
|
+
.put<User>("/api/users/1", {
|
|
224
|
+
name: "Jane Doe",
|
|
225
|
+
})
|
|
226
|
+
.subscribe(updatedUser => {
|
|
227
|
+
console.log("Usuario actualizado:", updatedUser)
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
##### DELETE Request
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
client.delete("/api/users/1").subscribe(() => {
|
|
235
|
+
console.log("Usuario eliminado")
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
##### Deshabilitar Loading Global
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const client = new HttpClient()
|
|
243
|
+
client.setIsLoadingEnabled(false)
|
|
244
|
+
|
|
245
|
+
// Este request no activará el loading global
|
|
246
|
+
client.get("/api/data").subscribe(data => {
|
|
247
|
+
console.log(data)
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### 4. Cache Module (`@mp-front/common/cache`)
|
|
254
|
+
|
|
255
|
+
#### SessionCache
|
|
256
|
+
|
|
257
|
+
Gestión de sesiones de usuario con Redis.
|
|
258
|
+
|
|
259
|
+
**Características:**
|
|
260
|
+
|
|
261
|
+
- Almacenamiento de sesión en Redis
|
|
262
|
+
- Renovación automática de timeout
|
|
263
|
+
- Integración con NextAuth
|
|
264
|
+
- Datos de usuario y tienda
|
|
265
|
+
|
|
266
|
+
**Métodos:**
|
|
267
|
+
|
|
268
|
+
##### `getBasicSession()`
|
|
269
|
+
|
|
270
|
+
Obtiene la sesión básica del usuario.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { SessionCache } from "@mp-front/common/cache"
|
|
274
|
+
|
|
275
|
+
const sessionCache = new SessionCache("user-id-123")
|
|
276
|
+
|
|
277
|
+
const session = await sessionCache.getBasicSession()
|
|
278
|
+
|
|
279
|
+
console.log("Usuario:", session.user)
|
|
280
|
+
// {
|
|
281
|
+
// name: 'John Doe',
|
|
282
|
+
// email: 'john@example.com',
|
|
283
|
+
// image: 'https://...',
|
|
284
|
+
// cveUsuario: '12345',
|
|
285
|
+
// rol: 'admin',
|
|
286
|
+
// appGroups: ['group1', 'group2']
|
|
287
|
+
// }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
##### `getUserAndShoppingStore()`
|
|
291
|
+
|
|
292
|
+
Obtiene sesión completa con datos de tienda.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const fullSession = await sessionCache.getUserAndShoppingStore()
|
|
296
|
+
|
|
297
|
+
console.log("Sesión completa:", fullSession)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### TerminalCache
|
|
301
|
+
|
|
302
|
+
Gestión de datos de terminal con Redis.
|
|
303
|
+
|
|
304
|
+
**Características:**
|
|
305
|
+
|
|
306
|
+
- Almacenamiento por clave de vendedor
|
|
307
|
+
- Generación de UUID único por vendedor
|
|
308
|
+
- Timeout configurable
|
|
309
|
+
|
|
310
|
+
**Métodos:**
|
|
311
|
+
|
|
312
|
+
##### `set(sellerKey: string, terminal: TerminalValue)`
|
|
313
|
+
|
|
314
|
+
Guarda datos de terminal.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { TerminalCache } from "@mp-front/common/cache"
|
|
318
|
+
|
|
319
|
+
const terminalCache = new TerminalCache()
|
|
320
|
+
|
|
321
|
+
await terminalCache.set("SELLER001", {
|
|
322
|
+
terminalId: "TERM123",
|
|
323
|
+
location: "Store A",
|
|
324
|
+
status: "active",
|
|
325
|
+
})
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
##### `get(sellerKey: string)`
|
|
329
|
+
|
|
330
|
+
Obtiene datos de terminal.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const terminal = await terminalCache.get("SELLER001")
|
|
334
|
+
|
|
335
|
+
if (terminal) {
|
|
336
|
+
console.log("Terminal:", terminal)
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
##### `delete(sellerKey: string)`
|
|
341
|
+
|
|
342
|
+
Elimina datos de terminal.
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
await terminalCache.delete("SELLER001")
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
### 5. Cache Providers Module (`@mp-front/common/cache-providers`)
|
|
351
|
+
|
|
352
|
+
#### RedisCache
|
|
353
|
+
|
|
354
|
+
Proveedor genérico de caché con Redis.
|
|
355
|
+
|
|
356
|
+
**Características:**
|
|
357
|
+
|
|
358
|
+
- Operaciones CRUD completas
|
|
359
|
+
- Encriptación opcional
|
|
360
|
+
- TTL (Time To Live) configurable
|
|
361
|
+
- Soporte para tipos genéricos
|
|
362
|
+
|
|
363
|
+
**Métodos:**
|
|
364
|
+
|
|
365
|
+
##### `set(params: ISetStateParams<T>)`
|
|
366
|
+
|
|
367
|
+
Guarda datos en Redis.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { RedisCache } from "@mp-front/common/cache-providers"
|
|
371
|
+
|
|
372
|
+
interface UserData {
|
|
373
|
+
name: string
|
|
374
|
+
email: string
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const redis = new RedisCache<UserData>()
|
|
378
|
+
|
|
379
|
+
await redis.set({
|
|
380
|
+
prefix: "user:123",
|
|
381
|
+
idData: "profile",
|
|
382
|
+
body: {
|
|
383
|
+
name: "John Doe",
|
|
384
|
+
email: "john@example.com",
|
|
385
|
+
},
|
|
386
|
+
expire: 3600, // 1 hora en segundos
|
|
387
|
+
encrypted: true, // Opcional: encriptar datos
|
|
388
|
+
})
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
##### `get<T>(prefix: string, idData: string)`
|
|
392
|
+
|
|
393
|
+
Obtiene datos de Redis.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const { data, sha } = await redis.get<UserData>("user:123", "profile")
|
|
397
|
+
|
|
398
|
+
console.log("Datos:", data)
|
|
399
|
+
console.log("SHA key:", sha)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
##### `delete(prefix: string, idData: string)`
|
|
403
|
+
|
|
404
|
+
Elimina datos de Redis.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
await redis.delete("user:123", "profile")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
##### Operaciones simples
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// GET simple
|
|
414
|
+
const value = await redis.simpleGet("key")
|
|
415
|
+
|
|
416
|
+
// HGET (hash get)
|
|
417
|
+
const fieldValue = await redis.simpleHGet("hash-key", "field")
|
|
418
|
+
|
|
419
|
+
// HSET (hash set)
|
|
420
|
+
await redis.simpleHSet("hash-key", "field", "value")
|
|
421
|
+
|
|
422
|
+
// HGETALL (obtener todo el hash)
|
|
423
|
+
const allFields = await redis.simpleHGetAll("hash-key")
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Variables de entorno:**
|
|
427
|
+
|
|
428
|
+
```env
|
|
429
|
+
REDIS_URL=redis://localhost:6379
|
|
430
|
+
TIMEOUT_SESSION_MINUTES=60
|
|
431
|
+
SECRET_SIGNATURE=your-secret-key-for-encryption
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
### 6. Helpers Module (`@mp-front/common/helpers`)
|
|
437
|
+
|
|
438
|
+
#### Logger
|
|
439
|
+
|
|
440
|
+
Sistema de logging con niveles y colores.
|
|
441
|
+
|
|
442
|
+
**Niveles de log:**
|
|
443
|
+
|
|
444
|
+
- `error`: Errores críticos
|
|
445
|
+
- `warn`: Advertencias
|
|
446
|
+
- `info`: Información general
|
|
447
|
+
- `http`: Logs HTTP
|
|
448
|
+
- `verbose`: Información detallada
|
|
449
|
+
- `debug`: Debugging
|
|
450
|
+
- `silly`: Todo
|
|
451
|
+
|
|
452
|
+
**Uso:**
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { Logger } from "@mp-front/common/helpers"
|
|
456
|
+
|
|
457
|
+
const logger = new Logger()
|
|
458
|
+
|
|
459
|
+
logger.logError("Error crítico", JSON.stringify(error))
|
|
460
|
+
logger.logWarn("Advertencia: API deprecated")
|
|
461
|
+
logger.logInfo("Usuario autenticado correctamente")
|
|
462
|
+
logger.logDebug("Datos de request:", JSON.stringify(data))
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Configuración:**
|
|
466
|
+
|
|
467
|
+
```env
|
|
468
|
+
NEXT_PUBLIC_APP_LOGS_NAME=MyApp
|
|
469
|
+
NEXT_PUBLIC_LOGS_LEVEL=debug
|
|
470
|
+
NEXT_PUBLIC_SILENT_LOGS=false
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### Encoder
|
|
474
|
+
|
|
475
|
+
Codificación/decodificación de datos en Base64.
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
import { Encoder } from "@mp-front/common/helpers"
|
|
479
|
+
|
|
480
|
+
const encoder = new Encoder()
|
|
481
|
+
|
|
482
|
+
// Codificar
|
|
483
|
+
const encoded = encoder.encode({ name: "John" }, "request-id-123")
|
|
484
|
+
// Resultado: { info: "base64...", requestID: "request-id-123" }
|
|
485
|
+
|
|
486
|
+
// Decodificar
|
|
487
|
+
const decoded = encoder.decode(encoded)
|
|
488
|
+
// Resultado: { name: 'John' }
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
#### Encrypter
|
|
492
|
+
|
|
493
|
+
Encriptación/desencriptación con node-jose (JWE).
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
import { Encrypter } from "@mp-front/common/helpers"
|
|
497
|
+
|
|
498
|
+
const encrypter = new Encrypter()
|
|
499
|
+
|
|
500
|
+
// Encriptar
|
|
501
|
+
const encrypted = await encrypter.encrypt({
|
|
502
|
+
password: "secret123",
|
|
503
|
+
apiKey: "key-xyz",
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
// Desencriptar
|
|
507
|
+
const decrypted = await encrypter.decrypt(encrypted)
|
|
508
|
+
|
|
509
|
+
// Verificar si está encriptado
|
|
510
|
+
const isEncrypted = await encrypter.isEncrypted(someString)
|
|
511
|
+
|
|
512
|
+
// Generar SHA256
|
|
513
|
+
const sha = encrypter.generateSHA({ userId: "123" })
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### 7. Errors Module (`@mp-front/common/errors`)
|
|
519
|
+
|
|
520
|
+
#### ErrorHandler
|
|
521
|
+
|
|
522
|
+
Gestor global de errores con patrón Singleton.
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { ErrorHandler } from "@mp-front/common/errors"
|
|
526
|
+
|
|
527
|
+
// Suscribirse a errores globales
|
|
528
|
+
ErrorHandler.getInstance()
|
|
529
|
+
.getSubject()
|
|
530
|
+
.subscribe(error => {
|
|
531
|
+
console.error("Error global:", error)
|
|
532
|
+
|
|
533
|
+
// Mostrar notificación
|
|
534
|
+
showNotification({
|
|
535
|
+
type: "error",
|
|
536
|
+
message: error.message,
|
|
537
|
+
code: error.code,
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### RuntimeError
|
|
543
|
+
|
|
544
|
+
Clase de error personalizada.
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
import { RuntimeError, RuntimeErrorCode } from "@mp-front/common/errors"
|
|
548
|
+
|
|
549
|
+
// Lanzar error
|
|
550
|
+
throw new RuntimeError(RuntimeErrorCode.VALIDATION_ERROR, "request-id-123")
|
|
551
|
+
|
|
552
|
+
// En un catch
|
|
553
|
+
try {
|
|
554
|
+
// código
|
|
555
|
+
} catch (error) {
|
|
556
|
+
throw new RuntimeError("CUSTOM_ERROR", requestId)
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
### 8. RxJS Module (`@mp-front/common/rxjs`)
|
|
563
|
+
|
|
564
|
+
#### LoadingHandler
|
|
565
|
+
|
|
566
|
+
Gestor global de estado de carga.
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import { LoadingHandler } from "@mp-front/common/rxjs"
|
|
570
|
+
|
|
571
|
+
// Suscribirse al estado de loading
|
|
572
|
+
LoadingHandler.getInstance()
|
|
573
|
+
.getSubject()
|
|
574
|
+
.subscribe(isLoading => {
|
|
575
|
+
if (isLoading) {
|
|
576
|
+
showSpinner()
|
|
577
|
+
} else {
|
|
578
|
+
hideSpinner()
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
// Activar loading manualmente
|
|
583
|
+
LoadingHandler.getInstance().setSubject(true)
|
|
584
|
+
|
|
585
|
+
// Desactivar loading
|
|
586
|
+
LoadingHandler.getInstance().setSubject(false)
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Integración con React:**
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
import { useEffect, useState } from 'react'
|
|
593
|
+
import { LoadingHandler } from '@mp-front/common/rxjs'
|
|
594
|
+
|
|
595
|
+
function App() {
|
|
596
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
597
|
+
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
const subscription = LoadingHandler.getInstance()
|
|
600
|
+
.getSubject()
|
|
601
|
+
.subscribe(setIsLoading)
|
|
602
|
+
|
|
603
|
+
return () => subscription.unsubscribe()
|
|
604
|
+
}, [])
|
|
605
|
+
|
|
606
|
+
return (
|
|
607
|
+
<>
|
|
608
|
+
{isLoading && <Spinner />}
|
|
609
|
+
{/* resto de la app */}
|
|
610
|
+
</>
|
|
611
|
+
)
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### 9. Middleware Module (`@mp-front/common/middleware`)
|
|
618
|
+
|
|
619
|
+
#### ApiMiddleware
|
|
620
|
+
|
|
621
|
+
Middleware para APIs de Next.js con manejo de sesión.
|
|
622
|
+
|
|
623
|
+
**Características:**
|
|
624
|
+
|
|
625
|
+
- Encoding/Decoding automático
|
|
626
|
+
- Manejo de errores
|
|
627
|
+
- Logging de requests
|
|
628
|
+
- Soporte para archivos
|
|
629
|
+
|
|
630
|
+
**Uso básico:**
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
import { ApiMiddleware } from "@mp-front/common/middleware"
|
|
634
|
+
|
|
635
|
+
class MyApiMiddleware extends ApiMiddleware {
|
|
636
|
+
constructor() {
|
|
637
|
+
super()
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const middleware = new MyApiMiddleware()
|
|
642
|
+
|
|
643
|
+
// Configurar sesión
|
|
644
|
+
middleware.setSession(session)
|
|
645
|
+
|
|
646
|
+
// Handler para GET/POST
|
|
647
|
+
export const POST = middleware.get<RequestType, ResponseType>(
|
|
648
|
+
(params, { requestID, headers }) => {
|
|
649
|
+
// Lógica de negocio
|
|
650
|
+
return of({
|
|
651
|
+
success: true,
|
|
652
|
+
data: processedData,
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
)
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Handler para archivos:**
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
export const POST = middleware.getFile<ResponseType>(file => {
|
|
662
|
+
// Procesar archivo
|
|
663
|
+
return of({
|
|
664
|
+
filename: file.name,
|
|
665
|
+
size: file.size,
|
|
666
|
+
})
|
|
667
|
+
})
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
### 10. Adapters Module (`@mp-front/common/adapters`)
|
|
673
|
+
|
|
674
|
+
#### IORedisAdapter
|
|
675
|
+
|
|
676
|
+
Adaptador de Redis para NextAuth.
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
import { IORedisAdapter } from "@mp-front/common/adapters"
|
|
680
|
+
|
|
681
|
+
export const authOptions = {
|
|
682
|
+
adapter: IORedisAdapter({
|
|
683
|
+
expire: 3600, // segundos
|
|
684
|
+
}),
|
|
685
|
+
// ... otras opciones
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## ⚙️ Configuración Completa
|
|
692
|
+
|
|
693
|
+
### Variables de Entorno
|
|
694
|
+
|
|
695
|
+
```env
|
|
696
|
+
# Logging
|
|
697
|
+
NEXT_PUBLIC_APP_LOGS_NAME=MyApp
|
|
698
|
+
NEXT_PUBLIC_LOGS_LEVEL=debug
|
|
699
|
+
NEXT_PUBLIC_SILENT_LOGS=false
|
|
700
|
+
|
|
701
|
+
# Authentication Backend
|
|
702
|
+
API_AUTH_BACK_URL=https://api.example.com/auth
|
|
703
|
+
API_AUTH_BACK_USERNAME_AUTH=username
|
|
704
|
+
API_AUTH_BACK_PASSWORD_AUTH=password
|
|
705
|
+
ID_FRONT=frontend-app-id
|
|
706
|
+
|
|
707
|
+
# Azure Entra ID
|
|
708
|
+
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
|
|
709
|
+
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
|
|
710
|
+
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOf
|
|
711
|
+
|
|
712
|
+
# Redis
|
|
713
|
+
REDIS_URL=redis://localhost:6379
|
|
714
|
+
TIMEOUT_SESSION_MINUTES=60
|
|
715
|
+
PREFIX_LOGIN=app
|
|
716
|
+
|
|
717
|
+
# Encryption
|
|
718
|
+
SECRET_SIGNATURE=your-secret-key-min-32-chars
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
## 🚀 Casos de Uso Avanzados
|
|
724
|
+
|
|
725
|
+
### 1. Sistema de Autenticación Completo
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
import { AuthorizationService } from "@mp-front/common/auth"
|
|
729
|
+
import { HttpClient } from "@mp-front/common/http"
|
|
730
|
+
import { SessionCache } from "@mp-front/common/cache"
|
|
731
|
+
|
|
732
|
+
class AuthSystem {
|
|
733
|
+
private authService = new AuthorizationService()
|
|
734
|
+
private httpClient = new HttpClient()
|
|
735
|
+
private sessionCache: SessionCache
|
|
736
|
+
|
|
737
|
+
async login(userId: string) {
|
|
738
|
+
// Obtener token
|
|
739
|
+
const token = await firstValueFrom(this.authService.get())
|
|
740
|
+
|
|
741
|
+
// Configurar sesión
|
|
742
|
+
this.sessionCache = new SessionCache(userId)
|
|
743
|
+
const session = await this.sessionCache.getBasicSession()
|
|
744
|
+
|
|
745
|
+
// Hacer request autenticado
|
|
746
|
+
this.httpClient
|
|
747
|
+
.get("/api/protected", {
|
|
748
|
+
headers: {
|
|
749
|
+
Authorization: `Bearer ${token}`,
|
|
750
|
+
},
|
|
751
|
+
})
|
|
752
|
+
.subscribe(data => {
|
|
753
|
+
console.log("Datos protegidos:", data)
|
|
754
|
+
})
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### 2. Sistema de Caché Multinivel
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
import { RedisCache } from "@mp-front/common/cache-providers"
|
|
763
|
+
import { Encrypter } from "@mp-front/common/helpers"
|
|
764
|
+
|
|
765
|
+
class CacheSystem<T> {
|
|
766
|
+
private redis = new RedisCache<T>()
|
|
767
|
+
private encrypter = new Encrypter()
|
|
768
|
+
private memoryCache = new Map<string, T>()
|
|
769
|
+
|
|
770
|
+
async get(key: string): Promise<T | null> {
|
|
771
|
+
// Nivel 1: Memoria
|
|
772
|
+
if (this.memoryCache.has(key)) {
|
|
773
|
+
return this.memoryCache.get(key)!
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Nivel 2: Redis
|
|
777
|
+
try {
|
|
778
|
+
const { data } = await this.redis.get<T>(key, "data")
|
|
779
|
+
this.memoryCache.set(key, data)
|
|
780
|
+
return data
|
|
781
|
+
} catch {
|
|
782
|
+
return null
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async set(key: string, value: T, ttl: number = 3600) {
|
|
787
|
+
// Guardar en memoria
|
|
788
|
+
this.memoryCache.set(key, value)
|
|
789
|
+
|
|
790
|
+
// Guardar en Redis
|
|
791
|
+
await this.redis.set({
|
|
792
|
+
prefix: key,
|
|
793
|
+
idData: "data",
|
|
794
|
+
body: value,
|
|
795
|
+
expire: ttl,
|
|
796
|
+
encrypted: true,
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### 3. Sistema de Manejo de Errores Global
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
import { ErrorHandler, RuntimeError } from "@mp-front/common/errors"
|
|
806
|
+
import { LoadingHandler } from "@mp-front/common/rxjs"
|
|
807
|
+
|
|
808
|
+
class GlobalErrorSystem {
|
|
809
|
+
constructor() {
|
|
810
|
+
this.setupErrorHandling()
|
|
811
|
+
this.setupLoadingHandling()
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private setupErrorHandling() {
|
|
815
|
+
ErrorHandler.getInstance()
|
|
816
|
+
.getSubject()
|
|
817
|
+
.subscribe(error => {
|
|
818
|
+
// Ocultar loading
|
|
819
|
+
LoadingHandler.getInstance().setSubject(false)
|
|
820
|
+
|
|
821
|
+
// Log error
|
|
822
|
+
console.error("Error:", error)
|
|
823
|
+
|
|
824
|
+
// Mostrar notificación
|
|
825
|
+
this.showErrorNotification(error)
|
|
826
|
+
|
|
827
|
+
// Enviar a servicio de tracking
|
|
828
|
+
this.trackError(error)
|
|
829
|
+
})
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
private setupLoadingHandling() {
|
|
833
|
+
LoadingHandler.getInstance()
|
|
834
|
+
.getSubject()
|
|
835
|
+
.subscribe(isLoading => {
|
|
836
|
+
document.body.classList.toggle("loading", isLoading)
|
|
837
|
+
})
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private showErrorNotification(error: RuntimeError) {
|
|
841
|
+
// Implementación de notificación
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private trackError(error: RuntimeError) {
|
|
845
|
+
// Enviar a Sentry, DataDog, etc.
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### 4. API Middleware con Validación
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
import { ApiMiddleware } from "@mp-front/common/middleware"
|
|
854
|
+
import { RuntimeError } from "@mp-front/common/errors"
|
|
855
|
+
import { of, throwError } from "rxjs"
|
|
856
|
+
|
|
857
|
+
interface CreateUserRequest {
|
|
858
|
+
name: string
|
|
859
|
+
email: string
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
interface CreateUserResponse {
|
|
863
|
+
id: string
|
|
864
|
+
name: string
|
|
865
|
+
email: string
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
class UserApiMiddleware extends ApiMiddleware {
|
|
869
|
+
createUser = this.get<CreateUserRequest, CreateUserResponse>(
|
|
870
|
+
(params, { requestID }) => {
|
|
871
|
+
// Validación
|
|
872
|
+
if (!params.name || !params.email) {
|
|
873
|
+
throw new RuntimeError("VALIDATION_ERROR", requestID)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Obtener sesión
|
|
877
|
+
const session = this.getSession()
|
|
878
|
+
|
|
879
|
+
// Lógica de negocio
|
|
880
|
+
const newUser = {
|
|
881
|
+
id: generateId(),
|
|
882
|
+
name: params.name,
|
|
883
|
+
email: params.email,
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return of(newUser)
|
|
887
|
+
}
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export const POST = new UserApiMiddleware().createUser
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## 📝 Mejores Prácticas
|
|
897
|
+
|
|
898
|
+
### 1. Manejo de Suscripciones RxJS
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
import { Component, OnDestroy } from "@angular/core"
|
|
902
|
+
import { Subject, takeUntil } from "rxjs"
|
|
903
|
+
|
|
904
|
+
class MyComponent implements OnDestroy {
|
|
905
|
+
private destroy$ = new Subject<void>()
|
|
906
|
+
|
|
907
|
+
ngOnInit() {
|
|
908
|
+
this.httpClient
|
|
909
|
+
.get("/api/data")
|
|
910
|
+
.pipe(takeUntil(this.destroy$))
|
|
911
|
+
.subscribe(data => {
|
|
912
|
+
// Procesar datos
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
ngOnDestroy() {
|
|
917
|
+
this.destroy$.next()
|
|
918
|
+
this.destroy$.complete()
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### 2. Tipado Estricto
|
|
924
|
+
|
|
925
|
+
```typescript
|
|
926
|
+
// Definir interfaces
|
|
927
|
+
interface User {
|
|
928
|
+
id: string
|
|
929
|
+
name: string
|
|
930
|
+
email: string
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Usar tipos genéricos
|
|
934
|
+
const redis = new RedisCache<User>()
|
|
935
|
+
const client = new HttpClient()
|
|
936
|
+
|
|
937
|
+
client.get<User>("/api/user/1").subscribe(user => {
|
|
938
|
+
// TypeScript sabe que user es de tipo User
|
|
939
|
+
console.log(user.name)
|
|
940
|
+
})
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### 3. Manejo de Errores
|
|
944
|
+
|
|
945
|
+
```typescript
|
|
946
|
+
import { catchError, of } from "rxjs"
|
|
947
|
+
|
|
948
|
+
client
|
|
949
|
+
.get<Data>("/api/data")
|
|
950
|
+
.pipe(
|
|
951
|
+
catchError(error => {
|
|
952
|
+
console.error("Error:", error)
|
|
953
|
+
return of(null) // Valor por defecto
|
|
954
|
+
})
|
|
955
|
+
)
|
|
956
|
+
.subscribe(data => {
|
|
957
|
+
if (data) {
|
|
958
|
+
// Procesar datos
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
---
|
|
964
|
+
|
|
965
|
+
## 🔧 Troubleshooting
|
|
966
|
+
|
|
967
|
+
### Problema: Redis no conecta
|
|
968
|
+
|
|
969
|
+
**Solución:**
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
import { RedisCache } from "@mp-front/common/cache-providers"
|
|
973
|
+
|
|
974
|
+
const redis = new RedisCache()
|
|
975
|
+
|
|
976
|
+
redis
|
|
977
|
+
.statusHost()
|
|
978
|
+
.then(() => {
|
|
979
|
+
console.log("Redis conectado")
|
|
980
|
+
})
|
|
981
|
+
.catch(error => {
|
|
982
|
+
console.error("Error de conexión:", error)
|
|
983
|
+
})
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### Problema: Logs no aparecen
|
|
987
|
+
|
|
988
|
+
**Verificar:**
|
|
989
|
+
|
|
990
|
+
1. `NEXT_PUBLIC_SILENT_LOGS=false`
|
|
991
|
+
2. `NEXT_PUBLIC_LOGS_LEVEL` está configurado correctamente
|
|
992
|
+
3. El nivel del log es mayor o igual al configurado
|
|
993
|
+
|
|
994
|
+
### Problema: Encriptación falla
|
|
995
|
+
|
|
996
|
+
**Verificar:**
|
|
997
|
+
|
|
998
|
+
1. `SECRET_SIGNATURE` está definido en `.env`
|
|
999
|
+
2. La clave tiene al menos 32 caracteres
|
|
1000
|
+
3. La clave es la misma para encriptar y desencriptar
|
|
1001
|
+
|
|
1002
|
+
---
|
|
1003
|
+
|
|
1004
|
+
## 📄 Licencia
|
|
1005
|
+
|
|
1006
|
+
Privada - Uso interno únicamente
|