Changes include: - Updated architecture documentation - Enhanced module definitions (OQI-001 to OQI-008) - ML integration documentation updates - Trading strategies documentation - Orchestration and inventory updates - Docker configuration updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
434 lines
13 KiB
Markdown
434 lines
13 KiB
Markdown
---
|
|
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
|