--- id: "US-INV-006" title: "Solicitar Retiro" type: "User Story" status: "Done" priority: "Media" epic: "OQI-004" project: "trading-platform" story_points: 3 created_date: "2025-12-05" updated_date: "2026-01-04" --- # US-INV-006: Solicitar Retiro ## Metadata | Campo | Valor | |-------|-------| | **ID** | US-INV-006 | | **Épica** | OQI-004 - Cuentas de Inversión | | **Módulo** | investment | | **Prioridad** | P0 | | **Story Points** | 5 | | **Sprint** | Sprint 5 | | **Estado** | Pendiente | | **Asignado a** | Por asignar | --- ## Historia de Usuario **Como** inversor, **quiero** solicitar retiros de mi cuenta de inversión, **para** retirar ganancias o capital cuando lo necesite. ## Descripción Detallada El usuario debe poder solicitar retiro de fondos desde su cuenta de inversión. El sistema debe validar balance disponible, aplicar período de espera de 72 horas, cerrar posiciones abiertas del agente si es necesario, y procesar el retiro vía Stripe. El usuario debe poder ver el estado de sus retiros pendientes. ## Mockups/Wireframes ``` ┌─────────────────────────────────────────────────────────────────┐ │ SOLICITAR RETIRO │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Balance disponible: $1,245.50 │ │ En posiciones abiertas: $180.70 │ │ │ │ ⚠️ Los retiros tienen un período de procesamiento de 72 horas │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Monto a retirar │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ │ │ $ │ │ │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ [ ] 25% [ ] 50% [ ] 75% [ ] 100% (todo) │ │ │ │ │ │ │ │ Mínimo: $50 USD │ │ │ │ Máximo disponible: $1,064.80 (balance - posiciones) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Método de pago para recibir │ │ │ │ │ │ │ │ (*) Tarjeta terminada en ****4242 │ │ │ │ ( ) Cuenta bancaria ****5678 │ │ │ │ │ │ │ │ [+ Agregar nuevo método] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 📋 Resumen │ │ │ │ │ │ │ │ Monto solicitado: $500.00 │ │ │ │ Comisión (2%): $10.00 │ │ │ │ ───────────────────────────── │ │ │ │ Recibirás: $490.00 │ │ │ │ │ │ │ │ Nuevo balance: $745.50 │ │ │ │ Fecha estimada: 2025-12-08 (72h) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ⚠️ Si tienes posiciones abiertas, el agente las cerrará antes │ │ de procesar el retiro para liberar fondos. │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ SOLICITAR RETIRO │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Criterios de Aceptación **Escenario 1: Solicitar retiro exitoso** ```gherkin DADO que el usuario tiene balance de $1,245.50 Y no tiene posiciones abiertas CUANDO solicita retiro de $500 Y selecciona método de pago Y hace click en "Solicitar retiro" ENTONCES se crea solicitud de retiro con status "pending" Y se reserva $500 del balance Y se muestra confirmación "Retiro solicitado, procesará en 72h" Y se envía email de confirmación Y se muestra en lista de retiros pendientes ``` **Escenario 2: Retiro con posiciones abiertas** ```gherkin DADO que el usuario tiene balance $1,245.50 Y tiene $180.70 en posiciones abiertas CUANDO solicita retiro de $1,000 ENTONCES se muestra advertencia "Se cerrarán posiciones abiertas" Y el usuario confirma Y el agente cierra todas las posiciones Y se procesa el retiro con el balance final ``` **Escenario 3: Monto mayor al disponible** ```gherkin DADO que el usuario tiene balance disponible de $1,064.80 CUANDO intenta retirar $1,500 ENTONCES se muestra error "Fondos insuficientes" Y se muestra "Máximo disponible: $1,064.80" Y el botón de confirmar está deshabilitado ``` **Escenario 4: Monto menor al mínimo** ```gherkin DADO que el usuario está solicitando retiro CUANDO ingresa monto de $25 Y el mínimo es $50 ENTONCES se muestra error "El monto mínimo es $50 USD" Y el botón está deshabilitado ``` **Escenario 5: Procesar retiro después de 72h** ```gherkin DADO que existe retiro con status "pending" Y han pasado 72 horas desde la solicitud CUANDO el cron job ejecuta processWithdrawals() ENTONCES se procesa el pago con Stripe Y se actualiza status a "completed" Y se actualiza balance de cuenta Y se envía email "Retiro completado" ``` **Escenario 6: Ver retiros pendientes** ```gherkin DADO que el usuario tiene 2 retiros pendientes CUANDO navega a sección de retiros ENTONCES se muestra lista de retiros Y cada retiro muestra: monto, fecha solicitud, tiempo restante, status Y puede cancelar retiros que aún están en período de espera ``` ## Criterios Adicionales - [ ] Permitir cancelar retiro dentro de las primeras 24h - [ ] Mostrar countdown de tiempo restante para retiros pendientes - [ ] Límite de 1 retiro activo por vez - [ ] Validar que cuenta no esté en status "closed" - [ ] Logging detallado para auditoría --- ## Tareas Técnicas **Database:** - [ ] DB-INV-001: Schema investment.withdrawals - [ ] DB-INV-002: Enum withdrawal_status (pending, processing, completed, failed, cancelled) - [ ] DB-INV-003: Índice en (account_id, status) **Backend:** - [ ] BE-INV-001: Endpoint POST /investment/accounts/:id/withdraw - [ ] BE-INV-002: Implementar WithdrawalService.requestWithdrawal() - [ ] BE-INV-003: Validar balance disponible - [ ] BE-INV-004: Integración con agente ML para cerrar posiciones - [ ] BE-INV-005: Implementar WithdrawalService.processWithdrawal() - [ ] BE-INV-006: Integración Stripe Transfers/Payouts - [ ] BE-INV-007: Cron job para procesar retiros después de 72h - [ ] BE-INV-008: Endpoint GET /investment/accounts/:id/withdrawals - [ ] BE-INV-009: Endpoint DELETE /investment/withdrawals/:id (cancelar) **Frontend:** - [ ] FE-INV-001: Crear página WithdrawPage.tsx - [ ] FE-INV-002: Crear componente WithdrawForm.tsx - [ ] FE-INV-003: Crear componente WithdrawSummary.tsx - [ ] FE-INV-004: Crear componente PendingWithdrawals.tsx - [ ] FE-INV-005: Crear componente CountdownTimer.tsx - [ ] FE-INV-006: Implementar withdrawalStore **Tests:** - [ ] TEST-INV-001: Test unitario WithdrawalService - [ ] TEST-INV-002: Test validaciones de balance - [ ] TEST-INV-003: Test integración Stripe - [ ] TEST-INV-004: Test cron job procesamiento - [ ] TEST-INV-005: Test E2E flujo completo retiro --- ## Dependencias **Depende de:** - [ ] US-INV-003: Realizar depósito - Estado: Pendiente - [ ] OQI-005: Integración Stripe - Estado: Pendiente - [ ] OQI-006: ML Agents (cerrar posiciones) - Estado: Pendiente **Bloquea:** - [ ] US-INV-007: Ver historial transacciones - [ ] US-INV-009: Cerrar cuenta --- ## Notas Técnicas **Endpoints involucrados:** | Método | Endpoint | Descripción | |--------|----------|-------------| | POST | /investment/accounts/:id/withdraw | Solicitar retiro | | GET | /investment/accounts/:id/withdrawals | Lista de retiros | | DELETE | /investment/withdrawals/:id | Cancelar retiro | **Entidades/Tablas:** - `investment.withdrawals`: Solicitudes de retiro - `investment.accounts`: Actualizar balance - `investment.transactions`: Registrar transacción **Request Body POST /withdraw:** ```typescript { amount: 500, paymentMethodId: "pm_xxx", closePositions: true, // si tiene posiciones abiertas reason: "profit_withdrawal" // opcional } ``` **Response:** ```typescript { withdrawal: { id: "uuid", accountId: "uuid", amount: 500, fee: 10, netAmount: 490, status: "pending", requestedAt: "2025-12-05T10:00:00Z", estimatedCompletionAt: "2025-12-08T10:00:00Z", paymentMethodId: "pm_xxx" }, account: { balance: 745.50, reservedBalance: 500 } } ``` **Estados de Retiro:** - `pending`: Solicitado, esperando 72h - `processing`: Procesando pago con Stripe - `completed`: Retiro completado exitosamente - `failed`: Fallo en procesamiento (reintentable) - `cancelled`: Cancelado por usuario **Flujo de Retiro:** 1. Usuario solicita retiro 2. Sistema valida balance y cierra posiciones si es necesario 3. Se crea withdrawal con status "pending" 4. Balance se reserva (no disponible para trading) 5. Después de 72h, cron job cambia status a "processing" 6. Se procesa pago con Stripe 7. Si éxito: status "completed", se actualiza balance 8. Si falla: status "failed", se libera balance reservado **Límites:** - Retiro mínimo: $50 USD - Retiro máximo: Balance disponible - Comisión: 2% del monto - Período de espera: 72 horas - Máximo 1 retiro pendiente por cuenta --- ## Definition of Ready (DoR) - [x] Historia claramente escrita - [x] Criterios de aceptación definidos - [x] Story points estimados - [x] Dependencias identificadas - [ ] Integración Stripe documentada - [ ] Diseño/mockup disponible - [x] API spec disponible ## Definition of Done (DoD) - [ ] Código implementado según criterios - [ ] Tests unitarios escritos y pasando - [ ] Tests de integración pasando - [ ] Integración Stripe funcionando - [ ] Cron job configurado y probado - [ ] 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