@nocios/crudify-ui 1.3.1 → 1.3.2
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 +155 -1108
- package/README_DEPTH.md +1046 -0
- package/package.json +1 -1
- package/MIGRATION-GUIDE.md +0 -312
- package/MIGRATION.md +0 -201
- package/MIGRATION_EXAMPLE.md +0 -538
- package/TECHNICAL_SPECIFICATION.md +0 -344
- package/example-app.tsx +0 -197
- package/mejoras_npm_lib.md +0 -790
- package/tsup.config.ts +0 -9
package/README_DEPTH.md
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
# @nocios/crudify-ui - Documentación Completa
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/%40nocios%2Fcrudify-ui)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**@nocios/crudify-ui** 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-browser
|
|
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-ui
|
|
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-ui';
|
|
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-ui';
|
|
88
|
+
|
|
89
|
+
function LoginPage() {
|
|
90
|
+
const {
|
|
91
|
+
login,
|
|
92
|
+
logout,
|
|
93
|
+
isAuthenticated,
|
|
94
|
+
isLoading,
|
|
95
|
+
error,
|
|
96
|
+
clearError
|
|
97
|
+
} = useAuth();
|
|
98
|
+
|
|
99
|
+
const handleLogin = async () => {
|
|
100
|
+
try {
|
|
101
|
+
const result = await login('user@example.com', 'password');
|
|
102
|
+
console.log('Login exitoso:', result);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Error de login:', error);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleLogout = async () => {
|
|
109
|
+
await logout();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div>
|
|
114
|
+
{isAuthenticated ? (
|
|
115
|
+
<button onClick={handleLogout}>Cerrar Sesión</button>
|
|
116
|
+
) : (
|
|
117
|
+
<button onClick={handleLogin} disabled={isLoading}>
|
|
118
|
+
{isLoading ? 'Iniciando...' : 'Iniciar Sesión'}
|
|
119
|
+
</button>
|
|
120
|
+
)}
|
|
121
|
+
{error && <div>Error: {error}</div>}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Características del useAuth:**
|
|
128
|
+
- ✅ Login/logout automático con refresh tokens
|
|
129
|
+
- ✅ Estado de autenticación en tiempo real
|
|
130
|
+
- ✅ Manejo de errores integrado
|
|
131
|
+
- ✅ Sincronización cross-tab
|
|
132
|
+
- ✅ Validación automática de tokens
|
|
133
|
+
|
|
134
|
+
### useUserData - Datos del Usuario
|
|
135
|
+
|
|
136
|
+
Hook para obtener y manejar los datos completos del usuario:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { useUserData } from '@nocios/crudify-ui';
|
|
140
|
+
|
|
141
|
+
function UserProfile() {
|
|
142
|
+
const {
|
|
143
|
+
userData,
|
|
144
|
+
sessionData,
|
|
145
|
+
isLoading,
|
|
146
|
+
error,
|
|
147
|
+
refetch
|
|
148
|
+
} = useUserData({
|
|
149
|
+
autoFetch: true, // Fetch automático al montar
|
|
150
|
+
retryOnError: true, // Retry automático en errores
|
|
151
|
+
cacheTime: 300000 // Cache por 5 minutos
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (isLoading) return <div>Cargando usuario...</div>;
|
|
155
|
+
if (error) return <div>Error: {error}</div>;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div>
|
|
159
|
+
<h2>Perfil de Usuario</h2>
|
|
160
|
+
|
|
161
|
+
{/* Datos del JWT (sessionData) */}
|
|
162
|
+
<p>Email: {sessionData?.email}</p>
|
|
163
|
+
<p>ID: {sessionData?._id}</p>
|
|
164
|
+
|
|
165
|
+
{/* Datos de la base de datos (userData) */}
|
|
166
|
+
<p>Nombre completo: {userData?.fullName}</p>
|
|
167
|
+
<p>Avatar: <img src={userData?.avatar} alt="Avatar" /></p>
|
|
168
|
+
<p>Último login: {userData?.lastLogin}</p>
|
|
169
|
+
|
|
170
|
+
<button onClick={() => refetch()}>
|
|
171
|
+
Actualizar Datos
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Características del useUserData:**
|
|
179
|
+
- ✅ Combina datos del JWT (sessionData) y de la BD (userData)
|
|
180
|
+
- ✅ Auto-fetch con deduplicación inteligente
|
|
181
|
+
- ✅ Retry automático en errores de red
|
|
182
|
+
- ✅ Cache configurable
|
|
183
|
+
- ✅ Refetch manual
|
|
184
|
+
|
|
185
|
+
### useData - Operaciones CRUD
|
|
186
|
+
|
|
187
|
+
Hook para realizar operaciones CRUD type-safe:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { useData } from '@nocios/crudify-ui';
|
|
191
|
+
|
|
192
|
+
function ProductManager() {
|
|
193
|
+
const { create, read, update, remove, isInitialized } = useData();
|
|
194
|
+
|
|
195
|
+
const createProduct = async () => {
|
|
196
|
+
try {
|
|
197
|
+
const result = await create('products', {
|
|
198
|
+
name: 'Nuevo Producto',
|
|
199
|
+
price: 99.99,
|
|
200
|
+
category: 'electronics'
|
|
201
|
+
});
|
|
202
|
+
console.log('Producto creado:', result);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('Error creando producto:', error);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const getProducts = async () => {
|
|
209
|
+
try {
|
|
210
|
+
const products = await read('products', {
|
|
211
|
+
filters: { category: 'electronics' },
|
|
212
|
+
limit: 10,
|
|
213
|
+
offset: 0
|
|
214
|
+
});
|
|
215
|
+
console.log('Productos:', products);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error obteniendo productos:', error);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const updateProduct = async (productId: string) => {
|
|
222
|
+
try {
|
|
223
|
+
const result = await update('products', productId, {
|
|
224
|
+
price: 89.99
|
|
225
|
+
});
|
|
226
|
+
console.log('Producto actualizado:', result);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Error actualizando producto:', error);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const deleteProduct = async (productId: string) => {
|
|
233
|
+
try {
|
|
234
|
+
await remove('products', productId);
|
|
235
|
+
console.log('Producto eliminado');
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('Error eliminando producto:', error);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
if (!isInitialized) return <div>Inicializando...</div>;
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<div>
|
|
245
|
+
<button onClick={createProduct}>Crear Producto</button>
|
|
246
|
+
<button onClick={getProducts}>Obtener Productos</button>
|
|
247
|
+
<button onClick={() => updateProduct('123')}>Actualizar</button>
|
|
248
|
+
<button onClick={() => deleteProduct('123')}>Eliminar</button>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Características del useData:**
|
|
255
|
+
- ✅ Operaciones CRUD type-safe
|
|
256
|
+
- ✅ Verificación automática de inicialización
|
|
257
|
+
- ✅ Soporte para transacciones
|
|
258
|
+
- ✅ Manejo de errores integrado
|
|
259
|
+
|
|
260
|
+
### useSession - Sesión de Bajo Nivel
|
|
261
|
+
|
|
262
|
+
Hook de bajo nivel para control directo de sesiones:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { useSession } from '@nocios/crudify-ui';
|
|
266
|
+
|
|
267
|
+
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)
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div>
|
|
286
|
+
<h3>Estado de Sesión</h3>
|
|
287
|
+
<p>Autenticado: {isAuthenticated ? 'Sí' : 'No'}</p>
|
|
288
|
+
<p>Cargando: {isLoading ? 'Sí' : 'No'}</p>
|
|
289
|
+
<p>Expira en: {Math.floor(expiresIn / 60)} minutos</p>
|
|
290
|
+
<p>Expira pronto: {isExpiringSoon ? 'Sí' : 'No'}</p>
|
|
291
|
+
|
|
292
|
+
{isExpiringSoon && (
|
|
293
|
+
<button onClick={() => refreshTokens()}>
|
|
294
|
+
Renovar Tokens
|
|
295
|
+
</button>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{tokens && (
|
|
299
|
+
<div>
|
|
300
|
+
<p>Access Token: {tokens.accessToken.substring(0, 20)}...</p>
|
|
301
|
+
<p>Refresh Token: {tokens.refreshToken?.substring(0, 20)}...</p>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### useSessionContext - Contexto de Sesión
|
|
310
|
+
|
|
311
|
+
Hook para acceder al contexto global de sesión:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { useSessionContext } from '@nocios/crudify-ui';
|
|
315
|
+
|
|
316
|
+
function NavigationBar() {
|
|
317
|
+
const {
|
|
318
|
+
isAuthenticated,
|
|
319
|
+
sessionData,
|
|
320
|
+
logout,
|
|
321
|
+
getTokenInfo
|
|
322
|
+
} = useSessionContext();
|
|
323
|
+
|
|
324
|
+
if (!isAuthenticated) return null;
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<nav>
|
|
328
|
+
<span>Bienvenido, {sessionData?.email}</span>
|
|
329
|
+
<button onClick={() => logout()}>Cerrar Sesión</button>
|
|
330
|
+
<button onClick={() => console.log(getTokenInfo())}>
|
|
331
|
+
Ver Token Info
|
|
332
|
+
</button>
|
|
333
|
+
</nav>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## 🎨 Componentes UI
|
|
339
|
+
|
|
340
|
+
### CrudifyLogin - Componente de Login Completo
|
|
341
|
+
|
|
342
|
+
Componente completo de autenticación con múltiples pantallas:
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import { CrudifyLogin } from '@nocios/crudify-ui';
|
|
346
|
+
import type { CrudifyLoginConfig } from '@nocios/crudify-ui';
|
|
347
|
+
|
|
348
|
+
function AuthPage() {
|
|
349
|
+
const config: CrudifyLoginConfig = {
|
|
350
|
+
appName: "Mi Aplicación",
|
|
351
|
+
logo: "/logo.png",
|
|
352
|
+
colors: {
|
|
353
|
+
primaryColor: "#1066BA",
|
|
354
|
+
bgColor: "#f5f5f5"
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const handleLoginSuccess = (userData, redirectUrl) => {
|
|
359
|
+
console.log('Login exitoso:', userData);
|
|
360
|
+
// Redireccionar o actualizar estado
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleError = (error: string) => {
|
|
364
|
+
console.error('Error de autenticación:', error);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<CrudifyLogin
|
|
369
|
+
config={config}
|
|
370
|
+
onLoginSuccess={handleLoginSuccess}
|
|
371
|
+
onError={handleError}
|
|
372
|
+
initialScreen="login" // "login" | "forgotPassword" | "resetPassword"
|
|
373
|
+
language="es" // "en" | "es"
|
|
374
|
+
redirectUrl="/dashboard"
|
|
375
|
+
autoReadFromCookies={true}
|
|
376
|
+
/>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Pantallas incluidas en CrudifyLogin:**
|
|
382
|
+
- 🔐 **Login**: Formulario principal de autenticación
|
|
383
|
+
- 🔑 **Forgot Password**: Solicitar recuperación de contraseña
|
|
384
|
+
- 📧 **Check Code**: Verificar código de recuperación
|
|
385
|
+
- 🔄 **Reset Password**: Establecer nueva contraseña
|
|
386
|
+
|
|
387
|
+
**Configuración disponible:**
|
|
388
|
+
- ✅ Logo y nombre de aplicación personalizable
|
|
389
|
+
- ✅ Colores y temas personalizables
|
|
390
|
+
- ✅ Traducciones completas (ES/EN)
|
|
391
|
+
- ✅ URLs de traducción externas
|
|
392
|
+
- ✅ Callbacks para todos los eventos
|
|
393
|
+
|
|
394
|
+
### UserProfileDisplay - Perfil de Usuario
|
|
395
|
+
|
|
396
|
+
Componente para mostrar información del perfil del usuario:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
import { UserProfileDisplay } from '@nocios/crudify-ui';
|
|
400
|
+
|
|
401
|
+
function ProfilePage() {
|
|
402
|
+
return (
|
|
403
|
+
<div>
|
|
404
|
+
<h1>Mi Perfil</h1>
|
|
405
|
+
<UserProfileDisplay />
|
|
406
|
+
</div>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### ProtectedRoute - Protección de Rutas
|
|
412
|
+
|
|
413
|
+
Componente para proteger rutas que requieren autenticación:
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
import { ProtectedRoute } from '@nocios/crudify-ui';
|
|
417
|
+
|
|
418
|
+
function App() {
|
|
419
|
+
return (
|
|
420
|
+
<Router>
|
|
421
|
+
<Routes>
|
|
422
|
+
<Route path="/login" element={<LoginPage />} />
|
|
423
|
+
<Route
|
|
424
|
+
path="/dashboard"
|
|
425
|
+
element={
|
|
426
|
+
<ProtectedRoute fallback={<LoginPage />}>
|
|
427
|
+
<Dashboard />
|
|
428
|
+
</ProtectedRoute>
|
|
429
|
+
}
|
|
430
|
+
/>
|
|
431
|
+
</Routes>
|
|
432
|
+
</Router>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### SessionDebugInfo - Debug de Sesión
|
|
438
|
+
|
|
439
|
+
Componente útil para desarrollo y debug:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
import { SessionDebugInfo } from '@nocios/crudify-ui';
|
|
443
|
+
|
|
444
|
+
function DevPage() {
|
|
445
|
+
return (
|
|
446
|
+
<div>
|
|
447
|
+
<h2>Información de Sesión (Dev)</h2>
|
|
448
|
+
<SessionDebugInfo />
|
|
449
|
+
</div>
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## 🛠️ Utilidades
|
|
455
|
+
|
|
456
|
+
### JWT Utils
|
|
457
|
+
|
|
458
|
+
Utilidades para trabajar con tokens JWT:
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
import {
|
|
462
|
+
decodeJwtSafely,
|
|
463
|
+
getCurrentUserEmail,
|
|
464
|
+
isTokenExpired
|
|
465
|
+
} from '@nocios/crudify-ui';
|
|
466
|
+
|
|
467
|
+
// Decodificar JWT de forma segura
|
|
468
|
+
const payload = decodeJwtSafely(token);
|
|
469
|
+
console.log('Usuario ID:', payload?.sub);
|
|
470
|
+
|
|
471
|
+
// Obtener email del usuario actual
|
|
472
|
+
const email = getCurrentUserEmail();
|
|
473
|
+
console.log('Email:', email);
|
|
474
|
+
|
|
475
|
+
// Verificar si un token está expirado
|
|
476
|
+
const expired = isTokenExpired(token);
|
|
477
|
+
console.log('Token expirado:', expired);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Token Storage
|
|
481
|
+
|
|
482
|
+
Sistema de almacenamiento seguro para tokens:
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
import { TokenStorage } from '@nocios/crudify-ui';
|
|
486
|
+
import type { TokenData, StorageType } from '@nocios/crudify-ui';
|
|
487
|
+
|
|
488
|
+
// Crear instancia de storage
|
|
489
|
+
const storage = new TokenStorage({
|
|
490
|
+
type: 'localStorage', // 'localStorage' | 'sessionStorage'
|
|
491
|
+
encryptionKey: 'mi-clave-secreta'
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// Guardar tokens
|
|
495
|
+
const tokens: TokenData = {
|
|
496
|
+
accessToken: 'access_token_here',
|
|
497
|
+
refreshToken: 'refresh_token_here',
|
|
498
|
+
expiresIn: 3600,
|
|
499
|
+
refreshExpiresIn: 86400
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
await storage.setTokens(tokens);
|
|
503
|
+
|
|
504
|
+
// Obtener tokens
|
|
505
|
+
const storedTokens = await storage.getTokens();
|
|
506
|
+
console.log('Tokens almacenados:', storedTokens);
|
|
507
|
+
|
|
508
|
+
// Limpiar tokens
|
|
509
|
+
await storage.clearTokens();
|
|
510
|
+
|
|
511
|
+
// Verificar validez
|
|
512
|
+
const isValid = await storage.isValid();
|
|
513
|
+
console.log('Tokens válidos:', isValid);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Error Handler
|
|
517
|
+
|
|
518
|
+
Sistema completo de manejo de errores:
|
|
519
|
+
|
|
520
|
+
```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';
|
|
529
|
+
|
|
530
|
+
// Manejo universal de errores
|
|
531
|
+
try {
|
|
532
|
+
// operación que puede fallar
|
|
533
|
+
} catch (error) {
|
|
534
|
+
const parsedError: ParsedError = handleCrudifyError(error);
|
|
535
|
+
|
|
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);
|
|
540
|
+
|
|
541
|
+
// Mostrar mensaje apropiado al usuario
|
|
542
|
+
const userMessage = getErrorMessage(parsedError.code as ErrorCode);
|
|
543
|
+
alert(userMessage);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Parser específico para errores de API
|
|
547
|
+
const apiError = parseApiError(response);
|
|
548
|
+
|
|
549
|
+
// Parser específico para errores de transacción
|
|
550
|
+
const transactionError = parseTransactionError(response);
|
|
551
|
+
|
|
552
|
+
// Códigos de error disponibles
|
|
553
|
+
console.log('Códigos disponibles:', ERROR_CODES);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
## 🔄 API de Crudify Browser
|
|
557
|
+
|
|
558
|
+
Esta librería re-exporta completamente `@nocios/crudify-browser`, proporcionando una API unificada:
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
import { crudify } from '@nocios/crudify-ui';
|
|
562
|
+
// Equivale a: import crudify from '@nocios/crudify-browser';
|
|
563
|
+
|
|
564
|
+
// Todas las funcionalidades de crudify-browser están disponibles
|
|
565
|
+
const result = await crudify.create('users', userData);
|
|
566
|
+
const users = await crudify.read('users');
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Esto significa que puedes:
|
|
570
|
+
- ✅ Importar solo desde `@nocios/crudify-ui`
|
|
571
|
+
- ✅ Usar todas las funcionalidades de crudify-browser
|
|
572
|
+
- ✅ Tener una API unificada
|
|
573
|
+
- ✅ Simplificar las dependencias
|
|
574
|
+
|
|
575
|
+
## 📱 TypeScript Support
|
|
576
|
+
|
|
577
|
+
La librería incluye tipos completos para TypeScript:
|
|
578
|
+
|
|
579
|
+
```tsx
|
|
580
|
+
import type {
|
|
581
|
+
// Tipos de API
|
|
582
|
+
CrudifyApiResponse,
|
|
583
|
+
CrudifyTransactionResponse,
|
|
584
|
+
UserProfile,
|
|
585
|
+
LoginResponse,
|
|
586
|
+
LoginRequest,
|
|
587
|
+
|
|
588
|
+
// Tipos de componentes
|
|
589
|
+
CrudifyLoginProps,
|
|
590
|
+
CrudifyLoginConfig,
|
|
591
|
+
|
|
592
|
+
// Tipos de hooks
|
|
593
|
+
UseAuthReturn,
|
|
594
|
+
UseUserDataReturn,
|
|
595
|
+
UseDataReturn,
|
|
596
|
+
SessionState,
|
|
597
|
+
|
|
598
|
+
// Tipos de utilidades
|
|
599
|
+
TokenData,
|
|
600
|
+
ParsedError,
|
|
601
|
+
ErrorCode
|
|
602
|
+
} from '@nocios/crudify-ui';
|
|
603
|
+
|
|
604
|
+
// Ejemplo con tipos
|
|
605
|
+
const handleLogin = async (
|
|
606
|
+
credentials: LoginRequest
|
|
607
|
+
): Promise<CrudifyApiResponse<LoginResponse>> => {
|
|
608
|
+
// implementación con tipos seguros
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## 🌐 Internacionalización
|
|
613
|
+
|
|
614
|
+
### Configuración básica
|
|
615
|
+
|
|
616
|
+
```tsx
|
|
617
|
+
import { CrudifyLogin } from '@nocios/crudify-ui';
|
|
618
|
+
|
|
619
|
+
// Usando traducciones incluidas
|
|
620
|
+
<CrudifyLogin language="es" /> // "en" | "es"
|
|
621
|
+
|
|
622
|
+
// Usando traducciones personalizadas
|
|
623
|
+
<CrudifyLogin
|
|
624
|
+
language="es"
|
|
625
|
+
translations={{
|
|
626
|
+
login: {
|
|
627
|
+
title: "Iniciar Sesión",
|
|
628
|
+
email: "Correo Electrónico",
|
|
629
|
+
password: "Contraseña",
|
|
630
|
+
submit: "Entrar"
|
|
631
|
+
}
|
|
632
|
+
}}
|
|
633
|
+
/>
|
|
634
|
+
|
|
635
|
+
// Usando URL externa para traducciones
|
|
636
|
+
<CrudifyLogin
|
|
637
|
+
language="es"
|
|
638
|
+
translationsUrl="/api/translations/es.json"
|
|
639
|
+
/>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Estructura de traducciones
|
|
643
|
+
|
|
644
|
+
```json
|
|
645
|
+
{
|
|
646
|
+
"login": {
|
|
647
|
+
"title": "Iniciar Sesión",
|
|
648
|
+
"email": "Correo Electrónico",
|
|
649
|
+
"password": "Contraseña",
|
|
650
|
+
"submit": "Entrar",
|
|
651
|
+
"forgotPassword": "¿Olvidaste tu contraseña?",
|
|
652
|
+
"logoAlt": "Logo de la aplicación"
|
|
653
|
+
},
|
|
654
|
+
"forgotPassword": {
|
|
655
|
+
"title": "Recuperar Contraseña",
|
|
656
|
+
"email": "Correo Electrónico",
|
|
657
|
+
"submit": "Enviar Código",
|
|
658
|
+
"backToLogin": "Volver al Login"
|
|
659
|
+
},
|
|
660
|
+
"resetPassword": {
|
|
661
|
+
"title": "Nueva Contraseña",
|
|
662
|
+
"password": "Nueva Contraseña",
|
|
663
|
+
"confirmPassword": "Confirmar Contraseña",
|
|
664
|
+
"submit": "Cambiar Contraseña"
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## 🔧 Configuración Avanzada
|
|
670
|
+
|
|
671
|
+
### SessionProvider con opciones completas
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
import { SessionProvider } from '@nocios/crudify-ui';
|
|
675
|
+
import type { UseSessionOptions } from '@nocios/crudify-ui';
|
|
676
|
+
|
|
677
|
+
const sessionOptions: UseSessionOptions = {
|
|
678
|
+
// Tipo de almacenamiento
|
|
679
|
+
storageType: 'localStorage', // 'localStorage' | 'sessionStorage'
|
|
680
|
+
|
|
681
|
+
// Callbacks del ciclo de vida
|
|
682
|
+
onSessionExpired: () => {
|
|
683
|
+
console.log('Sesión expirada - redirigir a login');
|
|
684
|
+
window.location.href = '/login';
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
onSessionRestored: (tokens) => {
|
|
688
|
+
console.log('Sesión restaurada exitosamente', tokens);
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
onTokensRefreshed: (tokens) => {
|
|
692
|
+
console.log('Tokens renovados', tokens);
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
onLogout: () => {
|
|
696
|
+
console.log('Usuario cerró sesión');
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
onError: (error) => {
|
|
700
|
+
console.error('Error de sesión:', error);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
function App() {
|
|
705
|
+
return (
|
|
706
|
+
<SessionProvider options={sessionOptions}>
|
|
707
|
+
<Router>
|
|
708
|
+
<Routes>
|
|
709
|
+
{/* tus rutas */}
|
|
710
|
+
</Routes>
|
|
711
|
+
</Router>
|
|
712
|
+
</SessionProvider>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Configuración manual de Crudify
|
|
718
|
+
|
|
719
|
+
Si necesitas configuración manual en lugar de variables de entorno:
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
import { crudify } from '@nocios/crudify-ui';
|
|
723
|
+
|
|
724
|
+
// Configurar manualmente antes de usar
|
|
725
|
+
crudify.configure({
|
|
726
|
+
publicApiKey: 'tu-api-key',
|
|
727
|
+
environment: 'production', // 'dev' | 'stg' | 'api' | 'prod'
|
|
728
|
+
baseUrl: 'https://api.tudominio.com' // opcional
|
|
729
|
+
});
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
## 🚀 Ejemplos de Uso Completos
|
|
733
|
+
|
|
734
|
+
### Aplicación básica con autenticación
|
|
735
|
+
|
|
736
|
+
```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';
|
|
745
|
+
|
|
746
|
+
// Componente Dashboard
|
|
747
|
+
function Dashboard() {
|
|
748
|
+
const { sessionData, logout } = useSessionContext();
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
<div>
|
|
752
|
+
<h1>Dashboard</h1>
|
|
753
|
+
<p>Bienvenido, {sessionData?.email}</p>
|
|
754
|
+
<button onClick={() => logout()}>Cerrar Sesión</button>
|
|
755
|
+
</div>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Componente Login
|
|
760
|
+
function LoginPage() {
|
|
761
|
+
return (
|
|
762
|
+
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '2rem' }}>
|
|
763
|
+
<CrudifyLogin
|
|
764
|
+
config={{
|
|
765
|
+
appName: "Mi App",
|
|
766
|
+
colors: { primaryColor: "#1976d2" }
|
|
767
|
+
}}
|
|
768
|
+
onLoginSuccess={(userData) => {
|
|
769
|
+
console.log('Login exitoso:', userData);
|
|
770
|
+
// La navegación se maneja automáticamente
|
|
771
|
+
}}
|
|
772
|
+
/>
|
|
773
|
+
</div>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Aplicación principal
|
|
778
|
+
function App() {
|
|
779
|
+
return (
|
|
780
|
+
<SessionProvider>
|
|
781
|
+
<BrowserRouter>
|
|
782
|
+
<Routes>
|
|
783
|
+
<Route path="/login" element={<LoginPage />} />
|
|
784
|
+
<Route
|
|
785
|
+
path="/dashboard"
|
|
786
|
+
element={
|
|
787
|
+
<ProtectedRoute fallback={<Navigate to="/login" />}>
|
|
788
|
+
<Dashboard />
|
|
789
|
+
</ProtectedRoute>
|
|
790
|
+
}
|
|
791
|
+
/>
|
|
792
|
+
<Route path="/" element={<Navigate to="/dashboard" />} />
|
|
793
|
+
</Routes>
|
|
794
|
+
</BrowserRouter>
|
|
795
|
+
</SessionProvider>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export default App;
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Aplicación con operaciones CRUD
|
|
803
|
+
|
|
804
|
+
```tsx
|
|
805
|
+
import React, { useState, useEffect } from 'react';
|
|
806
|
+
import { useData, useUserData } from '@nocios/crudify-ui';
|
|
807
|
+
|
|
808
|
+
function ProductManager() {
|
|
809
|
+
const [products, setProducts] = useState([]);
|
|
810
|
+
const [loading, setLoading] = useState(false);
|
|
811
|
+
|
|
812
|
+
const { create, read, update, remove } = useData();
|
|
813
|
+
const { userData } = useUserData();
|
|
814
|
+
|
|
815
|
+
// Cargar productos al montar
|
|
816
|
+
useEffect(() => {
|
|
817
|
+
loadProducts();
|
|
818
|
+
}, []);
|
|
819
|
+
|
|
820
|
+
const loadProducts = async () => {
|
|
821
|
+
setLoading(true);
|
|
822
|
+
try {
|
|
823
|
+
const result = await read('products', {
|
|
824
|
+
filters: { userId: userData?.id },
|
|
825
|
+
limit: 20
|
|
826
|
+
});
|
|
827
|
+
setProducts(result.data || []);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
console.error('Error cargando productos:', error);
|
|
830
|
+
} finally {
|
|
831
|
+
setLoading(false);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const createProduct = async (productData) => {
|
|
836
|
+
try {
|
|
837
|
+
await create('products', {
|
|
838
|
+
...productData,
|
|
839
|
+
userId: userData?.id
|
|
840
|
+
});
|
|
841
|
+
await loadProducts(); // Recargar lista
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error('Error creando producto:', error);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
const updateProduct = async (productId, updates) => {
|
|
848
|
+
try {
|
|
849
|
+
await update('products', productId, updates);
|
|
850
|
+
await loadProducts(); // Recargar lista
|
|
851
|
+
} catch (error) {
|
|
852
|
+
console.error('Error actualizando producto:', error);
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const deleteProduct = async (productId) => {
|
|
857
|
+
try {
|
|
858
|
+
await remove('products', productId);
|
|
859
|
+
await loadProducts(); // Recargar lista
|
|
860
|
+
} catch (error) {
|
|
861
|
+
console.error('Error eliminando producto:', error);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
if (loading) return <div>Cargando productos...</div>;
|
|
866
|
+
|
|
867
|
+
return (
|
|
868
|
+
<div>
|
|
869
|
+
<h2>Mis Productos</h2>
|
|
870
|
+
|
|
871
|
+
<button onClick={() => createProduct({
|
|
872
|
+
name: 'Nuevo Producto',
|
|
873
|
+
price: 99.99
|
|
874
|
+
})}>
|
|
875
|
+
Crear Producto
|
|
876
|
+
</button>
|
|
877
|
+
|
|
878
|
+
<ul>
|
|
879
|
+
{products.map(product => (
|
|
880
|
+
<li key={product.id}>
|
|
881
|
+
<span>{product.name} - ${product.price}</span>
|
|
882
|
+
<button onClick={() => updateProduct(product.id, {
|
|
883
|
+
price: product.price * 1.1
|
|
884
|
+
})}>
|
|
885
|
+
Aumentar Precio 10%
|
|
886
|
+
</button>
|
|
887
|
+
<button onClick={() => deleteProduct(product.id)}>
|
|
888
|
+
Eliminar
|
|
889
|
+
</button>
|
|
890
|
+
</li>
|
|
891
|
+
))}
|
|
892
|
+
</ul>
|
|
893
|
+
</div>
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
## 🔒 Seguridad
|
|
899
|
+
|
|
900
|
+
### Características de Seguridad
|
|
901
|
+
|
|
902
|
+
- ✅ **Encriptación de tokens**: Los tokens se almacenan encriptados
|
|
903
|
+
- ✅ **Refresh Token Pattern**: Tokens de acceso de corta duración
|
|
904
|
+
- ✅ **Validación automática**: Verificación de expiración y renovación
|
|
905
|
+
- ✅ **Almacenamiento seguro**: Soporte para localStorage y sessionStorage
|
|
906
|
+
- ✅ **Limpieza automática**: Tokens expirados se limpian automáticamente
|
|
907
|
+
|
|
908
|
+
### Mejores Prácticas
|
|
909
|
+
|
|
910
|
+
```tsx
|
|
911
|
+
// 1. Usar SessionProvider en el nivel más alto
|
|
912
|
+
function App() {
|
|
913
|
+
return (
|
|
914
|
+
<SessionProvider options={{
|
|
915
|
+
storageType: 'localStorage', // Más persistente
|
|
916
|
+
onSessionExpired: handleSessionExpired
|
|
917
|
+
}}>
|
|
918
|
+
<YourApp />
|
|
919
|
+
</SessionProvider>
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// 2. Proteger rutas sensibles
|
|
924
|
+
<ProtectedRoute fallback={<LoginRedirect />}>
|
|
925
|
+
<SensitiveComponent />
|
|
926
|
+
</ProtectedRoute>
|
|
927
|
+
|
|
928
|
+
// 3. Manejar errores de autenticación
|
|
929
|
+
const { error, clearError } = useAuth();
|
|
930
|
+
useEffect(() => {
|
|
931
|
+
if (error) {
|
|
932
|
+
console.error('Error de auth:', error);
|
|
933
|
+
// Mostrar notificación al usuario
|
|
934
|
+
clearError();
|
|
935
|
+
}
|
|
936
|
+
}, [error]);
|
|
937
|
+
|
|
938
|
+
// 4. Limpiar sesión al salir
|
|
939
|
+
useEffect(() => {
|
|
940
|
+
const handleBeforeUnload = () => {
|
|
941
|
+
// Opcional: limpiar datos sensibles
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
945
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
946
|
+
}, []);
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
## 🐛 Troubleshooting
|
|
950
|
+
|
|
951
|
+
### Problemas Comunes
|
|
952
|
+
|
|
953
|
+
**1. Token expirado constantemente**
|
|
954
|
+
```tsx
|
|
955
|
+
// Verificar configuración de refresh tokens
|
|
956
|
+
const { isExpiringSoon, refreshTokens } = useSession();
|
|
957
|
+
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
if (isExpiringSoon) {
|
|
960
|
+
refreshTokens();
|
|
961
|
+
}
|
|
962
|
+
}, [isExpiringSoon]);
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
**2. Sesión no se restaura al recargar**
|
|
966
|
+
```tsx
|
|
967
|
+
// Asegurar que SessionProvider esté en el nivel correcto
|
|
968
|
+
// y verificar storageType
|
|
969
|
+
<SessionProvider options={{ storageType: 'localStorage' }}>
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**3. Errores de CORS**
|
|
973
|
+
```tsx
|
|
974
|
+
// Verificar configuración de environment
|
|
975
|
+
crudify.configure({
|
|
976
|
+
environment: 'dev', // Asegurar environment correcto
|
|
977
|
+
publicApiKey: process.env.REACT_APP_CRUDIFY_PUBLIC_API_KEY
|
|
978
|
+
});
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
### Debug
|
|
982
|
+
|
|
983
|
+
```tsx
|
|
984
|
+
// Usar SessionDebugInfo para desarrollo
|
|
985
|
+
import { SessionDebugInfo } from '@nocios/crudify-ui';
|
|
986
|
+
|
|
987
|
+
function DevPanel() {
|
|
988
|
+
return process.env.NODE_ENV === 'development' ? (
|
|
989
|
+
<div style={{ position: 'fixed', bottom: 0, right: 0, background: 'white', padding: '1rem' }}>
|
|
990
|
+
<SessionDebugInfo />
|
|
991
|
+
</div>
|
|
992
|
+
) : null;
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
## 📚 Referencias de API
|
|
997
|
+
|
|
998
|
+
### Hooks API Reference
|
|
999
|
+
|
|
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 }` |
|
|
1007
|
+
|
|
1008
|
+
### Componentes API Reference
|
|
1009
|
+
|
|
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 |
|
|
1016
|
+
|
|
1017
|
+
### Utilidades API Reference
|
|
1018
|
+
|
|
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 |
|
|
1024
|
+
|
|
1025
|
+
## 🔄 Changelog
|
|
1026
|
+
|
|
1027
|
+
### v1.3.1
|
|
1028
|
+
- ✅ Sistema completo de Refresh Token Pattern
|
|
1029
|
+
- ✅ Hooks modernos (useAuth, useUserData, useData)
|
|
1030
|
+
- ✅ SessionProvider con contexto global
|
|
1031
|
+
- ✅ Componentes ProtectedRoute y SessionDebugInfo
|
|
1032
|
+
- ✅ Almacenamiento encriptado de tokens
|
|
1033
|
+
- ✅ Sistema completo de manejo de errores
|
|
1034
|
+
- ✅ Re-exportación completa de @nocios/crudify-browser
|
|
1035
|
+
|
|
1036
|
+
## 📄 Licencia
|
|
1037
|
+
|
|
1038
|
+
MIT © [Nocios](https://github.com/nocios)
|
|
1039
|
+
|
|
1040
|
+
## 🤝 Contribuir
|
|
1041
|
+
|
|
1042
|
+
Si encuentras bugs o tienes sugerencias, por favor crea un issue en el repositorio oficial.
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
**¿Necesitas ayuda?** Consulta la documentación completa o contacta al equipo de desarrollo.
|