erp-core/docs/03-requerimientos/RF-users/RF-USER-005.md

371 lines
9.5 KiB
Markdown

# RF-USER-005: Preferencias de Usuario
## Identificacion
| Campo | Valor |
|-------|-------|
| **ID** | RF-USER-005 |
| **Modulo** | MGN-002 |
| **Nombre Modulo** | Users - Gestion de Usuarios |
| **Prioridad** | P2 |
| **Complejidad** | Baja |
| **Estado** | Aprobado |
| **Autor** | System |
| **Fecha** | 2025-12-05 |
---
## Descripcion
El sistema debe permitir a cada usuario configurar sus preferencias personales, incluyendo idioma, zona horaria, formato de fecha/hora, tema visual y preferencias de notificaciones. Estas configuraciones personalizan la experiencia del usuario sin afectar a otros usuarios del tenant.
### Contexto de Negocio
Las preferencias de usuario permiten:
- Experiencia personalizada por usuario
- Soporte multi-idioma
- Adaptacion a zonas horarias locales
- Control sobre notificaciones recibidas
- Accesibilidad (tema oscuro, tamano de fuente)
---
## Criterios de Aceptacion
- [x] **CA-001:** El usuario debe poder seleccionar idioma preferido
- [x] **CA-002:** El usuario debe poder configurar zona horaria
- [x] **CA-003:** El usuario debe poder elegir formato de fecha (DD/MM/YYYY, MM/DD/YYYY, etc.)
- [x] **CA-004:** El usuario debe poder elegir formato de hora (12h, 24h)
- [x] **CA-005:** El usuario debe poder elegir tema (claro, oscuro, sistema)
- [x] **CA-006:** El usuario debe poder configurar preferencias de notificaciones
- [x] **CA-007:** Las preferencias deben aplicarse inmediatamente
- [x] **CA-008:** Las preferencias deben persistir entre sesiones
### Ejemplos de Verificacion
```gherkin
Scenario: Cambiar idioma
Given un usuario con idioma "es" (español)
When cambia su preferencia a "en" (ingles)
Then el sistema guarda la preferencia
And la interfaz cambia a ingles inmediatamente
And las fechas se formatean en ingles
Scenario: Configurar zona horaria
Given un usuario en Mexico (America/Mexico_City)
When configura su zona horaria
Then todas las fechas/horas se muestran en esa zona
And los eventos del calendario se ajustan
Scenario: Preferencias de notificaciones
Given un usuario con notificaciones por email activadas
When desactiva "notificaciones de marketing"
Then deja de recibir ese tipo de emails
And mantiene otras notificaciones activas
Scenario: Tema oscuro
Given un usuario con tema claro
When activa tema oscuro
Then la interfaz cambia a colores oscuros
And la preferencia se mantiene al recargar
```
---
## Reglas de Negocio
| ID | Regla | Validacion |
|----|-------|------------|
| RN-001 | Idiomas soportados: es, en, pt | Enum validation |
| RN-002 | Zona horaria debe ser valida IANA | Timezone validation |
| RN-003 | Preferencias son por usuario, no por tenant | user_id scope |
| RN-004 | Valores por defecto del tenant si no hay preferencia | Fallback logic |
| RN-005 | Tema "sistema" sigue preferencia del OS | CSS media query |
### Preferencias Disponibles
```typescript
interface UserPreferences {
// Localizacion
language: 'es' | 'en' | 'pt';
timezone: string; // IANA timezone
dateFormat: 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY-MM-DD';
timeFormat: '12h' | '24h';
currency: string; // ISO 4217
numberFormat: 'es-MX' | 'en-US' | 'pt-BR';
// Apariencia
theme: 'light' | 'dark' | 'system';
sidebarCollapsed: boolean;
compactMode: boolean;
fontSize: 'small' | 'medium' | 'large';
// Notificaciones
notifications: {
email: {
enabled: boolean;
digest: 'instant' | 'daily' | 'weekly';
marketing: boolean;
security: boolean;
updates: boolean;
};
push: {
enabled: boolean;
sound: boolean;
};
inApp: {
enabled: boolean;
desktop: boolean;
};
};
// Dashboard
dashboard: {
defaultView: string;
widgets: string[];
};
}
```
---
## Impacto en Capas
### Database
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Tabla | crear | `user_preferences` |
| Columna | - | `id` UUID PK |
| Columna | - | `user_id` UUID FK UNIQUE |
| Columna | - | `tenant_id` UUID FK |
| Columna | - | `language` VARCHAR(5) |
| Columna | - | `timezone` VARCHAR(50) |
| Columna | - | `date_format` VARCHAR(20) |
| Columna | - | `time_format` VARCHAR(5) |
| Columna | - | `theme` VARCHAR(10) |
| Columna | - | `notifications` JSONB |
| Columna | - | `dashboard` JSONB |
| Columna | - | `metadata` JSONB |
| Columna | - | `updated_at` TIMESTAMPTZ |
### Backend
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Controller | crear | `PreferencesController` |
| Service | crear | `PreferencesService` |
| Method | crear | `getPreferences()` |
| Method | crear | `updatePreferences()` |
| Method | crear | `resetPreferences()` |
| DTO | crear | `UpdatePreferencesDto` |
| DTO | crear | `PreferencesResponseDto` |
| Entity | crear | `UserPreferences` |
| Endpoint | crear | `GET /api/v1/users/me/preferences` |
| Endpoint | crear | `PATCH /api/v1/users/me/preferences` |
| Endpoint | crear | `POST /api/v1/users/me/preferences/reset` |
### Frontend
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Pagina | crear | `PreferencesPage` |
| Componente | crear | `LanguageSelector` |
| Componente | crear | `TimezoneSelector` |
| Componente | crear | `ThemeToggle` |
| Componente | crear | `NotificationSettings` |
| Store | crear | `preferencesStore` |
| Hook | crear | `usePreferences` |
| Context | crear | `PreferencesContext` |
---
## Dependencias
### Depende de (Bloqueantes)
| ID | Requerimiento | Estado |
|----|---------------|--------|
| RF-USER-001 | CRUD Usuarios | Tabla users |
### Dependencias Relacionadas
| ID | Requerimiento | Relacion |
|----|---------------|----------|
| RF-SETTINGS-001 | Tenant Settings | Valores por defecto |
---
## Especificaciones Tecnicas
### Endpoint GET /api/v1/users/me/preferences
```typescript
// Response 200
{
"language": "es",
"timezone": "America/Mexico_City",
"dateFormat": "DD/MM/YYYY",
"timeFormat": "24h",
"currency": "MXN",
"numberFormat": "es-MX",
"theme": "dark",
"sidebarCollapsed": false,
"compactMode": false,
"fontSize": "medium",
"notifications": {
"email": {
"enabled": true,
"digest": "daily",
"marketing": false,
"security": true,
"updates": true
},
"push": {
"enabled": true,
"sound": true
},
"inApp": {
"enabled": true,
"desktop": false
}
},
"dashboard": {
"defaultView": "overview",
"widgets": ["sales", "inventory", "tasks"]
}
}
```
### Endpoint PATCH /api/v1/users/me/preferences
```typescript
// Request - Actualizacion parcial
{
"theme": "light",
"notifications": {
"email": {
"marketing": false
}
}
}
// Response 200
{
// PreferencesResponseDto completo actualizado
}
```
### Merge de Preferencias
```typescript
// preferences.service.ts
async updatePreferences(
userId: string,
updates: Partial<UserPreferences>,
): Promise<UserPreferences> {
let preferences = await this.preferencesRepository.findOne({
where: { userId },
});
if (!preferences) {
// Crear con defaults del tenant
const tenantDefaults = await this.getTenantDefaults(userId);
preferences = this.preferencesRepository.create({
userId,
...tenantDefaults,
});
}
// Deep merge para objetos anidados (notifications, dashboard)
const merged = deepMerge(preferences, updates);
return this.preferencesRepository.save(merged);
}
```
### Aplicacion en Frontend
```typescript
// PreferencesContext.tsx
export const PreferencesProvider: React.FC = ({ children }) => {
const [preferences, setPreferences] = useState<UserPreferences | null>(null);
useEffect(() => {
loadPreferences();
}, []);
useEffect(() => {
if (preferences) {
// Aplicar tema
document.documentElement.setAttribute('data-theme', preferences.theme);
// Aplicar idioma
i18n.changeLanguage(preferences.language);
// Configurar moment/dayjs timezone
dayjs.tz.setDefault(preferences.timezone);
}
}, [preferences]);
return (
<PreferencesContext.Provider value={{ preferences, updatePreferences }}>
{children}
</PreferencesContext.Provider>
);
};
```
---
## Datos de Prueba
| Escenario | Entrada | Resultado |
|-----------|---------|-----------|
| Obtener preferencias | GET /preferences | 200, preferencias o defaults |
| Cambiar idioma | language: "en" | 200, idioma actualizado |
| Zona horaria invalida | timezone: "Invalid/Zone" | 400, "Zona horaria invalida" |
| Tema valido | theme: "dark" | 200, tema actualizado |
| Tema invalido | theme: "purple" | 400, "Valor no permitido" |
| Desactivar notificaciones | notifications.email.enabled: false | 200, actualizado |
| Reset preferencias | POST /reset | 200, defaults del tenant |
---
## Estimacion
| Capa | Story Points | Notas |
|------|--------------|-------|
| Database | 1 | Tabla user_preferences |
| Backend | 2 | CRUD preferencias |
| Frontend | 4 | UI de configuracion + contexto |
| **Total** | **7** | |
---
## Notas Adicionales
- Las preferencias se cargan al inicio de sesion y se cachean
- Cambios de tema deben ser instantaneos (sin reload)
- Considerar preferencias sincronizadas entre dispositivos
- Exportar/importar preferencias para usuarios
- Preferencias de accesibilidad (alto contraste, reducir animaciones)
---
## Historial de Cambios
| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | System | Creacion inicial |
---
## Aprobaciones
| Rol | Nombre | Fecha | Firma |
|-----|--------|-------|-------|
| Analista | System | 2025-12-05 | [x] |
| Tech Lead | - | - | [ ] |
| Product Owner | - | - | [ ] |