# 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 | - | - | [ ] |
