---
id: "US-TRD-016"
title: "Cambiar Tema del Chart"
type: "User Story"
status: "Done"
priority: "Baja"
epic: "OQI-003"
story_points: 2
created_date: "2025-12-05"
updated_date: "2026-01-04"
---

# US-TRD-016: Cambiar Tema del Chart (Claro/Oscuro)

## Metadata

| Campo | Valor |
|-------|-------|
| **ID** | US-TRD-016 |
| **Épica** | OQI-003 - Trading y Charts |
| **Módulo** | trading |
| **Prioridad** | P2 |
| **Story Points** | 2 |
| **Sprint** | Sprint 6 |
| **Estado** | Pendiente |
| **Asignado a** | Por asignar |

---

## Historia de Usuario

**Como** usuario de la plataforma,
**quiero** cambiar entre tema claro y oscuro para el chart de trading,
**para** adaptar la visualización a mis preferencias y reducir fatiga visual en sesiones largas.

## Descripción Detallada

El usuario debe poder alternar entre un tema claro y un tema oscuro para el chart de trading. El tema oscuro es especialmente útil para trading nocturno o sesiones prolongadas, reduciendo la fatiga visual. La preferencia debe persistir entre sesiones.

## Mockups/Wireframes

```
TEMA CLARO:
┌─────────────────────────────────────────────────────────────────┐
│  BTCUSDT   $97,234.50   +2.34% ▲                     [☀ Light] │
├─────────────────────────────────────────────────────────────────┤
│  Background: #FFFFFF                                            │
│  Grid: #E0E0E0                                                  │
│  Text: #000000                                                  │
│  Candles Up: #26A69A (Green)                                    │
│  Candles Down: #EF5350 (Red)                                    │
│  Volume: #757575 (Gray)                                         │
│  Crosshair: #000000                                             │
│                                                                  │
│    ████ (Green candles on white background)                     │
│    ████  ████                                                   │
│          ████  ════ (SMA - Blue line on white)                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

TEMA OSCURO:
┌─────────────────────────────────────────────────────────────────┐
│  BTCUSDT   $97,234.50   +2.34% ▲                     [🌙 Dark] │
├─────────────────────────────────────────────────────────────────┤
│  Background: #1E222D                                            │
│  Grid: #363C4E                                                  │
│  Text: #D1D4DC                                                  │
│  Candles Up: #26A69A (Green)                                    │
│  Candles Down: #EF5350 (Red)                                    │
│  Volume: #757575 (Gray)                                         │
│  Crosshair: #FFFFFF                                             │
│                                                                  │
│    ████ (Green candles on dark background)                      │
│    ████  ████                                                   │
│          ████  ════ (SMA - Blue line on dark)                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

CONTROL DE TEMA:
┌─────────────────────────┐
│ [☀ Light] [🌙 Dark]    │
│   └─Active (Blue)       │
└─────────────────────────┘
```

---

## Criterios de Aceptación

**Escenario 1: Cambiar a tema oscuro**
```gherkin
DADO que el usuario está viendo el chart en tema claro
CUANDO hace click en el botón "Dark"
ENTONCES el chart cambia a tema oscuro inmediatamente
Y el fondo cambia a #1E222D
Y el texto cambia a color claro (#D1D4DC)
Y la grid cambia a #363C4E
Y las velas mantienen sus colores (verde/rojo)
Y el botón "Dark" se marca como activo
```

**Escenario 2: Cambiar a tema claro**
```gherkin
DADO que el usuario está viendo el chart en tema oscuro
CUANDO hace click en el botón "Light"
ENTONCES el chart cambia a tema claro
Y el fondo cambia a #FFFFFF
Y el texto cambia a color oscuro (#000000)
Y el botón "Light" se marca como activo
```

**Escenario 3: Persistencia de preferencia**
```gherkin
DADO que el usuario selecciona tema oscuro
CUANDO cierra sesión y vuelve a iniciar sesión
ENTONCES el chart se carga automáticamente en tema oscuro
Y la preferencia se mantiene
```

**Escenario 4: Sincronización con tema del sistema**
```gherkin
DADO que el usuario tiene preferencia "Auto"
CUANDO el sistema operativo está en modo oscuro
ENTONCES el chart usa tema oscuro automáticamente

CUANDO el sistema cambia a modo claro
ENTONCES el chart cambia a tema claro
```

