max-remotes-helper 1.0.0 → 1.0.1

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 CHANGED
@@ -25,7 +25,7 @@ npm install bo-remotes-helper
25
25
  ### Basic Setup
26
26
 
27
27
  ```tsx
28
- import { RemoteWrapper, useRemoteAuth, Secured } from 'bo-remotes-helper';
28
+ import { RemoteWrapper, useRemoteAuth, Secured } from "bo-remotes-helper";
29
29
 
30
30
  // Wrap your remote component
31
31
  export const MyRemoteComponent = ({ token }) => {
@@ -39,11 +39,11 @@ export const MyRemoteComponent = ({ token }) => {
39
39
  // Use authentication context
40
40
  const MyContent = () => {
41
41
  const { state, hasPermission } = useRemoteAuth();
42
-
42
+
43
43
  return (
44
44
  <div>
45
45
  <h1>Welcome {state.user?.name}</h1>
46
-
46
+
47
47
  <Secured permission="edit">
48
48
  <button>Edit Content</button>
49
49
  </Secured>
@@ -55,12 +55,12 @@ const MyContent = () => {
55
55
  ### Module Federation Configuration
56
56
 
57
57
  ```javascript
58
- import { useRemoteConfig } from 'bo-remotes-helper';
58
+ import { useRemoteConfig } from "bo-remotes-helper";
59
59
 
60
60
  const { getWebpackConfig } = useRemoteConfig();
61
61
 
