@nocios/crudify-components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.github/workflows/test.yml +59 -0
  2. package/.nvmrc +1 -0
  3. package/README.md +398 -0
  4. package/README_DEPTH.md +1230 -0
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/coverage-final.json +85 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +506 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/sort-arrow-sprite.png +0 -0
  13. package/coverage/sorter.js +210 -0
  14. package/dist/CrudiaMarkdownField-C54-A_J3.d.mts +328 -0
  15. package/dist/CrudiaMarkdownField-C8HQh7s5.d.ts +328 -0
  16. package/dist/GlobalNotificationProvider-Zq18OkpI.d.mts +96 -0
  17. package/dist/GlobalNotificationProvider-Zq18OkpI.d.ts +96 -0
  18. package/dist/api-B4uXiHF0.d.mts +118 -0
  19. package/dist/api-B4uXiHF0.d.ts +118 -0
  20. package/dist/chunk-2XOTIEKS.js +1 -0
  21. package/dist/chunk-5HFI5CZ5.js +1 -0
  22. package/dist/chunk-CHDM7KGH.js +1 -0
  23. package/dist/chunk-HVTRRU4W.mjs +1 -0
  24. package/dist/chunk-JAPL7EZJ.mjs +1 -0
  25. package/dist/chunk-JNEWPO2J.mjs +1 -0
  26. package/dist/chunk-MFYHD6S5.js +1 -0
  27. package/dist/chunk-MGJZTOEM.mjs +1 -0
  28. package/dist/chunk-NBQH6QOU.mjs +1 -0
  29. package/dist/chunk-NSV6ECYO.js +1 -0
  30. package/dist/chunk-PNI3ZBZV.js +1 -0
  31. package/dist/chunk-U4RS66TB.mjs +1 -0
  32. package/dist/components.d.mts +24 -0
  33. package/dist/components.d.ts +24 -0
  34. package/dist/components.js +1 -0
  35. package/dist/components.mjs +1 -0
  36. package/dist/errorTranslation-DGdrMidg.d.ts +143 -0
  37. package/dist/errorTranslation-qwwQTvCO.d.mts +143 -0
  38. package/dist/hooks.d.mts +6 -0
  39. package/dist/hooks.d.ts +6 -0
  40. package/dist/hooks.js +1 -0
  41. package/dist/hooks.mjs +1 -0
  42. package/dist/index-BUKX3duW.d.ts +854 -0
  43. package/dist/index-Y9tTsinC.d.mts +854 -0
  44. package/dist/index.d.mts +1274 -0
  45. package/dist/index.d.ts +1274 -0
  46. package/dist/index.js +6 -0
  47. package/dist/index.mjs +6 -0
  48. package/dist/utils.d.mts +175 -0
  49. package/dist/utils.d.ts +175 -0
  50. package/dist/utils.js +1 -0
  51. package/dist/utils.mjs +1 -0
  52. package/package.json +88 -0
  53. package/vitest.config.ts +28 -0
