@nocios/crudify-components 2.0.61 → 2.0.88

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.
Files changed (85) hide show
  1. package/README.md +21 -0
  2. package/dist/api-jcCTCqPD.d.mts +61 -0
  3. package/dist/api-jcCTCqPD.d.ts +61 -0
  4. package/dist/chunk-4I767MRM.mjs +142 -0
  5. package/dist/chunk-5V5ILPP3.js +1 -0
  6. package/dist/chunk-BJEDX2HU.js +142 -0
  7. package/dist/chunk-GZOB4JDB.js +1 -0
  8. package/dist/chunk-ODKXUEH5.js +1 -0
  9. package/dist/chunk-U335FNUD.mjs +1 -0
  10. package/dist/chunk-YRU7AXYV.mjs +1 -0
  11. package/dist/chunk-Z75DRSTN.mjs +1 -0
  12. package/dist/{errorTranslation-CF-5JClP.d.ts → errorTranslation-BdqG-dXD.d.ts} +97 -26
  13. package/dist/{errorTranslation-BcX8AaK7.d.mts → errorTranslation-BxJmBGx0.d.mts} +97 -26
  14. package/dist/hooks.d.mts +3 -4
  15. package/dist/hooks.d.ts +3 -4
  16. package/dist/hooks.js +1 -1
  17. package/dist/hooks.mjs +1 -1
  18. package/dist/{index-D06kTP0C.d.mts → index-Cx9P3ZCG.d.mts} +225 -139
  19. package/dist/{index-DEDnmsdO.d.ts → index-rHtWMls-.d.ts} +225 -139
  20. package/dist/index.d.mts +508 -213
  21. package/dist/index.d.ts +508 -213
  22. package/dist/index.js +2 -2
  23. package/dist/index.mjs +2 -2
  24. package/dist/public-policies.d.mts +7 -0
  25. package/dist/public-policies.d.ts +7 -0
  26. package/dist/public-policies.js +1 -0
  27. package/dist/public-policies.mjs +1 -0
  28. package/dist/utils.d.mts +94 -26
  29. package/dist/utils.d.ts +94 -26
  30. package/dist/utils.js +1 -1
  31. package/dist/utils.mjs +1 -1
  32. package/package.json +44 -42
  33. package/.github/workflows/ci.yml +0 -70
  34. package/.husky/pre-commit +0 -26
  35. package/.husky/pre-push +0 -30
  36. package/.nvmrc +0 -1
  37. package/.prettierignore +0 -18
  38. package/.prettierrc +0 -9
  39. package/README_DEPTH.md +0 -1237
  40. package/coverage/base.css +0 -224
  41. package/coverage/block-navigation.js +0 -87
  42. package/coverage/coverage-final.json +0 -120
  43. package/coverage/favicon.png +0 -0
  44. package/coverage/index.html +0 -686
  45. package/coverage/lcov-report/base.css +0 -224
  46. package/coverage/lcov-report/block-navigation.js +0 -87
  47. package/coverage/lcov-report/favicon.png +0 -0
  48. package/coverage/lcov-report/index.html +0 -686
  49. package/coverage/lcov-report/prettify.css +0 -1
  50. package/coverage/lcov-report/prettify.js +0 -2
  51. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  52. package/coverage/lcov-report/sorter.js +0 -210
  53. package/coverage/lcov.info +0 -22388
  54. package/coverage/prettify.css +0 -1
  55. package/coverage/prettify.js +0 -2
  56. package/coverage/sort-arrow-sprite.png +0 -0
  57. package/coverage/sorter.js +0 -210
  58. package/dist/CrudiaMarkdownField-CkiBwG-U.d.ts +0 -343
  59. package/dist/CrudiaMarkdownField-D-DqiXMQ.d.mts +0 -343
  60. package/dist/GlobalNotificationProvider-CdwdNv_8.d.mts +0 -96
  61. package/dist/GlobalNotificationProvider-CdwdNv_8.d.ts +0 -96
  62. package/dist/chunk-2WAUZ6KI.js +0 -1
  63. package/dist/chunk-3IGZNZCT.mjs +0 -1
  64. package/dist/chunk-43L2PP77.mjs +0 -1
  65. package/dist/chunk-6VS5OT3A.mjs +0 -1
  66. package/dist/chunk-BWJTTMKS.js +0 -1
  67. package/dist/chunk-EMPPCCVU.js +0 -1
  68. package/dist/chunk-J43UPGBE.js +0 -1
  69. package/dist/chunk-K6ZRXOJ7.mjs +0 -1
  70. package/dist/chunk-RYQQZTEP.js +0 -1
  71. package/dist/chunk-VTMSOK4V.mjs +0 -1
  72. package/dist/components.d.mts +0 -24
  73. package/dist/components.d.ts +0 -24
  74. package/dist/components.js +0 -1
  75. package/dist/components.mjs +0 -1
  76. package/dist/tenantConfig-CYnS9TPV.d.mts +0 -318
  77. package/dist/tenantConfig-CYnS9TPV.d.ts +0 -318
  78. package/eslint.config.mjs +0 -140
  79. package/scripts/bump-version.cjs +0 -23
  80. package/sonar-project.properties +0 -23
  81. package/tests/helpers/testUtils.tsx +0 -89
  82. package/tests/hooks/useSession/testUtils.tsx +0 -212
  83. package/tests/setup.ts +0 -139
  84. package/tests/vitest.d.ts +0 -1
  85. package/vitest.config.ts +0 -39