62
- const config = getWebpackConfig('my-remote', {
63
- './MyComponent': './src/MyComponent',
62
+ const config = getWebpackConfig("my-remote", {
63
+ "./MyComponent": "./src/MyComponent",
64
64
  });
65
65
  ```
66
66
 
@@ -69,38 +69,75 @@ const config = getWebpackConfig('my-remote', {
69
69
  ### Components
70
70
 
71
71
  #### `RemoteWrapper`
72
+
72
73
  Main wrapper component that provides authentication context.
73
74
 
74
75
  #### `Secured`
76
+
75
77
  Conditional rendering component based on permissions.
76
78
 
77
79
  ### Hooks
78
80
 
79
81
  #### `useRemoteAuth()`
82
+
80
83
  Access authentication state and methods.
81
84
 
85
+ **[📖 Ver documentación completa de useRemoteAuth](./docs/useRemoteAuth.md)**
86
+
87
+ ```tsx
88
+ const { state, hasPermission, setToken, clearAuth } = useRemoteAuth();
89
+
90
+ // Verificar autenticación
91
+ if (state.isAuthenticated) {
92
+ console.log("User:", state.user?.name);
93
+ console.log("Permissions:", state.permissions);
94
+ }
95
+
96
+ // Verificar permisos específicos
97
+ if (hasPermission("users:write")) {
98
+ // Usuario puede editar
99
+ }
100
+ ```
101
+
82
102
  #### `useRemoteConfig()`
103
+
83
104
  Get standard Module Federation configuration.
84
105
 
106
+ ### Components
107
+
108
+ #### `<Secured />`
109
+
110
+ Conditional rendering based on permissions.
111
+
112
+ **[📖 Ver documentación completa de Secured](./docs/Secured.md)**
113
+
114
+ ```tsx
115
+ <Secured permission="admin:all" fallback={<div>No autorizado</div>}>
116
+ <AdminPanel />
117
+ </Secured>
118
+ ```
119
+
85
120
  ### Utils
86
121
 
87
122
  #### Token utilities
123
+
88
124
  - `decodeToken(token)` - Decode JWT safely
89
125
  - `isTokenExpired(token)` - Check token expiration
90
126
  - `tokenStorage` - Browser storage utilities
91
127
 
92
128
  #### Module utilities
129
+
93
130
  - `loadRemoteModule()` - Dynamic remote loading
94
131
  - `isRemoteAvailable()` - Check remote availability
95
132
 
96
133
  ## Differences from bo-library
97
134
 
98
- | Feature | bo-library | bo-remotes-helper |
99
- |---------|------------|-------------------|
100
- | Authentication | Full JWT verification | Client-side decode only |
101
- | Node.js APIs | Required (crypto, etc.) | Browser-only |
102
- | Bundle size | Large | Lightweight |
103
- | Use case | Host applications | Remote modules |
135
+ | Feature | bo-library | bo-remotes-helper |
136
+ | -------------- | ----------------------- | ----------------------- |
137
+ | Authentication | Full JWT verification | Client-side decode only |
138
+ | Node.js APIs | Required (crypto, etc.) | Browser-only |
139
+ | Bundle size | Large | Lightweight |
140
+ | Use case | Host applications | Remote modules |
104
141
 
105
142
  ## Development
106
143
 
@@ -118,6 +155,18 @@ npm run dev
118
155
  npm run test
119
156
  ```
120
157
 
158
+ ## Documentation
159
+
160
+ 📚 **[Documentación Completa](./docs/README.md)** - Guías, tutoriales y referencia completa
161
+
162
+ ### Quick Links
163
+
164
+ - 🔐 **[Hook useRemoteAuth](./docs/useRemoteAuth.md)** - Autenticación y permisos
165
+ - 🛡️ **[Componente Secured](./docs/Secured.md)** - Renderizado condicional
166
+ - 🚀 **[Quick Start Guide](./docs/quick-start.md)** - Comenzar en 5 minutos
167
+ - 🧪 **[Testing Guide](./docs/guides/testing.md)** - Probar componentes
168
+ - ❓ **[FAQ](./docs/FAQ.md)** - Preguntas frecuentes
169
+
121
170
  ## Integration with bo-module-react-template
122
171
 
123
- This library is designed to be the standard authentication solution for all remote modules created from `bo-module-react-template`.
172
+ This library is designed to be the standard authentication solution for all remote modules created from `bo-module-react-template`.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! For license information please see index.js.LICENSE.txt */
2
- (()=>{var e={20:(e,r,t)=>{"use strict";var o=t(953),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,i=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};r.Fragment=s,r.jsx=function(e,r,t){var o,s={},c=null,d=null;for(o in void 0!==t&&(c=""+t),void 0!==r.key&&(c=""+r.key),void 0!==r.ref&&(d=r.ref),r)a.call(r,o)&&!l.hasOwnProperty(o)&&(s[o]=r[o]);if(e&&e.defaultProps)for(o in r=e.defaultProps)void 0===s[o]&&(s[o]=r[o]);return{$$typeof:n,type:e,key:c,ref:d,props:s,_owner:i.current}}},60:(e,r,t)=>{var o={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!t.o(o,e)){var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}return t(o[e])})}n.keys=()=>Object.keys(o),n.id=60,e.exports=n},226:(e,r,t)=>{"use strict";t.r(r),t.d(r,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>a,loadRemoteModule:()=>s});var o=t(953),n=t.n(o);const s=async(e,r)=>{try{return await t(60)(`${e}/${r}`)}catch(t){throw console.error(`Failed to load remote module ${e}/${r}:`,t),t}},a=async e=>{try{const r=window[e];return"function"==typeof r?.get}catch(e){return!1}},i=(e,r,t)=>n().lazy(async()=>{try{return await s(e,r)}catch(o){return console.error(`Remote component loading failed: ${e}/${r}`,o),t?{default:t}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${r}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,r,t)=>{"use strict";t.r(r),t.d(r,{decodeToken:()=>o,extractPermissions:()=>s,isTokenExpired:()=>n,tokenStorage:()=>a});const o=e=>{try{if(!e)return null;const r=e.split(".");if(3!==r.length)return null;const t=r[1],o=t+"=".repeat((4-t.length%4)%4),n=atob(o);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const r=o(e);if(!r||!r.exp)return!0;const t=Math.floor(Date.now()/1e3);return r.exp<t},s=e=>{if(!e)return[];const r=["permissions","roles","authorities","scope"];for(const t of r){const r=e[t];if(Array.isArray(r))return r;if("string"==typeof r)return r.split(" ").filter(Boolean)}return[]},a={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(a.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(a.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(a.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,r,t)=>{"use strict";e.exports=t(20)},953:e=>{"use strict";e.exports=require("react")}},r={};function t(o){var n=r[o];if(void 0!==n)return n.exports;var s=r[o]={exports:{}};return e[o](s,s.exports,t),s.exports}t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.e=()=>Promise.resolve(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var o={};(()=>{"use strict";t.r(o),t.d(o,{RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>p.createRemoteComponentLoader,decodeToken:()=>n.decodeToken,extractPermissions:()=>n.extractPermissions,getRemoteEnvironment:()=>p.getRemoteEnvironment,isRemoteAvailable:()=>p.isRemoteAvailable,isTokenExpired:()=>n.isTokenExpired,loadRemoteModule:()=>p.loadRemoteModule,tokenStorage:()=>n.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>m});var e=t(848),r=t(953),n=t(433);const s={token:null,isAuthenticated:!1,permissions:[],user:void 0},a=(e,r)=>{switch(r.type){case"SET_TOKEN":const t=(0,n.decodeToken)(r.payload),o=t?(0,n.extractPermissions)(t):[];return{...e,token:r.payload,isAuthenticated:!(0,n.isTokenExpired)(r.payload),permissions:o,user:t?{id:t.sub||"",email:t.email||"",name:t.name||t.email||""}:void 0};case"CLEAR_AUTH":return{...s};case"SET_USER_INFO":return{...e,user:{id:r.payload.sub||"",email:r.payload.email||"",name:r.payload.name||r.payload.email||""}};default:return e}},i=(0,r.createContext)(void 0),l=({children:t,initialToken:o})=>{const[l,c]=(0,r.useReducer)(a,s);(0,r.useEffect)(()=>{const e=n.tokenStorage.get(),r=o||e;r&&!(0,n.isTokenExpired)(r)?c({type:"SET_TOKEN",payload:r}):e&&n.tokenStorage.remove()},[o]);const d={state:l,setToken:e=>{e&&(n.tokenStorage.save(e),c({type:"SET_TOKEN",payload:e}))},clearAuth:()=>{n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})},hasPermission:e=>!!l.isAuthenticated&&l.permissions.includes(e)};return(0,e.jsx)(i.Provider,{value:d,children:t})},c=()=>{const e=(0,r.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:r,permission:t,fallback:o=null})=>{const{hasPermission:n,state:s}=c();return s.isAuthenticated&&n(t)?(0,e.jsx)(e.Fragment,{children:r}):(0,e.jsx)(e.Fragment,{children:o})},u=({children:r,token:t,apiBaseUrl:o,module:n})=>(0,e.jsx)(l,{initialToken:t,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":o,children:r})}),m=()=>{const e=(0,r.useMemo)(()=>({react:{singleton:!0,eager:!1},"react-dom":{singleton:!0,eager:!1},"aurora-web":{singleton:!0,eager:!1},"react-i18next":{singleton:!0,eager:!1},i18next:{singleton:!0,eager:!1}}),[]);return{sharedDependencies:e,getWebpackConfig:(r,t)=>({name:r,filename:"remoteEntry.js",exposes:t,shared:e})}};var p=t(226)})(),module.exports=o})();
2
+ (()=>{var e={20:(e,t,o)=>{"use strict";var r=o(953),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,i=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};t.Fragment=s,t.jsx=function(e,t,o){var r,s={},c=null,d=null;for(r in void 0!==o&&(c=""+o),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)a.call(t,r)&&!l.hasOwnProperty(r)&&(s[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===s[r]&&(s[r]=t[r]);return{$$typeof:n,type:e,key:c,ref:d,props:s,_owner:i.current}}},60:(e,t,o)=>{var r={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!o.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return o(r[e])})}n.keys=()=>Object.keys(r),n.id=60,e.exports=n},226:(e,t,o)=>{"use strict";o.r(t),o.d(t,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>a,loadRemoteModule:()=>s});var r=o(953),n=o.n(r);const s=async(e,t)=>{try{return await o(60)(`${e}/${t}`)}catch(o){throw console.error(`Failed to load remote module ${e}/${t}:`,o),o}},a=async e=>{try{const t=window[e];return"function"==typeof t?.get}catch(e){return!1}},i=(e,t,o)=>n().lazy(async()=>{try{return await s(e,t)}catch(r){return console.error(`Remote component loading failed: ${e}/${t}`,r),o?{default:o}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${t}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,t,o)=>{"use strict";o.r(t),o.d(t,{decodeToken:()=>r,extractPermissions:()=>s,isTokenExpired:()=>n,tokenStorage:()=>a});const r=e=>{try{if(!e)return null;const t=e.split(".");if(3!==t.length)return null;const o=t[1],r=o+"=".repeat((4-o.length%4)%4),n=atob(r);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const t=r(e);if(!t||!t.exp)return!0;const o=Math.floor(Date.now()/1e3);return t.exp<o},s=e=>{if(!e)return[];const t=["permissions","roles","authorities","scope"];for(const o of t){const t=e[o];if(Array.isArray(t))return t;if("string"==typeof t)return t.split(" ").filter(Boolean)}return[]},a={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(a.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(a.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(a.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,t,o)=>{"use strict";e.exports=o(20)},953:e=>{"use strict";e.exports=require("react")}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.e=()=>Promise.resolve(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";o.r(r),o.d(r,{RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>p.createRemoteComponentLoader,decodeToken:()=>n.decodeToken,extractPermissions:()=>n.extractPermissions,getRemoteEnvironment:()=>p.getRemoteEnvironment,isRemoteAvailable:()=>p.isRemoteAvailable,isTokenExpired:()=>n.isTokenExpired,loadRemoteModule:()=>p.loadRemoteModule,tokenStorage:()=>n.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>m});var e=o(848),t=o(953),n=o(433);const s={token:null,isAuthenticated:!1,permissions:[],user:void 0},a=(e,t)=>{switch(t.type){case"SET_TOKEN":const o=(0,n.decodeToken)(t.payload),r=o?(0,n.extractPermissions)(o):[];return{...e,token:t.payload,isAuthenticated:!(0,n.isTokenExpired)(t.payload),permissions:r,user:o?{id:o.sub||"",email:o.email||"",name:o.name||o.email||""}:void 0};case"CLEAR_AUTH":return{...s};case"SET_USER_INFO":return{...e,user:{id:t.payload.sub||"",email:t.payload.email||"",name:t.payload.name||t.payload.email||""}};default:return e}},i=(0,t.createContext)(void 0),l=({children:o,initialToken:r})=>{const[l,c]=(0,t.useReducer)(a,s);(0,t.useEffect)(()=>{if(console.log("🔍 RemoteAuthContext: useEffect triggered",{initialToken:r?r.substring(0,30)+"...":"No initialToken"}),r)console.log("✅ RemoteAuthContext: Using initialToken from prop"),(0,n.isTokenExpired)(r)?(console.log("🔴 RemoteAuthContext: Provided token is expired, clearing auth"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})):(console.log("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN"),c({type:"SET_TOKEN",payload:r}),n.tokenStorage.save(r));else{console.log("⚪ RemoteAuthContext: No initialToken, checking stored token");const e=n.tokenStorage.get();e&&!(0,n.isTokenExpired)(e)?(console.log("💾 RemoteAuthContext: Using stored token"),c({type:"SET_TOKEN",payload:e})):e&&(console.log("🗑️ RemoteAuthContext: Stored token expired, clearing"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"}))}},[r]);const d={state:l,setToken:e=>{e&&(n.tokenStorage.save(e),c({type:"SET_TOKEN",payload:e}))},clearAuth:()=>{n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})},hasPermission:e=>!!l.isAuthenticated&&l.permissions.includes(e)};return(0,e.jsx)(i.Provider,{value:d,children:o})},c=()=>{const e=(0,t.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:t,permission:o,fallback:r=null})=>{const{hasPermission:n,state:s}=c();return s.isAuthenticated&&n(o)?(0,e.jsx)(e.Fragment,{children:t}):(0,e.jsx)(e.Fragment,{children:r})},u=({children:t,token:o,apiBaseUrl:r,module:n})=>(0,e.jsx)(l,{initialToken:o,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":r,children:t})}),m=()=>{const e=(0,t.useMemo)(()=>({react:{singleton:!0,eager:!1},"react-dom":{singleton:!0,eager:!1},"aurora-web":{singleton:!0,eager:!1},"react-i18next":{singleton:!0,eager:!1},i18next:{singleton:!0,eager:!1}}),[]);return{sharedDependencies:e,getWebpackConfig:(t,o)=>({name:t,filename:"remoteEntry.js",exposes:o,shared:e})}};var p=o(226)})(),module.exports=r})();
@@ -0,0 +1,371 @@
1
+ # Guía de Contribución a bo-remotes-helper
2
+
3
+ ¡Gracias por tu interés en contribuir a `bo-remotes-helper`! Esta guía te ayudará a comenzar.
4
+
5
+ ## 📋 Tabla de Contenidos
6
+
7
+ - [Código de Conducta](#código-de-conducta)
8
+ - [Cómo Contribuir](#cómo-contribuir)
9
+ - [Configuración del Entorno](#configuración-del-entorno)
10
+ - [Estructura del Proyecto](#estructura-del-proyecto)
11
+ - [Convenciones de Código](#convenciones-de-código)
12
+ - [Testing](#testing)
13
+ - [Documentación](#documentación)
14
+ - [Pull Requests](#pull-requests)
15
+
16
+ ## Código de Conducta
17
+
18
+ Este proyecto se adhiere al código de conducta de Intive. Al participar, se espera que mantengas un ambiente profesional y respetuoso.
19
+
20
+ ## Cómo Contribuir
21
+
22
+ ### Reportar Bugs
23
+
24
+ 1. Verifica que el bug no esté ya reportado en [Issues](#)
25
+ 2. Abre un nuevo issue con:
26
+ - Descripción clara del problema
27
+ - Pasos para reproducir
28
+ - Comportamiento esperado vs actual
29
+ - Versión de la librería
30
+ - Navegador/entorno
31
+
32
+ Ejemplo:
33
+
34
+ ```markdown
35
+ **Descripción:**
36
+ useRemoteAuth lanza error cuando...
37
+
38
+ **Pasos para reproducir:**
39
+
40
+ 1. Crear componente con...
41
+ 2. Llamar a hasPermission()...
42
+ 3. Ver error...
43
+
44
+ **Esperado:** Debería retornar false
45
+ **Actual:** Lanza TypeError
46
+
47
+ **Entorno:**
48
+
49
+ - bo-remotes-helper: v1.0.0
50
+ - React: 18.2.0
51
+ - Browser: Chrome 120
52
+ ```
53
+
54
+ ### Proponer Mejoras
55
+
56
+ 1. Abre un issue con etiqueta `enhancement`
57
+ 2. Describe el caso de uso
58
+ 3. Propón una solución (opcional)
59
+ 4. Espera feedback del equipo
60
+
61
+ ### Contribuir Código
62
+
63
+ 1. Fork el repositorio
64
+ 2. Crea una rama desde `main`: `feature/mi-mejora` o `fix/mi-bug`
65
+ 3. Implementa tus cambios
66
+ 4. Escribe/actualiza tests
67
+ 5. Actualiza documentación
68
+ 6. Crea un Pull Request
69
+
70
+ ## Configuración del Entorno
71
+
72
+ ### Prerrequisitos
73
+
74
+ - Node.js >= 16
75
+ - npm >= 8
76
+
77
+ ### Setup
78
+
79
+ ```bash
80
+ # Clonar el repositorio
81
+ git clone https://github.com/intive/bo-remotes-helper.git
82
+ cd bo-remotes-helper
83
+
84
+ # Instalar dependencias
85
+ npm install
86
+
87
+ # Ejecutar tests
88
+ npm test
89
+
90
+ # Build
91
+ npm run build
92
+
93
+ # Watch mode para desarrollo
94
+ npm run dev
95
+ ```
96
+
97
+ ## Estructura del Proyecto
98
+
99
+ ```
100
+ bo-remotes-helper/
101
+ ├── src/
102
+ │ ├── components/ # Componentes React
103
+ │ │ ├── RemoteWrapper.tsx
104
+ │ │ └── Secured.tsx
105
+ │ ├── context/ # Context providers
106
+ │ │ └── RemoteAuthContext.tsx
107
+ │ ├── hooks/ # Custom hooks (si los hay)
108
+ │ │ └── useRemoteConfig.ts
109
+ │ ├── types/ # Definiciones TypeScript
110
+ │ │ └── index.ts
111
+ │ ├── utils/ # Utilidades
112
+ │ │ ├── tokenUtils.ts
113
+ │ │ └── moduleUtils.ts
114
+ │ └── index.ts # Punto de entrada
115
+ ├── docs/ # Documentación
116
+ ├── dist/ # Build output
117
+ ├── webpack.config.js
118
+ ├── tsconfig.json
119
+ └── package.json
120
+ ```
121
+
122
+ ## Convenciones de Código
123
+
124
+ ### TypeScript
125
+
126
+ - Usar tipos explícitos (evitar `any`)
127
+ - Exportar interfaces/tipos necesarios
128
+ - Documentar funciones públicas con JSDoc
129
+
130
+ ```tsx
131
+ /**
132
+ * Decodifica un token JWT sin verificar firma
133
+ * @param token - Token JWT a decodificar
134
+ * @returns Objeto con el payload del token o null si es inválido
135
+ */
136
+ export const decodeToken = (token: string): TokenInfo | null => {
137
+ // implementación
138
+ };
139
+ ```
140
+
141
+ ### React
142
+
143
+ - Componentes funcionales con TypeScript
144
+ - Props interface exportada
145
+ - Usar `React.FC` con props tipadas
146
+
147
+ ```tsx
148
+ interface MyComponentProps {
149
+ title: string;
150
+ onAction?: () => void;
151
+ }
152
+
153
+ export const MyComponent: React.FC<MyComponentProps> = ({
154
+ title,
155
+ onAction,
156
+ }) => {
157
+ // implementación
158
+ };
159
+ ```
160
+
161
+ ### Nombres
162
+
163
+ - **Componentes**: PascalCase (`RemoteWrapper`)
164
+ - **Hooks**: camelCase con prefijo `use` (`useRemoteAuth`)
165
+ - **Utilidades**: camelCase (`decodeToken`)
166
+ - **Tipos/Interfaces**: PascalCase (`RemoteAuthState`)
167
+ - **Constantes**: UPPER_SNAKE_CASE (`DEFAULT_TOKEN_KEY`)
168
+
169
+ ### Formato
170
+
171
+ Usamos Prettier para formateo automático:
172
+
173
+ ```bash
174
+ npm run format
175
+ ```
176
+
177
+ Configuración:
178
+
179
+ ```json
180
+ {
181
+ "semi": true,
182
+ "singleQuote": true,
183
+ "tabWidth": 2,
184
+ "trailingComma": "es5"
185
+ }
186
+ ```
187
+
188
+ ## Testing
189
+
190
+ ### Escribir Tests
191
+
192
+ - Todos los PRs deben incluir tests
193
+ - Objetivo: >80% de cobertura
194
+ - Usar Jest + React Testing Library
195
+
196
+ ```tsx
197
+ // src/components/__tests__/Secured.test.tsx
198
+ import { render, screen } from "@testing-library/react";
199
+ import { RemoteAuthProvider } from "../../context/RemoteAuthContext";
200
+ import { Secured } from "../Secured";
201
+
202
+ describe("Secured Component", () => {
203
+ it("renders children when user has permission", () => {
204
+ const mockToken = "valid-token";
205
+
206
+ render(
207
+ <RemoteAuthProvider initialToken={mockToken}>
208
+ <Secured permission="test:view">
209
+ <div>Protected Content</div>
210
+ </Secured>
211
+ </RemoteAuthProvider>,
212
+ );
213
+
214
+ expect(screen.getByText("Protected Content")).toBeInTheDocument();
215
+ });
216
+ });
217
+ ```
218
+
219
+ ### Ejecutar Tests
220
+
221
+ ```bash
222
+ # Todos los tests
223
+ npm test
224
+
225
+ # Watch mode
226
+ npm test -- --watch
227
+
228
+ # Cobertura
229
+ npm test -- --coverage
230
+ ```
231
+
232
+ ## Documentación
233
+
234
+ ### Actualizar Docs
235
+
236
+ Si tu cambio afecta la API pública:
237
+
238
+ 1. Actualiza `docs/[archivo-relevante].md`
239
+ 2. Actualiza ejemplos si es necesario
240
+ 3. Actualiza `README.md` si cambió algo fundamental
241
+ 4. Agrega entrada en `CHANGELOG.md`
242
+
243
+ ### Escribir Docs
244
+
245
+ - Usa Markdown
246
+ - Incluye ejemplos de código
247
+ - Usa emojis para claridad visual
248
+ - Agrega enlaces a secciones relacionadas
249
+
250
+ Ejemplo:
251
+
252
+ ```markdown
253
+ ## useRemoteAuth()
254
+
255
+ Hook que proporciona acceso al contexto de autenticación.
256
+
257
+ **Uso:**
258
+ \`\`\`tsx
259
+ const { state, hasPermission } = useRemoteAuth();
260
+ \`\`\`
261
+
262
+ **Ver también:** [Quick Start](./quick-start.md)
263
+ ```
264
+
265
+ ## Pull Requests
266
+
267
+ ### Checklist
268
+
269
+ Antes de crear un PR, verifica:
270
+
271
+ - [ ] Los tests pasan (`npm test`)
272
+ - [ ] El build funciona (`npm run build`)
273
+ - [ ] Código formateado (`npm run format`)
274
+ - [ ] Documentación actualizada
275
+ - [ ] CHANGELOG.md actualizado
276
+ - [ ] Commits descriptivos
277
+
278
+ ### Formato de Commits
279
+
280
+ Usamos Conventional Commits:
281
+
282
+ ```
283
+ tipo(scope): descripción corta
284
+
285
+ Descripción más detallada si es necesario
286
+
287
+ BREAKING CHANGE: Si hay cambios que rompen compatibilidad
288
+ ```
289
+
290
+ Tipos:
291
+
292
+ - `feat`: Nueva funcionalidad
293
+ - `fix`: Corrección de bug
294
+ - `docs`: Solo cambios en documentación
295
+ - `refactor`: Refactorización sin cambiar funcionalidad
296
+ - `test`: Agregar o modificar tests
297
+ - `chore`: Cambios en build, deps, etc.
298
+
299
+ Ejemplos:
300
+
301
+ ```
302
+ feat(auth): agregar soporte para refresh token
303
+ fix(types): corregir tipo de retorno de hasPermission
304
+ docs(quick-start): actualizar ejemplo de setup
305
+ ```
306
+
307
+ ### Template de PR
308
+
309
+ ```markdown
310
+ ## Descripción
311
+
312
+ Breve descripción del cambio
313
+
314
+ ## Tipo de Cambio
315
+
316
+ - [ ] Bug fix
317
+ - [ ] Nueva funcionalidad
318
+ - [ ] Breaking change
319
+ - [ ] Documentación
320
+
321
+ ## Testing
322
+
323
+ ¿Cómo se probó este cambio?
324
+
325
+ ## Checklist
326
+
327
+ - [ ] Tests pasan
328
+ - [ ] Documentación actualizada
329
+ - [ ] CHANGELOG actualizado
330
+ ```
331
+
332
+ ### Proceso de Review
333
+
334
+ 1. Crea el PR contra `main`
335
+ 2. Asigna reviewers del equipo BO
336
+ 3. Espera al menos 1 aprobación
337
+ 4. Resuelve comentarios
338
+ 5. Merge (squash commits)
339
+
340
+ ## Versionado
341
+
342
+ Seguimos Semantic Versioning:
343
+
344
+ - **MAJOR**: Cambios incompatibles (v1.0.0 → v2.0.0)
345
+ - **MINOR**: Nueva funcionalidad compatible (v1.0.0 → v1.1.0)
346
+ - **PATCH**: Bug fixes compatibles (v1.0.0 → v1.0.1)
347
+
348
+ ## Release Process
349
+
350
+ 1. Actualizar versión en `package.json`
351
+ 2. Actualizar `CHANGELOG.md`
352
+ 3. Crear tag: `git tag v1.0.0`
353
+ 4. Push tag: `git push origin v1.0.0`
354
+ 5. Publish: `npm publish`
355
+
356
+ ## Contacto
357
+
358
+ - **Slack**: #bo-team
359
+ - **Email**: bo-team@intive.com
360
+ - **Issues**: [GitHub Issues](#)
361
+
362
+ ## Recursos
363
+
364
+ - [React Docs](https://react.dev)
365
+ - [TypeScript Handbook](https://www.typescriptlang.org/docs/)
366
+ - [Testing Library](https://testing-library.com/react)
367
+ - [Module Federation](https://webpack.js.org/concepts/module-federation/)
368
+
369
+ ---
370
+
371
+ ¡Gracias por contribuir! 🎉