@@ -0,0 +1,1230 @@
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 } = 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),
262
+ });
263
+
264
+ return (
265
+ <div>
266
+ <h3>Estado de Sesión</h3>
267
+ <p>Autenticado: {isAuthenticated ? "Sí" : "No"}</p>
268
+ <p>Cargando: {isLoading ? "Sí" : "No"}</p>
269
+ <p>Expira en: {Math.floor(expiresIn / 60)} minutos</p>
270
+ <p>Expira pronto: {isExpiringSoon ? "Sí" : "No"}</p>
271
+
272
+ {isExpiringSoon && <button onClick={() => refreshTokens()}>Renovar Tokens</button>}
273
+
274
+ {tokens && (
275
+ <div>
276
+ <p>Access Token: {tokens.accessToken.substring(0, 20)}...</p>
277
+ <p>Refresh Token: {tokens.refreshToken?.substring(0, 20)}...</p>
278
+ </div>
279
+ )}
280
+ </div>
281
+ );
282
+ }
283
+ ```
284
+
285
+ ### useSessionContext - Contexto de Sesión
286
+
287
+ Hook para acceder al contexto global de sesión:
288
+
289
+ ```tsx
290
+ import { useSessionContext } from "@nocios/crudify-components";
291
+
292
+ function NavigationBar() {
293
+ const { isAuthenticated, sessionData, logout, getTokenInfo } = useSessionContext();
294
+
295
+ if (!isAuthenticated) return null;
296
+
297
+ return (
298
+ <nav>
299
+ <span>Bienvenido, {sessionData?.email}</span>
300
+ <button onClick={() => logout()}>Cerrar Sesión</button>
301
+ <button onClick={() => console.log(getTokenInfo())}>Ver Token Info</button>
302
+ </nav>
303
+ );
304
+ }
305
+ ```
306
+
307
+ ## 🎨 Componentes UI
308
+
309
+ ### CrudifyLogin - Componente de Login Completo
310
+
311
+ Componente completo de autenticación con múltiples pantallas:
312
+
313
+ ```tsx
314
+ import { CrudifyLogin } from "@nocios/crudify-components";
315
+ import type { CrudifyLoginConfig } from "@nocios/crudify-components";
316
+
317
+ function AuthPage() {
318
+ const config: CrudifyLoginConfig = {
319
+ appName: "Mi Aplicación",
320
+ logo: "/logo.png",
321
+ colors: {
322
+ primaryColor: "#1066BA",
323
+ bgColor: "#f5f5f5",
324
+ },
325
+ };
326
+
327
+ const handleLoginSuccess = (userData, redirectUrl) => {
328
+ console.log("Login exitoso:", userData);
329
+ // Redireccionar o actualizar estado
330
+ };
331
+
332
+ const handleError = (error: string) => {
333
+ console.error("Error de autenticación:", error);
334
+ };
335
+
336
+ return (
337
+ <CrudifyLogin
338
+ config={config}
339
+ onLoginSuccess={handleLoginSuccess}
340
+ onError={handleError}
341
+ initialScreen="login" // "login" | "forgotPassword" | "resetPassword"
342
+ language="es" // "en" | "es"
343
+ redirectUrl="/dashboard"
344
+ autoReadFromCookies={true}
345
+ />
346
+ );
347
+ }
348
+ ```
349
+
350
+ **Pantallas incluidas en CrudifyLogin:**
351
+
352
+ - 🔐 **Login**: Formulario principal de autenticación
353
+ - 🔑 **Forgot Password**: Solicitar recuperación de contraseña
354
+ - 📧 **Check Code**: Verificar código de recuperación
355
+ - 🔄 **Reset Password**: Establecer nueva contraseña
356
+
357
+ **Configuración disponible:**
358
+
359
+ - ✅ Logo y nombre de aplicación personalizable
360
+ - ✅ Colores y temas personalizables
361
+ - ✅ Traducciones completas (ES/EN)
362
+ - ✅ URLs de traducción externas
363
+ - ✅ Callbacks para todos los eventos
364
+
365
+ ### UserProfileDisplay - Perfil de Usuario
366
+
367
+ Componente para mostrar información del perfil del usuario:
368
+
369
+ ```tsx
370
+ import { UserProfileDisplay } from "@nocios/crudify-components";
371
+
372
+ function ProfilePage() {
373
+ return (
374
+ <div>
375
+ <h1>Mi Perfil</h1>
376
+ <UserProfileDisplay />
377
+ </div>
378
+ );
379
+ }
380
+ ```
381
+
382
+ ### ProtectedRoute - Protección de Rutas
383
+
384
+ Componente para proteger rutas que requieren autenticación:
385
+
386
+ ```tsx
387
+ import { ProtectedRoute } from "@nocios/crudify-components";
388
+
389
+ function App() {
390
+ return (
391
+ <Router>
392
+ <Routes>
393
+ <Route path="/login" element={<LoginPage />} />
394
+ <Route
395
+ path="/dashboard"
396
+ element={
397
+ <ProtectedRoute fallback={<LoginPage />}>
398
+ <Dashboard />
399
+ </ProtectedRoute>
400
+ }
401
+ />
402
+ </Routes>
403
+ </Router>
404
+ );
405
+ }
406
+ ```
407
+
408
+ ### SessionDebugInfo - Debug de Sesión
409
+
410
+ Componente útil para desarrollo y debug:
411
+
412
+ ```tsx
413
+ import { SessionDebugInfo } from "@nocios/crudify-components";
414
+
415
+ function DevPage() {
416
+ return (
417
+ <div>
418
+ <h2>Información de Sesión (Dev)</h2>
419
+ <SessionDebugInfo />
420
+ </div>
421
+ );
422
+ }
423
+ ```
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-components";
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
+
642
+ ## 🛠️ Utilidades
643
+
644
+ ### JWT Utils
645
+
646
+ Utilidades para trabajar con tokens JWT:
647
+
648
+ ```tsx
649
+ import { decodeJwtSafely, getCurrentUserEmail, isTokenExpired } from "@nocios/crudify-components";
650
+
651
+ // Decodificar JWT de forma segura
652
+ const payload = decodeJwtSafely(token);
653
+ console.log("Usuario ID:", payload?.sub);
654
+
655
+ // Obtener email del usuario actual
656
+ const email = getCurrentUserEmail();
657
+ console.log("Email:", email);
658
+
659
+ // Verificar si un token está expirado
660
+ const expired = isTokenExpired(token);
661
+ console.log("Token expirado:", expired);
662
+ ```
663
+
664
+ ### Token Storage
665
+
666
+ Sistema de almacenamiento seguro para tokens:
667
+
668
+ ```tsx
669
+ import { TokenStorage } from "@nocios/crudify-components";
670
+ import type { TokenData, StorageType } from "@nocios/crudify-components";
671
+
672
+ // Crear instancia de storage
673
+ const storage = new TokenStorage({
674
+ type: "localStorage", // 'localStorage' | 'sessionStorage'
675
+ encryptionKey: "mi-clave-secreta",
676
+ });
677
+
678
+ // Guardar tokens
679
+ const tokens: TokenData = {
680
+ accessToken: "access_token_here",
681
+ refreshToken: "refresh_token_here",
682
+ expiresIn: 3600,
683
+ refreshExpiresIn: 86400,
684
+ };
685
+
686
+ await storage.setTokens(tokens);
687
+
688
+ // Obtener tokens
689
+ const storedTokens = await storage.getTokens();
690
+ console.log("Tokens almacenados:", storedTokens);
691
+
692
+ // Limpiar tokens
693
+ await storage.clearTokens();
694
+
695
+ // Verificar validez
696
+ const isValid = await storage.isValid();
697
+ console.log("Tokens válidos:", isValid);
698
+ ```
699
+
700
+ ### Error Handler
701
+
702
+ Sistema completo de manejo de errores:
703
+
704
+ ```tsx
705
+ import { handleCrudifyError, parseApiError, parseTransactionError, getErrorMessage, ERROR_CODES } from "@nocios/crudify-components";
706
+ import type { ParsedError, ErrorCode } from "@nocios/crudify-components";
707
+
708
+ // Manejo universal de errores
709
+ try {
710
+ // operación que puede fallar
711
+ } catch (error) {
712
+ const parsedError: ParsedError = handleCrudifyError(error);
713
+
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);
718
+
719
+ // Mostrar mensaje apropiado al usuario
720
+ const userMessage = getErrorMessage(parsedError.code as ErrorCode);
721
+ alert(userMessage);
722
+ }
723
+
724
+ // Parser específico para errores de API
725
+ const apiError = parseApiError(response);
726
+
727
+ // Parser específico para errores de transacción
728
+ const transactionError = parseTransactionError(response);
729
+
730
+ // Códigos de error disponibles
731
+ console.log("Códigos disponibles:", ERROR_CODES);
732
+ ```
733
+
734
+ ## 🔄 API de Crudify Browser
735
+
736
+ Esta librería re-exporta completamente `@nocios/crudify-sdk`, proporcionando una API unificada:
737
+
738
+ ```tsx
739
+ import { crudify } from "@nocios/crudify-components";
740
+ // Equivale a: import crudify from '@nocios/crudify-sdk';
741
+
742
+ // Todas las funcionalidades de crudify-browser están disponibles
743
+ const result = await crudify.create("users", userData);
744
+ const users = await crudify.read("users");
745
+ ```
746
+
747
+ Esto significa que puedes:
748
+
749
+ - ✅ Importar solo desde `@nocios/crudify-components`
750
+ - ✅ Usar todas las funcionalidades de crudify-browser
751
+ - ✅ Tener una API unificada
752
+ - ✅ Simplificar las dependencias
753
+
754
+ ## 📱 TypeScript Support
755
+
756
+ La librería incluye tipos completos para TypeScript:
757
+
758
+ ```tsx
759
+ import type {
760
+ // Tipos de API
761
+ CrudifyApiResponse,
762
+ CrudifyTransactionResponse,
763
+ UserProfile,
764
+ LoginResponse,
765
+ LoginRequest,
766
+
767
+ // Tipos de componentes
768
+ CrudifyLoginProps,
769
+ CrudifyLoginConfig,
770
+
771
+ // Tipos de hooks
772
+ UseAuthReturn,
773
+ UseUserDataReturn,
774
+ UseDataReturn,
775
+ SessionState,
776
+
777
+ // Tipos de utilidades
778
+ TokenData,
779
+ ParsedError,
780
+ ErrorCode,
781
+ } from "@nocios/crudify-components";
782
+
783
+ // Ejemplo con tipos
784
+ const handleLogin = async (credentials: LoginRequest): Promise<CrudifyApiResponse<LoginResponse>> => {
785
+ // implementación con tipos seguros
786
+ };
787
+ ```
788
+
789
+ ## 🌐 Internacionalización
790
+
791
+ ### Configuración básica
792
+
793
+ ```tsx
794
+ import { CrudifyLogin } from '@nocios/crudify-components';
795
+
796
+ // Usando traducciones incluidas
797
+ <CrudifyLogin language="es" /> // "en" | "es"
798
+
799
+ // Usando traducciones personalizadas
800
+ <CrudifyLogin
801
+ language="es"
802
+ translations={{
803
+ login: {
804
+ title: "Iniciar Sesión",
805
+ email: "Correo Electrónico",
806
+ password: "Contraseña",
807
+ submit: "Entrar"
808
+ }
809
+ }}
810
+ />
811
+
812
+ // Usando URL externa para traducciones
813
+ <CrudifyLogin
814
+ language="es"
815
+ translationsUrl="/api/translations/es.json"
816
+ />
817
+ ```
818
+
819
+ ### Estructura de traducciones
820
+
821
+ ```json
822
+ {
823
+ "login": {
824
+ "title": "Iniciar Sesión",
825
+ "email": "Correo Electrónico",
826
+ "password": "Contraseña",
827
+ "submit": "Entrar",
828
+ "forgotPassword": "¿Olvidaste tu contraseña?",
829
+ "logoAlt": "Logo de la aplicación"
830
+ },
831
+ "forgotPassword": {
832
+ "title": "Recuperar Contraseña",
833
+ "email": "Correo Electrónico",
834
+ "submit": "Enviar Código",
835
+ "backToLogin": "Volver al Login"
836
+ },
837
+ "resetPassword": {
838
+ "title": "Nueva Contraseña",
839
+ "password": "Nueva Contraseña",
840
+ "confirmPassword": "Confirmar Contraseña",
841
+ "submit": "Cambiar Contraseña"
842
+ }
843
+ }
844
+ ```
845
+
846
+ ## 🔧 Configuración Avanzada
847
+
848
+ ### SessionProvider con opciones completas
849
+
850
+ ```tsx
851
+ import { SessionProvider } from "@nocios/crudify-components";
852
+ import type { UseSessionOptions } from "@nocios/crudify-components";
853
+
854
+ const sessionOptions: UseSessionOptions = {
855
+ // Tipo de almacenamiento
856
+ storageType: "localStorage", // 'localStorage' | 'sessionStorage'
857
+
858
+ // Callbacks del ciclo de vida
859
+ onSessionExpired: () => {
860
+ console.log("Sesión expirada - redirigir a login");
861
+ window.location.href = "/login";
862
+ },
863
+
864
+ onSessionRestored: (tokens) => {
865
+ console.log("Sesión restaurada exitosamente", tokens);
866
+ },
867
+
868
+ onTokensRefreshed: (tokens) => {
869
+ console.log("Tokens renovados", tokens);
870
+ },
871
+
872
+ onLogout: () => {
873
+ console.log("Usuario cerró sesión");
874
+ },
875
+
876
+ onError: (error) => {
877
+ console.error("Error de sesión:", error);
878
+ },
879
+ };
880
+
881
+ function App() {
882
+ return (
883
+ <SessionProvider options={sessionOptions}>
884
+ <Router>
885
+ <Routes>{/* tus rutas */}</Routes>
886
+ </Router>
887
+ </SessionProvider>
888
+ );
889
+ }
890
+ ```
891
+
892
+ ### Configuración manual de Crudify
893
+
894
+ Si necesitas configuración manual en lugar de variables de entorno:
895
+
896
+ ```tsx
897
+ import { crudify } from "@nocios/crudify-components";
898
+
899
+ // Configurar manualmente antes de usar
900
+ crudify.configure({
901
+ publicApiKey: "tu-api-key",
902
+ environment: "production", // 'dev' | 'stg' | 'api' | 'prod'
903
+ baseUrl: "https://api.tudominio.com", // opcional
904
+ });
905
+ ```
906
+
907
+ ## 🚀 Ejemplos de Uso Completos
908
+
909
+ ### Aplicación básica con autenticación
910
+
911
+ ```tsx
912
+ import React from "react";
913
+ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
914
+ import { SessionProvider, ProtectedRoute, CrudifyLogin, useSessionContext } from "@nocios/crudify-components";
915
+
916
+ // Componente Dashboard
917
+ function Dashboard() {
918
+ const { sessionData, logout } = useSessionContext();
919
+
920
+ return (
921
+ <div>
922
+ <h1>Dashboard</h1>
923
+ <p>Bienvenido, {sessionData?.email}</p>
924
+ <button onClick={() => logout()}>Cerrar Sesión</button>
925
+ </div>
926
+ );
927
+ }
928
+
929
+ // Componente Login
930
+ function LoginPage() {
931
+ return (
932
+ <div style={{ maxWidth: "400px", margin: "0 auto", padding: "2rem" }}>
933
+ <CrudifyLogin
934
+ config={{
935
+ appName: "Mi App",
936
+ colors: { primaryColor: "#1976d2" },
937
+ }}
938
+ onLoginSuccess={(userData) => {
939
+ console.log("Login exitoso:", userData);
940
+ // La navegación se maneja automáticamente
941
+ }}
942
+ />
943
+ </div>
944
+ );
945
+ }
946
+
947
+ // Aplicación principal
948
+ function App() {
949
+ return (
950
+ <SessionProvider>
951
+ <BrowserRouter>
952
+ <Routes>
953
+ <Route path="/login" element={<LoginPage />} />
954
+ <Route
955
+ path="/dashboard"
956
+ element={
957
+ <ProtectedRoute fallback={<Navigate to="/login" />}>
958
+ <Dashboard />
959
+ </ProtectedRoute>
960
+ }
961
+ />
962
+ <Route path="/" element={<Navigate to="/dashboard" />} />
963
+ </Routes>
964
+ </BrowserRouter>
965
+ </SessionProvider>
966
+ );
967
+ }
968
+
969
+ export default App;
970
+ ```
971
+
972
+ ### Aplicación con operaciones CRUD
973
+
974
+ ```tsx
975
+ import React, { useState, useEffect } from "react";
976
+ import { useData, useUserData } from "@nocios/crudify-components";
977
+
978
+ function ProductManager() {
979
+ const [products, setProducts] = useState([]);
980
+ const [loading, setLoading] = useState(false);
981
+
982
+ const { create, read, update, remove } = useData();
983
+ const { userData } = useUserData();
984
+
985
+ // Cargar productos al montar
986
+ useEffect(() => {
987
+ loadProducts();
988
+ }, []);
989
+
990
+ const loadProducts = async () => {
991
+ setLoading(true);
992
+ try {
993
+ const result = await read("products", {
994
+ filters: { userId: userData?.id },
995
+ limit: 20,
996
+ });
997
+ setProducts(result.data || []);
998
+ } catch (error) {
999
+ console.error("Error cargando productos:", error);
1000
+ } finally {
1001
+ setLoading(false);
1002
+ }
1003
+ };
1004
+
1005
+ const createProduct = async (productData) => {
1006
+ try {
1007
+ await create("products", {
1008
+ ...productData,
1009
+ userId: userData?.id,
1010
+ });
1011
+ await loadProducts(); // Recargar lista
1012
+ } catch (error) {
1013
+ console.error("Error creando producto:", error);
1014
+ }
1015
+ };
1016
+
1017
+ const updateProduct = async (productId, updates) => {
1018
+ try {
1019
+ await update("products", productId, updates);
1020
+ await loadProducts(); // Recargar lista
1021
+ } catch (error) {
1022
+ console.error("Error actualizando producto:", error);
1023
+ }
1024
+ };
1025
+
1026
+ const deleteProduct = async (productId) => {
1027
+ try {
1028
+ await remove("products", productId);
1029
+ await loadProducts(); // Recargar lista
1030
+ } catch (error) {
1031
+ console.error("Error eliminando producto:", error);
1032
+ }
1033
+ };
1034
+
1035
+ if (loading) return <div>Cargando productos...</div>;
1036
+
1037
+ return (
1038
+ <div>
1039
+ <h2>Mis Productos</h2>
1040
+
1041
+ <button
1042
+ onClick={() =>
1043
+ createProduct({
1044
+ name: "Nuevo Producto",
1045
+ price: 99.99,
1046
+ })
1047
+ }
1048
+ >
1049
+ Crear Producto
1050
+ </button>
1051
+
1052
+ <ul>
1053
+ {products.map((product) => (
1054
+ <li key={product.id}>
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
+ >
1065
+ Aumentar Precio 10%
1066
+ </button>
1067
+ <button onClick={() => deleteProduct(product.id)}>Eliminar</button>
1068
+ </li>
1069
+ ))}
1070
+ </ul>
1071
+ </div>
1072
+ );
1073
+ }
1074
+ ```
1075
+
1076
+ ## 🔒 Seguridad
1077
+
1078
+ ### Características de Seguridad
1079
+
1080
+ - ✅ **Encriptación de tokens**: Los tokens se almacenan encriptados
1081
+ - ✅ **Refresh Token Pattern**: Tokens de acceso de corta duración
1082
+ - ✅ **Validación automática**: Verificación de expiración y renovación
1083
+ - ✅ **Almacenamiento seguro**: Soporte para localStorage y sessionStorage
1084
+ - ✅ **Limpieza automática**: Tokens expirados se limpian automáticamente
1085
+
1086
+ ### Mejores Prácticas
1087
+
1088
+ ```tsx
1089
+ // 1. Usar SessionProvider en el nivel más alto
1090
+ function App() {
1091
+ return (
1092
+ <SessionProvider
1093
+ options={{
1094
+ storageType: "localStorage", // Más persistente
1095
+ onSessionExpired: handleSessionExpired,
1096
+ }}
1097
+ >
1098
+ <YourApp />
1099
+ </SessionProvider>
1100
+ );
1101
+ }
1102
+
1103
+ // 2. Proteger rutas sensibles
1104
+ <ProtectedRoute fallback={<LoginRedirect />}>
1105
+ <SensitiveComponent />
1106
+ </ProtectedRoute>;
1107
+
1108
+ // 3. Manejar errores de autenticación
1109
+ const { error, clearError } = useAuth();
1110
+ useEffect(() => {
1111
+ if (error) {
1112
+ console.error("Error de auth:", error);
1113
+ // Mostrar notificación al usuario
1114
+ clearError();
1115
+ }
1116
+ }, [error]);
1117
+
1118
+ // 4. Limpiar sesión al salir
1119
+ useEffect(() => {
1120
+ const handleBeforeUnload = () => {
1121
+ // Opcional: limpiar datos sensibles
1122
+ };
1123
+
1124
+ window.addEventListener("beforeunload", handleBeforeUnload);
1125
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
1126
+ }, []);
1127
+ ```
1128
+
1129
+ ## 🐛 Troubleshooting
1130
+
1131
+ ### Problemas Comunes
1132
+
1133
+ **1. Token expirado constantemente**
1134
+
1135
+ ```tsx
1136
+ // Verificar configuración de refresh tokens
1137
+ const { isExpiringSoon, refreshTokens } = useSession();
1138
+
1139
+ useEffect(() => {
1140
+ if (isExpiringSoon) {
1141
+ refreshTokens();
1142
+ }
1143
+ }, [isExpiringSoon]);
1144
+ ```
1145
+
1146
+ **2. Sesión no se restaura al recargar**
1147
+
1148
+ ```tsx
1149
+ // Asegurar que SessionProvider esté en el nivel correcto
1150
+ // y verificar storageType
1151
+ <SessionProvider options={{ storageType: 'localStorage' }}>
1152
+ ```
1153
+
1154
+ **3. Errores de CORS**
1155
+
1156
+ ```tsx
1157
+ // Verificar configuración de environment
1158
+ crudify.configure({
1159
+ environment: "dev", // Asegurar environment correcto
1160
+ publicApiKey: process.env.REACT_APP_CRUDIFY_PUBLIC_API_KEY,
1161
+ });
1162
+ ```
1163
+
1164
+ ### Debug
1165
+
1166
+ ```tsx
1167
+ // Usar SessionDebugInfo para desarrollo
1168
+ import { SessionDebugInfo } from "@nocios/crudify-components";
1169
+
1170
+ function DevPanel() {
1171
+ return process.env.NODE_ENV === "development" ? (
1172
+ <div style={{ position: "fixed", bottom: 0, right: 0, background: "white", padding: "1rem" }}>
1173
+ <SessionDebugInfo />
1174
+ </div>
1175
+ ) : null;
1176
+ }
1177
+ ```
1178
+
1179
+ ## 📚 Referencias de API
1180
+
1181
+ ### Hooks API Reference
1182
+
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 }` |
1190
+
1191
+ ### Componentes API Reference
1192
+
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 |
1199
+
1200
+ ### Utilidades API Reference
1201
+
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 |
1207
+
1208
+ ## 🔄 Changelog
1209
+
1210
+ ### v1.3.1
1211
+
1212
+ - ✅ Sistema completo de Refresh Token Pattern
1213
+ - ✅ Hooks modernos (useAuth, useUserData, useData)
1214
+ - ✅ SessionProvider con contexto global
1215
+ - ✅ Componentes ProtectedRoute y SessionDebugInfo
1216
+ - ✅ Almacenamiento encriptado de tokens
1217
+ - ✅ Sistema completo de manejo de errores
1218
+ - ✅ Re-exportación completa de @nocios/crudify-sdk
1219
+
1220
+ ## 📄 Licencia
1221
+
1222
+ MIT © [Nocios](https://github.com/nocios)
1223
+
1224
+ ## 🤝 Contribuir
1225
+
1226
+ Si encuentras bugs o tienes sugerencias, por favor crea un issue en el repositorio oficial.
1227
+
1228
+ ---
1229
+
1230
+ **¿Necesitas ayuda?** Consulta la documentación completa o contacta al equipo de desarrollo.