371 lines
8.2 KiB
Markdown
371 lines
8.2 KiB
Markdown
# Módulos Compartidos - AvanzaCast
|
|
|
|
## 📋 Descripción
|
|
|
|
Los módulos compartidos (`shared/`) contienen código reutilizable que se usa en todos los módulos de la aplicación (landing-page, broadcast-studio, admin-panel). Esto garantiza consistencia en autenticación, idioma y componentes UI.
|
|
|
|
## 📦 Estructura de Módulos Compartidos
|
|
|
|
```
|
|
shared/
|
|
├── types/ # TypeScript types compartidos
|
|
├── utils/ # Utilidades (auth, i18n, api client)
|
|
├── hooks/ # React hooks compartidos
|
|
└── components/ # Componentes React compartidos
|
|
```
|
|
|
|
## 🔑 Autenticación Compartida
|
|
|
|
### Hook `useAuth()`
|
|
|
|
Todos los módulos frontend usan el mismo hook de autenticación:
|
|
|
|
```typescript
|
|
import { useAuth } from '@avanzacast/shared-hooks';
|
|
|
|
function MyComponent() {
|
|
const { user, isAuthenticated, login, logout, register } = useAuth();
|
|
|
|
const handleLogin = async () => {
|
|
const result = await login({
|
|
email: 'user@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
if (result.success) {
|
|
console.log('Login exitoso');
|
|
} else {
|
|
console.error(result.error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{isAuthenticated ? (
|
|
<p>Bienvenido, {user?.name}</p>
|
|
) : (
|
|
<button onClick={handleLogin}>Iniciar sesión</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Funciones de utilidad
|
|
|
|
```typescript
|
|
import {
|
|
saveTokens,
|
|
getTokens,
|
|
clearAuth,
|
|
isAuthenticated
|
|
} from '@avanzacast/shared-utils';
|
|
|
|
// Guardar tokens después del login
|
|
saveTokens({ accessToken: '...', refreshToken: '...', expiresIn: 3600 });
|
|
|
|
// Verificar si está autenticado
|
|
if (isAuthenticated()) {
|
|
// Usuario logueado
|
|
}
|
|
|
|
// Cerrar sesión
|
|
clearAuth();
|
|
```
|
|
|
|
## 🌍 Internacionalización (i18n)
|
|
|
|
### Hook `useLanguage()`
|
|
|
|
```typescript
|
|
import { useLanguage } from '@avanzacast/shared-hooks';
|
|
|
|
function MyComponent() {
|
|
const { language, setLanguage, t, availableLanguages } = useLanguage();
|
|
|
|
return (
|
|
<div>
|
|
<h1>{t('landing.hero_title')}</h1>
|
|
<p>Idioma actual: {language}</p>
|
|
|
|
<select
|
|
value={language}
|
|
onChange={(e) => setLanguage(e.target.value as any)}
|
|
>
|
|
{availableLanguages.map(lang => (
|
|
<option key={lang.code} value={lang.code}>
|
|
{lang.flag} {lang.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Traducciones disponibles
|
|
|
|
Las traducciones están en `shared/utils/i18n.ts` y soportan:
|
|
|
|
- **Español (es)** 🇪🇸
|
|
- **English (en)** 🇺🇸
|
|
- **Português (pt)** 🇧🇷
|
|
- **Français (fr)** 🇫🇷
|
|
|
|
#### Claves de traducción:
|
|
|
|
```typescript
|
|
// Comunes
|
|
t('common.login') // "Iniciar sesión"
|
|
t('common.register') // "Registrarse"
|
|
t('common.email') // "Correo electrónico"
|
|
t('common.password') // "Contraseña"
|
|
|
|
// Navegación
|
|
t('nav.home') // "Inicio"
|
|
t('nav.features') // "Características"
|
|
t('nav.broadcasts') // "Transmisiones"
|
|
t('nav.studio') // "Estudio"
|
|
|
|
// Landing page
|
|
t('landing.hero_title') // "La manera más sencilla de transmitir..."
|
|
t('landing.get_started') // "Empecemos"
|
|
|
|
// Autenticación
|
|
t('auth.login_title') // "Inicia sesión en tu cuenta"
|
|
t('auth.register_title') // "Crea tu cuenta"
|
|
```
|
|
|
|
## 🧩 Componentes Compartidos
|
|
|
|
### LanguageSelector
|
|
|
|
Selector de idioma que se sincroniza entre todos los módulos:
|
|
|
|
```typescript
|
|
import { LanguageSelector } from '@avanzacast/shared-components';
|
|
|
|
function Header() {
|
|
return (
|
|
<header>
|
|
{/* Dropdown con banderas */}
|
|
<LanguageSelector variant="dropdown" position="right" />
|
|
|
|
{/* Solo banderas */}
|
|
<LanguageSelector variant="flags" />
|
|
</header>
|
|
);
|
|
}
|
|
```
|
|
|
|
### AuthButton
|
|
|
|
Botón de login/logout que se adapta al estado de autenticación:
|
|
|
|
```typescript
|
|
import { AuthButton } from '@avanzacast/shared-components';
|
|
|
|
function Header() {
|
|
return (
|
|
<header>
|
|
<AuthButton
|
|
variant="primary"
|
|
size="md"
|
|
loginUrl="/auth/login"
|
|
dashboardUrl="/broadcasts"
|
|
/>
|
|
</header>
|
|
);
|
|
}
|
|
```
|
|
|
|
## 🔌 API Client
|
|
|
|
Cliente HTTP centralizado para hacer peticiones al backend:
|
|
|
|
```typescript
|
|
import { apiClient } from '@avanzacast/shared-utils';
|
|
|
|
// Login
|
|
const response = await apiClient.login({
|
|
email: 'user@example.com',
|
|
password: 'password123'
|
|
});
|
|
|
|
// Register
|
|
const response = await apiClient.register({
|
|
email: 'new@example.com',
|
|
password: 'password123',
|
|
name: 'John Doe'
|
|
});
|
|
|
|
// Get profile
|
|
const response = await apiClient.getProfile();
|
|
|
|
// Custom endpoint
|
|
const response = await apiClient.get('/broadcasts');
|
|
const response = await apiClient.post('/broadcasts', { title: 'Mi transmisión' });
|
|
```
|
|
|
|
## 📘 TypeScript Types
|
|
|
|
Todos los tipos están centralizados en `shared/types/`:
|
|
|
|
```typescript
|
|
import type {
|
|
User,
|
|
AuthTokens,
|
|
LoginCredentials,
|
|
RegisterData,
|
|
SupportedLanguage,
|
|
Broadcast,
|
|
Guest
|
|
} from '@avanzacast/shared-types';
|
|
|
|
const user: User = {
|
|
id: '123',
|
|
email: 'user@example.com',
|
|
name: 'John Doe',
|
|
role: 'user',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
};
|
|
```
|
|
|
|
## 🚀 Uso en los Módulos
|
|
|
|
### Landing Page
|
|
|
|
```typescript
|
|
// packages/landing-page/src/App.tsx
|
|
import { useAuth, useLanguage } from '@avanzacast/shared-hooks';
|
|
import { LanguageSelector, AuthButton } from '@avanzacast/shared-components';
|
|
|
|
export function App() {
|
|
const { t } = useLanguage();
|
|
const { isAuthenticated } = useAuth();
|
|
|
|
return (
|
|
<div>
|
|
<header>
|
|
<LanguageSelector />
|
|
<AuthButton />
|
|
</header>
|
|
<h1>{t('landing.hero_title')}</h1>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Broadcast Studio
|
|
|
|
```typescript
|
|
// packages/broadcast-studio/src/App.tsx
|
|
import { useAuth } from '@avanzacast/shared-hooks';
|
|
import { useEffect } from 'react';
|
|
|
|
export function App() {
|
|
const { isAuthenticated, user } = useAuth();
|
|
|
|
useEffect(() => {
|
|
if (!isAuthenticated) {
|
|
window.location.href = '/auth/login?redirect=/studio';
|
|
}
|
|
}, [isAuthenticated]);
|
|
|
|
return (
|
|
<div>
|
|
<h1>Estudio de {user?.name}</h1>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Admin Panel
|
|
|
|
```typescript
|
|
// packages/admin-panel/src/App.tsx
|
|
import { useAuth, useLanguage } from '@avanzacast/shared-hooks';
|
|
|
|
export function App() {
|
|
const { user } = useAuth();
|
|
const { language, setLanguage } = useLanguage();
|
|
|
|
return (
|
|
<div>
|
|
<h1>Admin Panel - {user?.name}</h1>
|
|
<p>Idioma: {language}</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## 🔄 Sincronización entre módulos
|
|
|
|
Cuando un usuario:
|
|
|
|
1. **Inicia sesión en landing-page** → Los datos se guardan en localStorage
|
|
2. **Navega a broadcast-studio** → El hook `useAuth()` lee el mismo localStorage
|
|
3. **Cambia el idioma en studio** → El cambio se refleja en todos los módulos
|
|
4. **Cierra sesión en admin-panel** → Todos los módulos detectan el logout
|
|
|
|
## ⚙️ Configuración de Variables de Entorno
|
|
|
|
Cada módulo frontend necesita configurar la URL del backend:
|
|
|
|
```bash
|
|
# packages/landing-page/.env
|
|
VITE_API_URL=http://localhost:4000/api/v1
|
|
|
|
# packages/broadcast-studio/.env
|
|
VITE_API_URL=http://localhost:4000/api/v1
|
|
|
|
# packages/admin-panel/.env
|
|
VITE_API_URL=http://localhost:4000/api/v1
|
|
```
|
|
|
|
## 📝 Añadir nuevas traducciones
|
|
|
|
Edita `shared/utils/i18n.ts`:
|
|
|
|
```typescript
|
|
const translations: Record<SupportedLanguage, Translation> = {
|
|
es: {
|
|
myNewSection: {
|
|
title: 'Mi nuevo título',
|
|
description: 'Mi nueva descripción'
|
|
}
|
|
},
|
|
en: {
|
|
myNewSection: {
|
|
title: 'My new title',
|
|
description: 'My new description'
|
|
}
|
|
},
|
|
// ... pt, fr
|
|
};
|
|
```
|
|
|
|
Luego úsalo:
|
|
|
|
```typescript
|
|
const { t } = useLanguage();
|
|
console.log(t('myNewSection.title'));
|
|
```
|
|
|
|
## 🎯 Beneficios de los Módulos Compartidos
|
|
|
|
✅ **Consistencia**: Mismo comportamiento de auth e i18n en todos los módulos
|
|
✅ **Mantenibilidad**: Cambiar una vez, actualiza en todos lados
|
|
✅ **Reutilización**: No duplicar código entre módulos
|
|
✅ **Type Safety**: TypeScript types compartidos evitan inconsistencias
|
|
✅ **Sincronización**: Cambios en un módulo se reflejan en otros automáticamente
|
|
|
|
## 🔧 Próximos Pasos
|
|
|
|
1. Implementar backend API endpoints (`/auth/login`, `/auth/register`)
|
|
2. Agregar refresh token automático cuando el access token expire
|
|
3. Implementar protección de rutas en cada módulo
|
|
4. Añadir más traducciones según se necesite
|
|
5. Crear más componentes compartidos (Modals, Alerts, Forms, etc.)
|