package/README_DEPTH.md DELETED
@@ -1,1237 +0,0 @@
1
- # @nocios/crudify-components - Documentación Completa
2
-
3
- [![npm version](https://badge.fury.io/js/%40nocios%2Fcrudify-ui.svg)](https://badge.fury.io/js/%40nocios%2Fcrudify-ui)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
-
6
- **@nocios/crudify-components** es una biblioteca completa de componentes UI y hooks de React para aplicaciones que utilizan el sistema Crudify. Proporciona autenticación moderna con Refresh Token Pattern, manejo de sesiones, componentes de UI listos para usar y una API unificada.
7
-
8
- ## 🚀 Características Principales
9
-
10
- - **🔐 Autenticación Moderna**: Sistema completo con Refresh Token Pattern
11
- - **⚡ Manejo de Sesiones**: Persistencia automática y sincronización cross-tab
12
- - **🎨 Componentes UI**: Componentes React pre-construidos con Material-UI
13
- - **🪝 React Hooks**: Hooks especializados para operaciones CRUD y autenticación
14
- - **🔄 API Unificada**: Re-exporta completamente @nocios/crudify-sdk
15
- - **🌍 Internacionalización**: Soporte completo para múltiples idiomas
16
- - **💾 Almacenamiento Seguro**: Encriptación de tokens con múltiples opciones de storage
17
- - **📱 TypeScript**: Completamente tipado para mejor experiencia de desarrollo
18
-
19
- ## 📦 Instalación
20
-
21
- ```bash
22
- npm install @nocios/crudify-components
23
-
24
- # Peer dependencies (si no las tienes instaladas)
25
- npm install @mui/material @mui/icons-material react react-dom
26
- ```
27
-
28
- ### Peer Dependencies Requeridas
29
-
30
- ```json
31
- {
32
- "@mui/icons-material": "^7.1.0",
33
- "@mui/material": "^7.1.0",
34
- "react": "^19.1.0",
35
- "react-dom": "^19.1.0"
36
- }
37
- ```
38
-
39
- ## 🏗️ Configuración Inicial
40
-
41
- ### 1. Configurar SessionProvider
42
-
43
- El `SessionProvider` es el punto de entrada principal para manejar la autenticación y sesiones en tu aplicación:
44
-
45
- ```tsx
46
- import React from 'react';
47
- import { SessionProvider } from '@nocios/crudify-components';
48
-
49
- function App() {
50
- return (
51
- <SessionProvider
52
- options={{
53
- // Configuración automática basada en variables de entorno
54
- // o configuración manual si es necesario
55
- storageType: 'localStorage', // 'localStorage' | 'sessionStorage'
56
- onSessionExpired: () => {
57
- console.log('Sesión expirada');
58
- },
59
- onSessionRestored: (tokens) => {
60
- console.log('Sesión restaurada:', tokens);
61
- },
62
- }}
63
- >
64
- <YourApp />
65
- </SessionProvider>
66
- );
67
- }
68
- ```
69
-
70
- ### 2. Variables de Entorno
71
-
72
- La biblioteca detecta automáticamente la configuración desde las variables de entorno:
73
-
74
- ```bash
75
- # .env
76
- REACT_APP_CRUDIFY_PUBLIC_API_KEY=tu_api_key_aquí
77
- REACT_APP_CRUDIFY_ENV=dev # dev | stg | api | prod
78
- ```
79
-
80
- ## 🪝 Hooks Principales
81
-
82
- ### useAuth - Autenticación
83
-
84
- Hook principal para manejo completo de autenticación:
85
-
86
- ```tsx
87
- import { useAuth } from '@nocios/crudify-components';
88
-
89
- function LoginPage() {
90
- const { login, logout, isAuthenticated, isLoading, error, clearError } = useAuth();
91
-
92
- const handleLogin = async () => {
93
- try {
94
- const result = await login('user@example.com', 'password');
95
- console.log('Login exitoso:', result);
96
- } catch (error) {
97
- console.error('Error de login:', error);
98
- }
99
- };
100
-
101
- const handleLogout = async () => {
102
- await logout();
103
- };
104
-
105
- return (
106
- <div>
107
- {isAuthenticated ? (
108
- <button onClick={handleLogout}>Cerrar Sesión</button>
109
- ) : (
110
- <button onClick={handleLogin} disabled={isLoading}>
111
- {isLoading ? 'Iniciando...' : 'Iniciar Sesión'}
112
- </button>
113
- )}
114
- {error && <div>Error: {error}</div>}
115
- </div>
116
- );
117
- }
118
- ```
119
-
120
- **Características del useAuth:**
121
-
122
- - ✅ Login/logout automático con refresh tokens
123
- - ✅ Estado de autenticación en tiempo real
124
- - ✅ Manejo de errores integrado
125
- - ✅ Sincronización cross-tab
126
- - ✅ Validación automática de tokens
127
-
128
- ### useUserData - Datos del Usuario
129
-
130
- Hook para obtener y manejar los datos completos del usuario:
131
-
132
- ```tsx
133
- import { useUserData } from '@nocios/crudify-components';
134
-
135
- function UserProfile() {
136
- const { userData, sessionData, isLoading, error, refetch } = useUserData({
137
- autoFetch: true, // Fetch automático al montar
138
- retryOnError: true, // Retry automático en errores
139
- cacheTime: 300000, // Cache por 5 minutos
140
- });
141
-
142
- if (isLoading) return <div>Cargando usuario...</div>;
143
- if (error) return <div>Error: {error}</div>;
144
-
145
- return (
146
- <div>
147
- <h2>Perfil de Usuario</h2>
148
-
149
- {/* Datos del JWT (sessionData) */}
150
- <p>Email: {sessionData?.email}</p>
151
- <p>ID: {sessionData?._id}</p>
152
-
153
- {/* Datos de la base de datos (userData) */}
154
- <p>Nombre completo: {userData?.fullName}</p>
155
- <p>
156
- Avatar: <img src={userData?.avatar} alt="Avatar" />
157
- </p>
158
- <p>Último login: {userData?.lastLogin}</p>
159
-
160
- <button onClick={() => refetch()}>Actualizar Datos</button>
161
- </div>
162
- );
163
- }
164
- ```
165
-
166
- **Características del useUserData:**
167
-
168
- - ✅ Combina datos del JWT (sessionData) y de la BD (userData)
169
- - ✅ Auto-fetch con deduplicación inteligente
170
- - ✅ Retry automático en errores de red
171
- - ✅ Cache configurable
172
- - ✅ Refetch manual
173
-
174
- ### useData - Operaciones CRUD
175
-
176
- Hook para realizar operaciones CRUD type-safe:
177
-
178
- ```tsx
179
- import { useData } from '@nocios/crudify-components';
180
-
181
- function ProductManager() {
182
- const { create, read, update, remove, isInitialized } = useData();
183
-
184
- const createProduct = async () => {
185
- try {
186
- const result = await create('products', {
187
- name: 'Nuevo Producto',
188
- price: 99.99,
189
- category: 'electronics',
190
- });
191
- console.log('Producto creado:', result);
192
- } catch (error) {
193
- console.error('Error creando producto:', error);
194
- }
195
- };
196
-
197
- const getProducts = async () => {
198
- try {
199
- const products = await read('products', {
200
- filters: { category: 'electronics' },
201
- limit: 10,
202
- offset: 0,
203
- });
204
- console.log('Productos:', products);
205
- } catch (error) {
206
- console.error('Error obteniendo productos:', error);
207
- }
208
- };
209
-
210
- const updateProduct = async (productId: string) => {
211
- try {
212
- const result = await update('products', productId, {
213
- price: 89.99,
214
- });
215
- console.log('Producto actualizado:', result);
216
- } catch (error) {
217
- console.error('Error actualizando producto:', error);
218
- }
219
- };
220
-
221
- const deleteProduct = async (productId: string) => {
222
- try {
223
- await remove('products', productId);
224
- console.log('Producto eliminado');
225
- } catch (error) {
226
- console.error('Error eliminando producto:', error);
227
- }
228
- };
229
-
230
- if (!isInitialized) return <div>Inicializando...</div>;
231
-
232
- return (
233
- <div>
234
- <button onClick={createProduct}>Crear Producto</button>
235
- <button onClick={getProducts}>Obtener Productos</button>
236
- <button onClick={() => updateProduct('123')}>Actualizar</button>
237
- <button onClick={() => deleteProduct('123')}>Eliminar</button>
238
- </div>
239
- );
240
- }
241
- ```
242
-
243
- **Características del useData:**
244
-
245
- - ✅ Operaciones CRUD type-safe
246
- - ✅ Verificación automática de inicialización
247
- - ✅ Soporte para transacciones
248
- - ✅ Manejo de errores integrado
249
-
250
- ### useSession - Sesión de Bajo Nivel
251
-
252
- Hook de bajo nivel para control directo de sesiones:
253
-
254
- ```tsx
255
- import { useSession } from '@nocios/crudify-components';
256
-
257
- function SessionManager() {
258
- const { isAuthenticated, isLoading, tokens, error, login, logout, refreshTokens, isExpiringSoon, expiresIn } =
259
- useSession({
260
- onSessionExpired: () => console.log('Sesión expirada'),
261
- onSessionRestored: (tokens) => console.log('Sesión restaurada', tokens),
262
- onTokensRefreshed: (tokens) => console.log('Tokens renovados', tokens),
263
- });
264
-
265
- return (
266
- <div>
267
- <h3>Estado de Sesión</h3>
268
- <p>Autenticado: {isAuthenticated ? 'Sí' : 'No'}</p>
269
- <p>Cargando: {isLoading ? 'Sí' : 'No'}</p>
270
- <p>Expira en: {Math.floor(expiresIn / 60)} minutos</p>
271
- <p>Expira pronto: {isExpiringSoon ? 'Sí' : 'No'}</p>
272
-
273
- {isExpiringSoon && <button onClick={() => refreshTokens()}>Renovar Tokens</button>}
274
-
275
- {tokens && (
276
- <div>
277
- <p>Access Token: {tokens.accessToken.substring(0, 20)}...</p>
278
- <p>Refresh Token: {tokens.refreshToken?.substring(0, 20)}...</p>
279
- </div>
280
- )}
281
- </div>
282
- );
283
- }
284
- ```
285
-
286
- ### useSessionContext - Contexto de Sesión
287
-
288
- Hook para acceder al contexto global de sesión:
289
-
290
- ```tsx
291
- import { useSessionContext } from '@nocios/crudify-components';
292
-
293
- function NavigationBar() {
294
- const { isAuthenticated, sessionData, logout, getTokenInfo } = useSessionContext();
295
-
296
- if (!isAuthenticated) return null;
297
-
298
- return (
299
- <nav>
300
- <span>Bienvenido, {sessionData?.email}</span>
301
- <button onClick={() => logout()}>Cerrar Sesión</button>
302
- <button onClick={() => console.log(getTokenInfo())}>Ver Token Info</button>
303
- </nav>
304
- );
305
- }
306
- ```
307
-
308
- ## 🎨 Componentes UI
309
-
310
- ### CrudifyLogin - Componente de Login Completo
311
-
312
- Componente completo de autenticación con múltiples pantallas:
313
-
314
- ```tsx
315
- import { CrudifyLogin } from '@nocios/crudify-components';
316
- import type { CrudifyLoginConfig } from '@nocios/crudify-components';
317
-
318
- function AuthPage() {
319
- const config: CrudifyLoginConfig = {
320
- appName: 'Mi Aplicación',
321
- logo: '/logo.png',
322
- colors: {
323
- primaryColor: '#1066BA',
324
- bgColor: '#f5f5f5',
325
- },
326
- };
327
-
328
- const handleLoginSuccess = (userData, redirectUrl) => {
329
- console.log('Login exitoso:', userData);
330
- // Redireccionar o actualizar estado
331
- };
332
-
333
- const handleError = (error: string) => {
334
- console.error('Error de autenticación:', error);
335
- };
336
-
337
- return (
338
- <CrudifyLogin
339
- config={config}
340
- onLoginSuccess={handleLoginSuccess}
341
- onError={handleError}
342
- initialScreen="login" // "login" | "forgotPassword" | "resetPassword"
343
- language="es" // "en" | "es"
344
- redirectUrl="/dashboard"
345
- autoReadFromCookies={true}
346
- />
347
- );
348
- }
349
- ```
350
-
351
- **Pantallas incluidas en CrudifyLogin:**
352
-
353
- - 🔐 **Login**: Formulario principal de autenticación
354
- - 🔑 **Forgot Password**: Solicitar recuperación de contraseña
355
- - 📧 **Check Code**: Verificar código de recuperación
356
- - 🔄 **Reset Password**: Establecer nueva contraseña
357
-
358
- **Configuración disponible:**
359
-
360
- - ✅ Logo y nombre de aplicación personalizable
361
- - ✅ Colores y temas personalizables
362
- - ✅ Traducciones completas (ES/EN)
363
- - ✅ URLs de traducción externas
364
- - ✅ Callbacks para todos los eventos
365
-
366
- ### UserProfileDisplay - Perfil de Usuario
367
-
368
- Componente para mostrar información del perfil del usuario:
369
-
370
- ```tsx
371
- import { UserProfileDisplay } from '@nocios/crudify-components';
372
-
373
- function ProfilePage() {
374
- return (
375
- <div>
376
- <h1>Mi Perfil</h1>
377
- <UserProfileDisplay />
378
- </div>
379
- );
380
- }
381
- ```
382
-
383
- ### ProtectedRoute - Protección de Rutas
384
-
385
- Componente para proteger rutas que requieren autenticación:
386
-
387
- ```tsx
388
- import { ProtectedRoute } from '@nocios/crudify-components';
389
-
390
- function App() {
391
- return (
392
- <Router>
393
- <Routes>
394
- <Route path="/login" element={<LoginPage />} />
395
- <Route
396
- path="/dashboard"
397
- element={
398
- <ProtectedRoute fallback={<LoginPage />}>
399
- <Dashboard />
400
- </ProtectedRoute>
401
- }
402
- />
403
- </Routes>
404
- </Router>
405
- );
406
- }
407
- ```
408
-
409
- ### SessionDebugInfo - Debug de Sesión
410
-
411
- Componente útil para desarrollo y debug:
412
-
413
- ```tsx
414
- import { SessionDebugInfo } from '@nocios/crudify-components';
415
-
416
- function DevPage() {
417
- return (
418
- <div>
419
- <h2>Información de Sesión (Dev)</h2>
420
- <SessionDebugInfo />
421
- </div>
422
- );
423
- }
424
- ```
425
-
426
- ### Policies - Gestión de Políticas Públicas
427
-
428
- Componente completo para configurar políticas de acceso a datos públicos:
429
-
430
- ```tsx
431
- import { Policies, POLICY_ACTIONS, PREFERRED_POLICY_ORDER, PolicyAction } from '@nocios/crudify-components';
432
- import { useState } from 'react';
433
-
434
- function ModuleConfiguration() {
435
- const [policies, setPolicies] = useState([
436
- {
437
- id: '1',
438
- action: 'read',
439
- fields: {
440
- allow: ['name', 'email'],
441
- owner_allow: ['phone'],
442
- deny: ['password', 'internal_notes'],
443
- },
444
- },
445
- {
446
- id: '2',
447
- action: 'create',
448
- fields: {
449
- allow: ['name', 'email'],
450
- owner_allow: [],
451
- deny: ['status', 'verified'],
452
- },
453
- },
454
- ]);
455
-
456
- const availableFields = ['name', 'email', 'phone', 'address', 'status', 'verified', 'password', 'internal_notes'];
457
-
458
- const handlePolicyChange = (newPolicies) => {
459
- setPolicies(newPolicies);
460
- // Guardar en base de datos o estado global
461
- };
462
-
463
- return (
464
- <div>
465
- <h2>Configuración de Políticas Públicas</h2>
466
-
467
- <Policies
468
- policies={policies}
469
- onChange={handlePolicyChange}
470
- availableFields={availableFields}
471
- errors={null} // O errores de validación del servidor
472
- isSubmitting={false}
473
- />
474
-
475
- {/* Mostrar acciones disponibles */}
476
- <div style={{ marginTop: '20px' }}>
477
- <h3>Acciones Disponibles:</h3>
478
- <ul>
479
- {POLICY_ACTIONS.map((action) => (
480
- <li key={action}>{action}</li>
481
- ))}
482
- </ul>
483
- </div>
484
- </div>
485
- );
486
- }
487
- ```
488
-
489
- #### Propiedades del Componente Policies
490
-
491
- | Prop | Tipo | Requerido | Descripción |
492
- | ----------------- | ------------------------------ | --------- | --------------------------------------- |
493
- | `policies` | `Policy[]` | ✅ | Array de políticas configuradas |
494
- | `onChange` | `(policies: Policy[]) => void` | ✅ | Callback cuando cambian las políticas |
495
- | `availableFields` | `string[]` | ✅ | Campos disponibles para configurar |
496
- | `errors` | `string \| object` | ❌ | Errores de validación |
497
- | `isSubmitting` | `boolean` | ❌ | Estado de envío (deshabilita controles) |
498
-
499
- #### Estructura de una Policy
500
-
501
- ```tsx
502
- type Policy = {
503
- id: string; // ID único de la política
504
- action: PolicyAction; // 'create' | 'read' | 'update' | 'delete'
505
-
506
- // Para acciones create, read, update:
507
- fields?: {
508
- allow: string[]; // Campos permitidos para todos
509
- owner_allow: string[]; // Campos adicionales para el propietario
510
- deny: string[]; // Campos explícitamente denegados
511
- };
512
-
513
- // Para acción delete:
514
- permission?: string; // '*' | 'deny' | 'owner'
515
- };
516
- ```
517
-
518
- #### Constantes Disponibles
519
-
520
- ```tsx
521
- // Acciones de política disponibles
522
- export const POLICY_ACTIONS = ['create', 'read', 'update', 'delete'] as const;
523
- export type PolicyAction = (typeof POLICY_ACTIONS)[number];
524
-
525
- // Orden preferido para mostrar las políticas
526
- export const PREFERRED_POLICY_ORDER: PolicyAction[] = ['create', 'read', 'update', 'delete'];
527
- ```
528
-
529
- #### Casos de Uso Comunes
530
-
531
- **1. Configuración de API Pública**
532
-
533
- ```tsx
534
- // Permitir lectura pública de ciertos campos
535
- const publicReadPolicy = {
536
- id: 'public-read',
537
- action: 'read',
538
- fields: {
539
- allow: ['name', 'description', 'published_at'],
540
- owner_allow: ['views', 'stats'],
541
- deny: ['internal_notes', 'admin_flags'],
542
- },
543
- };
544
- ```
545
-
546
- **2. Restricciones de Creación**
547
-
548
- ```tsx
549
- // Permitir creación pero restringir ciertos campos
550
- const createPolicy = {
551
- id: 'public-create',
552
- action: 'create',
553
- fields: {
554
- allow: ['title', 'content', 'category'],
555
- owner_allow: [],
556
- deny: ['status', 'featured', 'admin_verified'],
557
- },
558
- };
559
- ```
560
-
561
- **3. Permisos de Eliminación**
562
-
563
- ```tsx
564
- // Solo el propietario puede eliminar
565
- const deletePolicy = {
566
- id: 'owner-delete',
567
- action: 'delete',
568
- permission: 'owner', // '*' = todos, 'deny' = nadie, 'owner' = solo propietario
569
- };
570
- ```
571
-
572
- #### Manejo de Errores
573
-
574
- ```tsx
575
- function PolicyManager() {
576
- const [errors, setErrors] = useState(null);
577
-
578
- const handleSubmit = async (policies) => {
579
- try {
580
- await saveModulePolicies(moduleId, policies);
581
- setErrors(null);
582
- } catch (error) {
583
- // Error de campo específico
584
- if (error.fieldErrors) {
585
- setErrors({
586
- _error: 'Hay errores en la configuración',
587
- ...error.fieldErrors,
588
- });
589
- } else {
590
- // Error general
591
- setErrors('Error al guardar las políticas');
592
- }
593
- }
594
- };
595
-
596
- return (
597
- <Policies
598
- policies={policies}
599
- onChange={setPolicies}
600
- availableFields={fields}
601
- errors={errors} // Se mostrará como Alert en el componente
602
- isSubmitting={isLoading}
603
- />
604
- );
605
- }
606
- ```
607
-
608
- #### Integración con i18next
609
-
610
- El componente usa react-i18next para internacionalización. Las claves de traducción incluidas:
611
-
612
- ```json
613
- {
614
- "modules": {
615
- "form": {
616
- "publicPolicies": {
617
- "title": "Políticas Públicas",
618
- "description": "Configura qué datos pueden acceder los usuarios públicos",
619
- "noPolicies": "No hay políticas configuradas. Agrega una política para comenzar.",
620
- "addPolicy": "Agregar Política",
621
- "actions": {
622
- "create": "Crear",
623
- "read": "Leer",
624
- "update": "Actualizar",
625
- "delete": "Eliminar"
626
- }
627
- }
628
- }
629
- }
630
- }
631
- ```
632
-
633
- #### Características del Componente
634
-
635
- - ✅ **Interfaz Intuitiva**: UI clara para gestionar permisos complejos
636
- - ✅ **Validación en Tiempo Real**: Errores mostrados inmediatamente
637
- - ✅ **Scroll Automático**: Nuevo ítem automáticamente visible
638
- - ✅ **Prevención de Duplicados**: No permite acciones duplicadas
639
- - ✅ **Orden Lógico**: Políticas ordenadas según PREFERRED_POLICY_ORDER
640
- - ✅ **Responsive**: Adaptable a diferentes tamaños de pantalla
641
- - ✅ **Internacionalización**: Soporte completo para múltiples idiomas
642
-
643
- ## 🛠️ Utilidades
644
-
645
- ### JWT Utils
646
-
647
- Utilidades para trabajar con tokens JWT:
648
-
649
- ```tsx
650
- import { decodeJwtSafely, getCurrentUserEmail, isTokenExpired } from '@nocios/crudify-components';
651
-
652
- // Decodificar JWT de forma segura
653
- const payload = decodeJwtSafely(token);
654
- console.log('Usuario ID:', payload?.sub);
655
-
656
- // Obtener email del usuario actual
657
- const email = getCurrentUserEmail();
658
- console.log('Email:', email);
659
-
660
- // Verificar si un token está expirado
661
- const expired = isTokenExpired(token);
662
- console.log('Token expirado:', expired);
663
- ```
664
-
665
- ### Token Storage
666
-
667
- Sistema de almacenamiento seguro para tokens:
668
-
669
- ```tsx
670
- import { TokenStorage } from '@nocios/crudify-components';
671
- import type { TokenData, StorageType } from '@nocios/crudify-components';
672
-
673
- // Crear instancia de storage
674
- const storage = new TokenStorage({
675
- type: 'localStorage', // 'localStorage' | 'sessionStorage'
676
- encryptionKey: 'mi-clave-secreta',
677
- });
678
-
679
- // Guardar tokens
680
- const tokens: TokenData = {
681
- accessToken: 'access_token_here',
682
- refreshToken: 'refresh_token_here',
683
- expiresIn: 3600,
684
- refreshExpiresIn: 86400,
685
- };
686
-
687
- await storage.setTokens(tokens);
688
-
689
- // Obtener tokens
690
- const storedTokens = await storage.getTokens();
691
- console.log('Tokens almacenados:', storedTokens);
692
-
693
- // Limpiar tokens
694
- await storage.clearTokens();
695
-
696
- // Verificar validez
697
- const isValid = await storage.isValid();
698
- console.log('Tokens válidos:', isValid);
699
- ```
700
-
701
- ### Error Handler
702
-
703
- Sistema completo de manejo de errores:
704
-
705
- ```tsx
706
- import {
707
- handleCrudifyError,
708
- parseApiError,
709
- parseTransactionError,
710
- getErrorMessage,
711
- ERROR_CODES,
712
- } from '@nocios/crudify-components';
713
- import type { ParsedError, ErrorCode } from '@nocios/crudify-components';
714
-
715
- // Manejo universal de errores
716
- try {
717
- // operación que puede fallar
718
- } catch (error) {
719
- const parsedError: ParsedError = handleCrudifyError(error);
720
-
721
- console.log('Tipo:', parsedError.type);
722
- console.log('Código:', parsedError.code);
723
- console.log('Mensaje:', parsedError.message);
724
- console.log('Severidad:', parsedError.severity);
725
-
726
- // Mostrar mensaje apropiado al usuario
727
- const userMessage = getErrorMessage(parsedError.code as ErrorCode);
728
- alert(userMessage);
729
- }
730
-
731
- // Parser específico para errores de API
732
- const apiError = parseApiError(response);
733
-
734
- // Parser específico para errores de transacción
735
- const transactionError = parseTransactionError(response);
736
-
737
- // Códigos de error disponibles
738
- console.log('Códigos disponibles:', ERROR_CODES);
739
- ```
740
-
741
- ## 🔄 API de Crudify Browser
742
-
743
- Esta librería re-exporta completamente `@nocios/crudify-sdk`, proporcionando una API unificada:
744
-
745
- ```tsx
746
- import { crudify } from '@nocios/crudify-components';
747
- // Equivale a: import crudify from '@nocios/crudify-sdk';
748
-
749
- // Todas las funcionalidades de crudify-browser están disponibles
750
- const result = await crudify.create('users', userData);
751
- const users = await crudify.read('users');
752
- ```
753
-
754
- Esto significa que puedes:
755
-
756
- - ✅ Importar solo desde `@nocios/crudify-components`
757
- - ✅ Usar todas las funcionalidades de crudify-browser
758
- - ✅ Tener una API unificada
759
- - ✅ Simplificar las dependencias
760
-
761
- ## 📱 TypeScript Support
762
-
763
- La librería incluye tipos completos para TypeScript:
764
-
765
- ```tsx
766
- import type {
767
- // Tipos de API
768
- CrudifyApiResponse,
769
- CrudifyTransactionResponse,
770
- UserProfile,
771
- LoginResponse,
772
- LoginRequest,
773
-
774
- // Tipos de componentes
775
- CrudifyLoginProps,
776
- CrudifyLoginConfig,
777
-
778
- // Tipos de hooks
779
- UseAuthReturn,
780
- UseUserDataReturn,
781
- UseDataReturn,
782
- SessionState,
783
-
784
- // Tipos de utilidades
785
- TokenData,
786
- ParsedError,
787
- ErrorCode,
788
- } from '@nocios/crudify-components';
789
-
790
- // Ejemplo con tipos
791
- const handleLogin = async (credentials: LoginRequest): Promise<CrudifyApiResponse<LoginResponse>> => {
792
- // implementación con tipos seguros
793
- };
794
- ```
795
-
796
- ## 🌐 Internacionalización
797
-
798
- ### Configuración básica
799
-
800
- ```tsx
801
- import { CrudifyLogin } from '@nocios/crudify-components';
802
-
803
- // Usando traducciones incluidas
804
- <CrudifyLogin language="es" /> // "en" | "es"
805
-
806
- // Usando traducciones personalizadas
807
- <CrudifyLogin
808
- language="es"
809
- translations={{
810
- login: {
811
- title: "Iniciar Sesión",
812
- email: "Correo Electrónico",
813
- password: "Contraseña",
814
- submit: "Entrar"
815
- }
816
- }}
817
- />
818
-
819
- // Usando URL externa para traducciones
820
- <CrudifyLogin
821
- language="es"
822
- translationsUrl="/api/translations/es.json"
823
- />
824
- ```
825
-
826
- ### Estructura de traducciones
827
-
828
- ```json
829
- {
830
- "login": {
831
- "title": "Iniciar Sesión",
832
- "email": "Correo Electrónico",
833
- "password": "Contraseña",
834
- "submit": "Entrar",
835
- "forgotPassword": "¿Olvidaste tu contraseña?",
836
- "logoAlt": "Logo de la aplicación"
837
- },
838
- "forgotPassword": {
839
- "title": "Recuperar Contraseña",
840
- "email": "Correo Electrónico",
841
- "submit": "Enviar Código",
842
- "backToLogin": "Volver al Login"
843
- },
844
- "resetPassword": {
845
- "title": "Nueva Contraseña",
846
- "password": "Nueva Contraseña",
847
- "confirmPassword": "Confirmar Contraseña",
848
- "submit": "Cambiar Contraseña"
849
- }
850
- }
851
- ```
852
-
853
- ## 🔧 Configuración Avanzada
854
-
855
- ### SessionProvider con opciones completas
856
-
857
- ```tsx
858
- import { SessionProvider } from '@nocios/crudify-components';
859
- import type { UseSessionOptions } from '@nocios/crudify-components';
860
-
861
- const sessionOptions: UseSessionOptions = {
862
- // Tipo de almacenamiento
863
- storageType: 'localStorage', // 'localStorage' | 'sessionStorage'
864
-
865
- // Callbacks del ciclo de vida
866
- onSessionExpired: () => {
867
- console.log('Sesión expirada - redirigir a login');
868
- window.location.href = '/login';
869
- },
870
-
871
- onSessionRestored: (tokens) => {
872
- console.log('Sesión restaurada exitosamente', tokens);
873
- },
874
-
875
- onTokensRefreshed: (tokens) => {
876
- console.log('Tokens renovados', tokens);
877
- },
878
-
879
- onLogout: () => {
880
- console.log('Usuario cerró sesión');
881
- },
882
-
883
- onError: (error) => {
884
- console.error('Error de sesión:', error);
885
- },
886
- };
887
-
888
- function App() {
889
- return (
890
- <SessionProvider options={sessionOptions}>
891
- <Router>
892
- <Routes>{/* tus rutas */}</Routes>
893
- </Router>
894
- </SessionProvider>
895
- );
896
- }
897
- ```
898
-
899
- ### Configuración manual de Crudify
900
-
901
- Si necesitas configuración manual en lugar de variables de entorno:
902
-
903
- ```tsx
904
- import { crudify } from '@nocios/crudify-components';
905
-
906
- // Configurar manualmente antes de usar
907
- crudify.configure({
908
- publicApiKey: 'tu-api-key',
909
- environment: 'production', // 'dev' | 'stg' | 'api' | 'prod'
910
- baseUrl: 'https://api.tudominio.com', // opcional
911
- });
912
- ```
913
-
914
- ## 🚀 Ejemplos de Uso Completos
915
-
916
- ### Aplicación básica con autenticación
917
-
918
- ```tsx
919
- import React from 'react';
920
- import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
921
- import { SessionProvider, ProtectedRoute, CrudifyLogin, useSessionContext } from '@nocios/crudify-components';
922
-
923
- // Componente Dashboard
924
- function Dashboard() {
925
- const { sessionData, logout } = useSessionContext();
926
-
927
- return (
928
- <div>
929
- <h1>Dashboard</h1>
930
- <p>Bienvenido, {sessionData?.email}</p>
931
- <button onClick={() => logout()}>Cerrar Sesión</button>
932
- </div>
933
- );
934
- }
935
-
936
- // Componente Login
937
- function LoginPage() {
938
- return (
939
- <div style={{ maxWidth: '400px', margin: '0 auto', padding: '2rem' }}>
940
- <CrudifyLogin
941
- config={{
942
- appName: 'Mi App',
943
- colors: { primaryColor: '#1976d2' },
944
- }}
945
- onLoginSuccess={(userData) => {
946
- console.log('Login exitoso:', userData);
947
- // La navegación se maneja automáticamente
948
- }}
949
- />
950
- </div>
951
- );
952
- }
953
-
954
- // Aplicación principal
955
- function App() {
956
- return (
957
- <SessionProvider>
958
- <BrowserRouter>
959
- <Routes>
960
- <Route path="/login" element={<LoginPage />} />
961
- <Route
962
- path="/dashboard"
963
- element={
964
- <ProtectedRoute fallback={<Navigate to="/login" />}>
965
- <Dashboard />
966
- </ProtectedRoute>
967
- }
968
- />
969
- <Route path="/" element={<Navigate to="/dashboard" />} />
970
- </Routes>
971
- </BrowserRouter>
972
- </SessionProvider>
973
- );
974
- }
975
-
976
- export default App;
977
- ```
978
-
979
- ### Aplicación con operaciones CRUD
980
-
981
- ```tsx
982
- import React, { useState, useEffect } from 'react';
983
- import { useData, useUserData } from '@nocios/crudify-components';
984
-
985
- function ProductManager() {
986
- const [products, setProducts] = useState([]);
987
- const [loading, setLoading] = useState(false);
988
-
989
- const { create, read, update, remove } = useData();
990
- const { userData } = useUserData();
991
-
992
- // Cargar productos al montar
993
- useEffect(() => {
994
- loadProducts();
995
- }, []);
996
-
997
- const loadProducts = async () => {
998
- setLoading(true);
999
- try {
1000
- const result = await read('products', {
1001
- filters: { userId: userData?.id },
1002
- limit: 20,
1003
- });
1004
- setProducts(result.data || []);
1005
- } catch (error) {
1006
- console.error('Error cargando productos:', error);
1007
- } finally {
1008
- setLoading(false);
1009
- }
1010
- };
1011
-
1012
- const createProduct = async (productData) => {
1013
- try {
1014
- await create('products', {
1015
- ...productData,
1016
- userId: userData?.id,
1017
- });
1018
- await loadProducts(); // Recargar lista
1019
- } catch (error) {
1020
- console.error('Error creando producto:', error);
1021
- }
1022
- };
1023
-
1024
- const updateProduct = async (productId, updates) => {
1025
- try {
1026
- await update('products', productId, updates);
1027
- await loadProducts(); // Recargar lista
1028
- } catch (error) {
1029
- console.error('Error actualizando producto:', error);
1030
- }
1031
- };
1032
-
1033
- const deleteProduct = async (productId) => {
1034
- try {
1035
- await remove('products', productId);
1036
- await loadProducts(); // Recargar lista
1037
- } catch (error) {
1038
- console.error('Error eliminando producto:', error);
1039
- }
1040
- };
1041
-
1042
- if (loading) return <div>Cargando productos...</div>;
1043
-
1044
- return (
1045
- <div>
1046
- <h2>Mis Productos</h2>
1047
-
1048
- <button
1049
- onClick={() =>
1050
- createProduct({
1051
- name: 'Nuevo Producto',
1052
- price: 99.99,
1053
- })
1054
- }
1055
- >
1056
- Crear Producto
1057
- </button>
1058
-
1059
- <ul>
1060
- {products.map((product) => (
1061
- <li key={product.id}>
1062
- <span>
1063
- {product.name} - ${product.price}
1064
- </span>
1065
- <button
1066
- onClick={() =>
1067
- updateProduct(product.id, {
1068
- price: product.price * 1.1,
1069
- })
1070
- }
1071
- >
1072
- Aumentar Precio 10%
1073
- </button>
1074
- <button onClick={() => deleteProduct(product.id)}>Eliminar</button>
1075
- </li>
1076
- ))}
1077
- </ul>
1078
- </div>
1079
- );
1080
- }
1081
- ```
1082
-
1083
- ## 🔒 Seguridad
1084
-
1085
- ### Características de Seguridad
1086
-
1087
- - ✅ **Encriptación de tokens**: Los tokens se almacenan encriptados
1088
- - ✅ **Refresh Token Pattern**: Tokens de acceso de corta duración
1089
- - ✅ **Validación automática**: Verificación de expiración y renovación
1090
- - ✅ **Almacenamiento seguro**: Soporte para localStorage y sessionStorage
1091
- - ✅ **Limpieza automática**: Tokens expirados se limpian automáticamente
1092
-
1093
- ### Mejores Prácticas
1094
-
1095
- ```tsx
1096
- // 1. Usar SessionProvider en el nivel más alto
1097
- function App() {
1098
- return (
1099
- <SessionProvider
1100
- options={{
1101
- storageType: 'localStorage', // Más persistente
1102
- onSessionExpired: handleSessionExpired,
1103
- }}
1104
- >
1105
- <YourApp />
1106
- </SessionProvider>
1107
- );
1108
- }
1109
-
1110
- // 2. Proteger rutas sensibles
1111
- <ProtectedRoute fallback={<LoginRedirect />}>
1112
- <SensitiveComponent />
1113
- </ProtectedRoute>;
1114
-
1115
- // 3. Manejar errores de autenticación
1116
- const { error, clearError } = useAuth();
1117
- useEffect(() => {
1118
- if (error) {
1119
- console.error('Error de auth:', error);
1120
- // Mostrar notificación al usuario
1121
- clearError();
1122
- }
1123
- }, [error]);
1124
-
1125
- // 4. Limpiar sesión al salir
1126
- useEffect(() => {
1127
- const handleBeforeUnload = () => {
1128
- // Opcional: limpiar datos sensibles
1129
- };
1130
-
1131
- window.addEventListener('beforeunload', handleBeforeUnload);
1132
- return () => window.removeEventListener('beforeunload', handleBeforeUnload);
1133
- }, []);
1134
- ```
1135
-
1136
- ## 🐛 Troubleshooting
1137
-
1138
- ### Problemas Comunes
1139
-
1140
- **1. Token expirado constantemente**
1141
-
1142
- ```tsx
1143
- // Verificar configuración de refresh tokens
1144
- const { isExpiringSoon, refreshTokens } = useSession();
1145
-
1146
- useEffect(() => {
1147
- if (isExpiringSoon) {
1148
- refreshTokens();
1149
- }
1150
- }, [isExpiringSoon]);
1151
- ```
1152
-
1153
- **2. Sesión no se restaura al recargar**
1154
-
1155
- ```tsx
1156
- // Asegurar que SessionProvider esté en el nivel correcto
1157
- // y verificar storageType
1158
- <SessionProvider options={{ storageType: 'localStorage' }}>
1159
- ```
1160
-
1161
- **3. Errores de CORS**
1162
-
1163
- ```tsx
1164
- // Verificar configuración de environment
1165
- crudify.configure({
1166
- environment: 'dev', // Asegurar environment correcto
1167
- publicApiKey: process.env.REACT_APP_CRUDIFY_PUBLIC_API_KEY,
1168
- });
1169
- ```
1170
-
1171
- ### Debug
1172
-
1173
- ```tsx
1174
- // Usar SessionDebugInfo para desarrollo
1175
- import { SessionDebugInfo } from '@nocios/crudify-components';
1176
-
1177
- function DevPanel() {
1178
- return process.env.NODE_ENV === 'development' ? (
1179
- <div style={{ position: 'fixed', bottom: 0, right: 0, background: 'white', padding: '1rem' }}>
1180
- <SessionDebugInfo />
1181
- </div>
1182
- ) : null;
1183
- }
1184
- ```
1185
-
1186
- ## 📚 Referencias de API
1187
-
1188
- ### Hooks API Reference
1189
-
1190
- | Hook | Propósito | Returns |
1191
- | ------------------- | ----------------------- | -------------------------------------------------------- |
1192
- | `useAuth` | Autenticación principal | `{ login, logout, isAuthenticated, isLoading, error }` |
1193
- | `useUserData` | Datos del usuario | `{ userData, sessionData, isLoading, error, refetch }` |
1194
- | `useData` | Operaciones CRUD | `{ create, read, update, remove, isInitialized }` |
1195
- | `useSession` | Control de sesión | `{ tokens, isAuthenticated, refreshTokens, expiresIn }` |
1196
- | `useSessionContext` | Contexto global | `{ sessionData, isAuthenticated, logout, getTokenInfo }` |
1197
-
1198
- ### Componentes API Reference
1199
-
1200
- | Componente | Props Principales | Propósito |
1201
- | -------------------- | ---------------------------------- | ---------------------------- |
1202
- | `SessionProvider` | `options: UseSessionOptions` | Provider global de sesión |
1203
- | `CrudifyLogin` | `config, onLoginSuccess, language` | Componente de login completo |
1204
- | `ProtectedRoute` | `fallback, children` | Protección de rutas |
1205
- | `UserProfileDisplay` | `showAvatar, showDetails` | Display de perfil |
1206
-
1207
- ### Utilidades API Reference
1208
-
1209
- | Utilidad | Funciones | Propósito |
1210
- | --------------- | ------------------------------------------------------ | --------------------- |
1211
- | `JWT Utils` | `decodeJwtSafely, getCurrentUserEmail, isTokenExpired` | Manejo de JWT |
1212
- | `Token Storage` | `setTokens, getTokens, clearTokens, isValid` | Almacenamiento seguro |
1213
- | `Error Handler` | `handleCrudifyError, parseApiError, getErrorMessage` | Manejo de errores |
1214
-
1215
- ## 🔄 Changelog
1216
-
1217
- ### v1.3.1
1218
-
1219
- - ✅ Sistema completo de Refresh Token Pattern
1220
- - ✅ Hooks modernos (useAuth, useUserData, useData)
1221
- - ✅ SessionProvider con contexto global
1222
- - ✅ Componentes ProtectedRoute y SessionDebugInfo
1223
- - ✅ Almacenamiento encriptado de tokens
1224
- - ✅ Sistema completo de manejo de errores
1225
- - ✅ Re-exportación completa de @nocios/crudify-sdk
1226
-
1227
- ## 📄 Licencia
1228
-
1229
- MIT © [Nocios](https://github.com/nocios)
1230
-
1231
- ## 🤝 Contribuir
1232
-
1233
- Si encuentras bugs o tienes sugerencias, por favor crea un issue en el repositorio oficial.
1234
-
1235
- ---
1236
-
1237
- **¿Necesitas ayuda?** Consulta la documentación completa o contacta al equipo de desarrollo.