**Escenario 5: Indicadores en ambos temas**
```gherkin
DADO que el usuario tiene indicadores SMA y RSI activos
CUANDO cambia entre temas
ENTONCES los indicadores mantienen sus colores distintivos
Y son visibles en ambos temas
Y los colores contrastan correctamente con el fondo
```

**Escenario 6: Keyboard shortcut**
```gherkin
DADO que el usuario está en el chart
CUANDO presiona "Ctrl + D" (o "Cmd + D" en Mac)
ENTONCES el tema alterna entre claro y oscuro
```

## Criterios Adicionales

- [ ] Transición suave entre temas (200ms)
- [ ] Aplicar tema también a paneles laterales
- [ ] Opción "Auto" que sigue el tema del sistema
- [ ] Previsualización de temas antes de aplicar
- [ ] Temas personalizados (futuro)

---

## Tareas Técnicas

**Database:**
- [ ] DB-TRD-025: Añadir campo theme a user_preferences
  - Valores: 'light', 'dark', 'auto'

**Backend:**
- [ ] BE-TRD-092: Crear endpoint PATCH /users/preferences/theme
- [ ] BE-TRD-093: Implementar UserPreferencesService.updateTheme()

**Frontend:**
- [ ] FE-TRD-086: Crear componente ThemeToggle.tsx
- [ ] FE-TRD-087: Definir paletas de colores para cada tema
- [ ] FE-TRD-088: Implementar hook useTheme
- [ ] FE-TRD-089: Aplicar tema a Lightweight Charts
- [ ] FE-TRD-090: Implementar detección de tema del sistema
- [ ] FE-TRD-091: Añadir transiciones CSS
- [ ] FE-TRD-092: Implementar keyboard shortcut

**Tests:**
- [ ] TEST-TRD-043: Test unitario cambio de tema
- [ ] TEST-TRD-044: Test integración persistencia de tema
- [ ] TEST-TRD-045: Test E2E alternancia de temas

---

## Dependencias

**Depende de:**
- [ ] US-TRD-001: Ver chart - Estado: Pendiente

**Bloquea:**
- Ninguna

---

## Notas Técnicas

**Endpoints involucrados:**
| Método | Endpoint | Descripción |
|--------|----------|-------------|
| PATCH | /users/preferences/theme | Actualizar preferencia de tema |
| GET | /users/preferences | Obtener preferencias (incluye tema) |

**Entidades/Tablas:**
```sql
ALTER TABLE auth.user_preferences
ADD COLUMN theme VARCHAR(10) DEFAULT 'light';
-- Valores: 'light', 'dark', 'auto'
```

**Componentes UI:**
- `ThemeToggle`: Botones de alternancia de tema
- `ThemeProvider`: Context provider de tema

**Theme Palettes:**
```typescript
const LIGHT_THEME = {
  chart: {
    background: '#FFFFFF',
    textColor: '#000000',
    gridColor: '#E0E0E0',
    crosshairColor: '#000000',
  },
  candles: {
    upColor: '#26A69A',      // Verde
    downColor: '#EF5350',    // Rojo
    borderUpColor: '#26A69A',
    borderDownColor: '#EF5350',
    wickUpColor: '#26A69A',
    wickDownColor: '#EF5350',
  },
  volume: {
    upColor: 'rgba(38, 166, 154, 0.5)',
    downColor: 'rgba(239, 83, 80, 0.5)',
  },
  indicators: {
    sma: '#2962FF',          // Azul
    ema: '#FF6D00',          // Naranja
    rsi: '#9C27B0',          // Púrpura
    macd: '#00C853',         // Verde oscuro
  },
  ui: {
    background: '#FAFAFA',
    cardBackground: '#FFFFFF',
    border: '#E0E0E0',
    text: '#000000',
    textSecondary: '#757575',
  }
};

const DARK_THEME = {
  chart: {
    background: '#1E222D',
    textColor: '#D1D4DC',
    gridColor: '#363C4E',
    crosshairColor: '#FFFFFF',
  },
  candles: {
    upColor: '#26A69A',
    downColor: '#EF5350',
    borderUpColor: '#26A69A',
    borderDownColor: '#EF5350',
    wickUpColor: '#26A69A',
    wickDownColor: '#EF5350',
  },
  volume: {
    upColor: 'rgba(38, 166, 154, 0.5)',
    downColor: 'rgba(239, 83, 80, 0.5)',
  },
  indicators: {
    sma: '#5E81F4',
    ema: '#FF9800',
    rsi: '#BA68C8',
    macd: '#66BB6A',
  },
  ui: {
    background: '#131722',
    cardBackground: '#1E222D',
    border: '#363C4E',
    text: '#D1D4DC',
    textSecondary: '#898E9C',
  }
};
```

