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