--- id: "US-TRD-013" title: "Configurar Alertas de Precio" type: "User Story" status: "Done" priority: "Media" epic: "OQI-003" story_points: 5 created_date: "2025-12-05" updated_date: "2026-01-04" --- # US-TRD-013: Configurar Alertas de Precio ## Metadata | Campo | Valor | |-------|-------| | **ID** | US-TRD-013 | | **Épica** | OQI-003 - Trading y Charts | | **Módulo** | trading | | **Prioridad** | P2 | | **Story Points** | 3 | | **Sprint** | Sprint 6 | | **Estado** | Pendiente | | **Asignado a** | Por asignar | --- ## Historia de Usuario **Como** trader, **quiero** configurar alertas de precio para símbolos específicos, **para** recibir notificaciones cuando el precio alcance niveles importantes sin monitorear constantemente. ## Descripción Detallada El usuario debe poder crear alertas de precio para cualquier símbolo, especificando condiciones como "precio mayor que", "precio menor que", o "precio cruza". Cuando la condición se cumpla, el usuario recibe una notificación push y/o email. ## Mockups/Wireframes ``` ┌─────────────────────────────────────────────────────────────────┐ │ PRICE ALERTS │ ├─────────────────────────────────────────────────────────────────┤ │ [+ Create Alert] │ │ │ │ Active Alerts (3) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ BTCUSDT [...] │ │ │ │ When price goes ABOVE $100,000 │ │ │ │ Current: $97,234.50 | Distance: +2.84% │ │ │ │ Created: Dec 5, 2025 10:00 AM │ │ │ │ [🔔 Enabled] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ ETHUSDT [...] │ │ │ │ When price goes BELOW $3,700 │ │ │ │ Current: $3,845.20 | Distance: -3.92% │ │ │ │ Created: Dec 4, 2025 02:30 PM │ │ │ │ [🔕 Disabled] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ SOLUSDT [...] │ │ │ │ When price CROSSES $150 (from either direction) │ │ │ │ Current: $142.73 | Distance: -5.09% │ │ │ │ Created: Dec 3, 2025 09:15 AM │ │ │ │ [🔔 Enabled] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ Triggered Alerts (5) [View History] │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ CREATE PRICE ALERT │ ├─────────────────────────────────────┤ │ Symbol: │ │ ┌─────────────────────────────────┐ │ │ │ BTCUSDT [▼] │ │ │ └─────────────────────────────────┘ │ │ Current Price: $97,234.50 │ │ │ │ Condition: │ │ ┌─────────────────────────────────┐ │ │ │ Price goes ABOVE [▼] │ │ │ └─────────────────────────────────┘ │ │ Options: Above, Below, Crosses │ │ │ │ Target Price: │ │ ┌─────────────────────────────────┐ │ │ │ 100,000.00 │ │ │ └─────────────────────────────────┘ │ │ Distance: +2.84% │ │ [Set +1%] [Set +5%] [Set +10%] │ │ │ │ Notification Method: │ │ [✓] Push Notification │ │ [✓] Email │ │ [ ] SMS (Premium) │ │ │ │ Message (optional): │ │ ┌─────────────────────────────────┐ │ │ │ BTC hitting resistance │ │ │ └─────────────────────────────────┘ │ │ │ │ Expires After: │ │ ┌─────────────────────────────────┐ │ │ │ Never [▼] │ │ │ └─────────────────────────────────┘ │ │ │ │ [Cancel] [Create Alert] │ └─────────────────────────────────────┘ ``` --- ## Criterios de Aceptación **Escenario 1: Crear alerta "price above"** ```gherkin DADO que el usuario está en Price Alerts CUANDO hace click en "+ Create Alert" Y selecciona símbolo "BTCUSDT" Y selecciona condición "Price goes ABOVE" Y ingresa precio $100,000 Y habilita "Push Notification" y "Email" Y hace click en "Create Alert" ENTONCES se crea la alerta Y aparece en "Active Alerts" Y muestra distancia actual (+2.84%) Y el sistema comienza a monitorear ``` **Escenario 2: Activación de alerta "above"** ```gherkin DADO que existe alerta BTCUSDT ABOVE $100,000 Y el precio actual es $99,500 CUANDO el precio sube y alcanza $100,000 ENTONCES se dispara la alerta Y se envía push notification Y se envía email Y la alerta pasa a "Triggered Alerts" Y se deshabilita automáticamente ``` **Escenario 3: Crear alerta "price below"** ```gherkin DADO que el usuario crea alerta CUANDO selecciona "Price goes BELOW $3,700" para ETHUSDT Y el precio actual es $3,845 ENTONCES se crea alerta activa Y se activa solo cuando el precio baje a $3,700 o menos ``` **Escenario 4: Alerta "crosses" bidireccional** ```gherkin DADO que el usuario crea alerta "CROSSES $150" para SOLUSDT CUANDO el precio cruza $150 desde arriba (150.10 → 149.90) O desde abajo (149.90 → 150.10) ENTONCES se dispara la alerta Y notifica al usuario ``` **Escenario 5: Deshabilitar alerta temporalmente** ```gherkin DADO que el usuario tiene alerta activa CUANDO hace click en el toggle [🔔 Enabled] ENTONCES cambia a [🔕 Disabled] Y el sistema deja de monitorear esa alerta Y puede re-habilitarla más tarde ``` **Escenario 6: Alerta con expiración** ```gherkin DADO que el usuario crea alerta CUANDO selecciona "Expires After: 24 hours" Y la alerta no se dispara en 24 horas ENTONCES la alerta se elimina automáticamente Y se muestra en historial como "Expired" ``` **Escenario 7: Límite de alertas** ```gherkin DADO que el usuario tiene 10 alertas activas (límite) CUANDO intenta crear otra ENTONCES se muestra error "Maximum 10 active alerts" Y sugiere deshabilitar o eliminar alertas existentes ``` ## Criterios Adicionales - [ ] Click en alerta abre chart del símbolo - [ ] Sonido diferenciado para alertas - [ ] Historial de alertas disparadas - [ ] Plantillas de alertas (niveles psicológicos, ATH, etc.) - [ ] Re-activar alerta después de dispararse --- ## Tareas Técnicas **Database:** - [ ] DB-TRD-021: Crear tabla trading.price_alerts - Campos: id, user_id, symbol, condition, target_price, notification_methods, message, expires_at, status, triggered_at - [ ] DB-TRD-022: Crear índice en (user_id, status, symbol) **Backend:** - [ ] BE-TRD-070: Crear endpoint POST /trading/alerts - [ ] BE-TRD-071: Crear endpoint GET /trading/alerts - [ ] BE-TRD-072: Crear endpoint PATCH /trading/alerts/:id - [ ] BE-TRD-073: Crear endpoint DELETE /trading/alerts/:id - [ ] BE-TRD-074: Implementar AlertService.create() - [ ] BE-TRD-075: Implementar AlertMonitorService (background job) - [ ] BE-TRD-076: Implementar NotificationService (push, email) - [ ] BE-TRD-077: Implementar lógica de condiciones (above, below, crosses) **Frontend:** - [ ] FE-TRD-070: Crear componente PriceAlertsPanel.tsx - [ ] FE-TRD-071: Crear componente CreateAlertDialog.tsx - [ ] FE-TRD-072: Crear componente AlertCard.tsx - [ ] FE-TRD-073: Crear componente AlertHistory.tsx - [ ] FE-TRD-074: Implementar hook useAlerts - [ ] FE-TRD-075: Implementar notificaciones push (Web Push API) **Tests:** - [ ] TEST-TRD-034: Test unitario condiciones de alertas - [ ] TEST-TRD-035: Test integración crear/disparar alerta - [ ] TEST-TRD-036: Test E2E flujo completo alertas --- ## Dependencias **Depende de:** - [ ] US-TRD-001: Ver chart - Estado: Pendiente (necesita precios) **Bloquea:** - Ninguna --- ## Notas Técnicas **Endpoints involucrados:** | Método | Endpoint | Descripción | |--------|----------|-------------| | POST | /trading/alerts | Crear alerta | | GET | /trading/alerts | Listar alertas | | PATCH | /trading/alerts/:id | Actualizar/deshabilitar alerta | | DELETE | /trading/alerts/:id | Eliminar alerta | | GET | /trading/alerts/history | Historial de alertas disparadas | **Entidades/Tablas:** ```sql CREATE TABLE trading.price_alerts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, symbol VARCHAR(20) NOT NULL, condition VARCHAR(20) NOT NULL, -- 'above', 'below', 'crosses' target_price DECIMAL(20, 8) NOT NULL, notification_methods JSONB DEFAULT '{"push": true, "email": true}', message TEXT, expires_at TIMESTAMP, status VARCHAR(20) DEFAULT 'active', -- 'active', 'disabled', 'triggered', 'expired' triggered_at TIMESTAMP, triggered_price DECIMAL(20, 8), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_price_alerts_user_status ON trading.price_alerts(user_id, status, symbol); CREATE INDEX idx_price_alerts_active ON trading.price_alerts(status, symbol) WHERE status = 'active'; ``` **Componentes UI:** - `PriceAlertsPanel`: Panel principal - `CreateAlertDialog`: Modal de creación - `AlertCard`: Card de alerta individual - `AlertHistory`: Historial de disparadas - `ConditionSelector`: Selector de condición **Request Body (Create):** ```typescript { symbol: "BTCUSDT", condition: "above", targetPrice: 100000.00, notificationMethods: { push: true, email: true, sms: false }, message: "BTC hitting resistance", expiresAt: "2025-12-06T10:00:00Z" // null para never } ``` **Response:** ```typescript { alert: { id: "uuid", symbol: "BTCUSDT", condition: "above", targetPrice: 100000.00, currentPrice: 97234.50, distance: 2.84, distancePercentage: 2.84, notificationMethods: { push: true, email: true, sms: false }, message: "BTC hitting resistance", expiresAt: "2025-12-06T10:00:00Z", status: "active", createdAt: "2025-12-05T10:00:00Z" } } ``` **Alert Monitor Logic (Background Job - cada 5 segundos):** ```typescript const activeAlerts = await getActiveAlerts(); for (const alert of activeAlerts) { const currentPrice = await getCurrentPrice(alert.symbol); const previousPrice = await getPreviousPrice(alert.symbol); let shouldTrigger = false; switch (alert.condition) { case 'above': shouldTrigger = currentPrice >= alert.targetPrice; break; case 'below': shouldTrigger = currentPrice <= alert.targetPrice; break; case 'crosses': // Cruce desde arriba o desde abajo const crossedFromAbove = previousPrice > alert.targetPrice && currentPrice <= alert.targetPrice; const crossedFromBelow = previousPrice < alert.targetPrice && currentPrice >= alert.targetPrice; shouldTrigger = crossedFromAbove || crossedFromBelow; break; } if (shouldTrigger) { await triggerAlert(alert, currentPrice); } // Check expiration if (alert.expiresAt && new Date() > alert.expiresAt) { await expireAlert(alert.id); } } ``` **Trigger Alert Logic:** ```typescript async function triggerAlert(alert, price) { // Update alert status await updateAlert(alert.id, { status: 'triggered', triggeredAt: new Date(), triggeredPrice: price }); // Send notifications if (alert.notificationMethods.push) { await sendPushNotification(alert.userId, { title: `Price Alert: ${alert.symbol}`, body: `${alert.symbol} ${alert.condition} ${alert.targetPrice}. Current: ${price}`, data: { alertId: alert.id, symbol: alert.symbol } }); } if (alert.notificationMethods.email) { await sendEmail(alert.userId, { subject: `Price Alert: ${alert.symbol}`, body: renderAlertEmail(alert, price) }); } if (alert.notificationMethods.sms) { await sendSMS(alert.userId, `${alert.symbol} reached ${price}`); } } ``` **Condiciones disponibles:** - **above**: Se dispara cuando `currentPrice >= targetPrice` - **below**: Se dispara cuando `currentPrice <= targetPrice` - **crosses**: Se dispara cuando el precio cruza el nivel en cualquier dirección **Expiration options:** - Never - 1 hour - 24 hours - 7 days - Custom date/time **Notification Methods:** - Push Notification (Web Push API) - Email - SMS (Premium feature) --- ## 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