--- 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(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 ( {children} ); } // 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