**Request Body:**
```typescript
{
  theme: "dark"  // "light", "dark", "auto"
}
```

**Response:**
```typescript
{
  theme: "dark",
  updatedAt: "2025-12-05T10:00:00Z"
}
```

**Frontend Implementation:**
```typescript
// Theme Context
const ThemeContext = createContext<ThemeContextType>(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState<'light' | 'dark' | 'auto'>('light');
  const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');

  // Load theme from user preferences
  useEffect(() => {
    loadUserTheme().then(setTheme);
  }, []);

  // Listen to system theme changes
  useEffect(() => {
    if (theme === 'auto') {
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      const updateResolvedTheme = () => {
        setResolvedTheme(mediaQuery.matches ? 'dark' : 'light');
      };

      updateResolvedTheme();
      mediaQuery.addEventListener('change', updateResolvedTheme);
      return () => mediaQuery.removeEventListener('change', updateResolvedTheme);
    } else {
      setResolvedTheme(theme as 'light' | 'dark');
    }
  }, [theme]);

  const toggleTheme = async () => {
    const newTheme = resolvedTheme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    await saveUserTheme(newTheme);
  };

  return (
    <ThemeContext.Provider value={{ theme, resolvedTheme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Apply theme to chart
function applyChartTheme(chart, theme) {
  const palette = theme === 'dark' ? DARK_THEME : LIGHT_THEME;

  chart.applyOptions({
    layout: {
      background: { color: palette.chart.background },
      textColor: palette.chart.textColor,
    },
    grid: {
      vertLines: { color: palette.chart.gridColor },
      horzLines: { color: palette.chart.gridColor },
    },
    crosshair: {
      vertLine: { color: palette.chart.crosshairColor },
      horzLine: { color: palette.chart.crosshairColor },
    },
  });

  candleSeries.applyOptions({
    upColor: palette.candles.upColor,
    downColor: palette.candles.downColor,
    borderUpColor: palette.candles.borderUpColor,
    borderDownColor: palette.candles.borderDownColor,
    wickUpColor: palette.candles.wickUpColor,
    wickDownColor: palette.candles.wickDownColor,
  });
}

// Keyboard shortcut
useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if ((e.ctrlKey || e.metaKey) && e.key === 'd') {
      e.preventDefault();
      toggleTheme();
    }
  };

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [toggleTheme]);
```

**CSS Transitions:**
```css
.chart-container {
  transition: background-color 200ms ease-in-out;
}

.theme-toggle {
  transition: all 200ms ease-in-out;
}

.theme-toggle.active {
  background-color: #2962FF;
  color: white;
}
```

---

## Definition of Ready (DoR)

- [x] Historia claramente escrita
- [x] Criterios de aceptación definidos
- [x] Story points estimados
- [x] Dependencias identificadas
- [x] Sin bloqueadores
- [ ] Diseño/mockup disponible
- [ ] API spec disponible

## Definition of Done (DoD)

- [ ] Código implementado según criterios
- [ ] Tests unitarios escritos y pasando
- [ ] Tests de integración pasando
- [ ] Code review aprobado
- [ ] Documentación actualizada
- [ ] QA aprobado
- [ ] Desplegado en ambiente de pruebas

---

## Historial de Cambios

| Fecha | Cambio | Autor |
|-------|--------|-------|
| 2025-12-05 | Creación | Requirements-Analyst |

---

**Creada por:** Requirements-Analyst
**Fecha:** 2025-12-05
**Última actualización:** 2025-12-05
