--- id: "RF-INV-004" title: "Sistema de Retiros" type: "Requirement" status: "Done" priority: "Alta" epic: "OQI-004" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # RF-INV-004: Sistema de Retiros ## Metadata | Campo | Valor | |-------|-------| | **ID** | RF-INV-004 | | **Épica** | OQI-004 - Cuentas de Inversión | | **Tipo** | Requerimiento Funcional | | **Prioridad** | P0 | | **Story Points** | 10 | | **Estado** | Aprobado | | **Última actualización** | 2025-12-05 | --- ## Descripción El sistema debe permitir a los usuarios retirar fondos de sus cuentas de inversión hacia su wallet interno o directamente a su cuenta bancaria/tarjeta, con validaciones de seguridad y procesamiento seguro. --- ## Destinos de Retiro ### 1. Wallet Interno | Característica | Valor | |----------------|-------| | Mínimo | $10 USD | | Máximo | Balance disponible | | Comisión | Sin comisión | | Tiempo procesamiento | Instantáneo | | Disponibilidad | Inmediata para re-inversión o retiro externo | ### 2. Stripe (Payout a banco/tarjeta) | Característica | Valor | |----------------|-------| | Mínimo | $50 USD | | Máximo | $25,000 USD | | Comisión | 0.25% (mín $0.25) | | Tiempo procesamiento | 2-5 días hábiles | | Requisito | Cuenta bancaria o tarjeta débito verificada | --- ## Balance Disponible vs Total ``` ┌─────────────────────────────────────────────────────────────┐ │ BALANCE DE CUENTA │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Balance Total = Balance Disponible + Balance Bloqueado │ │ │ │ ┌──────────────────┐ ┌────────────────────────────┐ │ │ │ DISPONIBLE │ │ BLOQUEADO │ │ │ │ │ │ │ │ │ │ • Para retiro │ │ • Margin usado en trades │ │ │ │ • Para trading │ │ • Retiros pendientes │ │ │ │ │ │ • Reservas de seguridad │ │ │ └──────────────────┘ └────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### Cálculo de Balance Disponible ```typescript availableBalance = totalBalance - unrealizedPnl // P&L no realizado de posiciones abiertas - marginUsed // Margen usado en posiciones abiertas - pendingWithdrawals // Retiros en proceso - reserveAmount // Reserva mínima (5% del balance) ``` --- ## Flujo de Retiro ### Diagrama de Flujo ``` ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Request │────▶│ Validate │────▶│ Security │────▶│ Process │ │ Withdraw│ │ Amount │ │ Check │ │ Withdraw │ └─────────┘ └──────────┘ └──────────┘ └────┬─────┘ │ ┌──────────────────────────────────┘ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Confirm │────▶│ Transfer │────▶│ Notify │ │ Email │ │ Funds │ │ User │ └──────────┘ └──────────┘ └──────────┘ ``` ### Estados de Retiro | Estado | Descripción | |--------|-------------| | `pending_confirmation` | Esperando confirmación por email | | `pending` | Confirmado, esperando procesamiento | | `processing` | En proceso de transferencia | | `completed` | Fondos transferidos | | `failed` | Error en procesamiento | | `cancelled` | Cancelado por usuario | --- ## Funcionalidades Requeridas ### RF-INV-004.1: Solicitar Retiro El usuario debe poder: - Ver balance disponible para retiro - Seleccionar destino (wallet o payout) - Ingresar monto a retirar - Ver resumen con comisiones - Confirmar solicitud ### RF-INV-004.2: Verificación de Seguridad Para retiros, el sistema debe validar: - Confirmación por email (link válido por 1 hora) - 2FA si está habilitado - Límites diarios no excedidos - Cuenta no en proceso de cierre ### RF-INV-004.3: Retiro a Wallet ```typescript interface WalletWithdrawalRequest { accountId: string; // Cuenta de inversión origen amount: number; // Monto en USD } interface WalletWithdrawalResponse { withdrawalId: string; status: 'completed'; amount: number; fee: 0; newAccountBalance: number; newWalletBalance: number; } ``` ### RF-INV-004.4: Retiro Externo (Stripe Payout) ```typescript interface ExternalWithdrawalRequest { accountId: string; amount: number; payoutMethodId: string; // Stripe connected account o método guardado } interface ExternalWithdrawalResponse { withdrawalId: string; status: 'pending'; amount: number; fee: number; netAmount: number; estimatedArrival: Date; // 2-5 días hábiles } ``` ### RF-INV-004.5: Límites y Validaciones | Validación | Límite | Mensaje de Error | |------------|--------|------------------| | Mínimo wallet | $10 | "El monto mínimo para retiro a wallet es $10" | | Mínimo externo | $50 | "El monto mínimo para retiro externo es $50" | | Máximo diario | $25,000 | "Has alcanzado el límite diario de retiros" | | Balance insuficiente | - | "Balance disponible insuficiente" | | Posiciones abiertas | - | "Debes cerrar posiciones antes de retirar todo" | --- ## Modelo de Datos ### Entidad: WithdrawalTransaction ```typescript interface WithdrawalTransaction { id: string; // UUID accountId: string; // FK a investment_accounts userId: string; // FK a users destination: 'wallet' | 'bank' | 'card'; status: WithdrawalStatus; // Montos amount: number; // Monto solicitado fee: number; // Comisión netAmount: number; // Monto a recibir currency: 'USD'; // Stripe specific stripePayoutId?: string; stripeTransferId?: string; // Wallet specific walletTransactionId?: string; // Seguridad confirmationToken?: string; confirmedAt?: Date; confirmationIp?: string; // Metadata ipAddress: string; userAgent: string; // Timestamps createdAt: Date; processedAt?: Date; completedAt?: Date; failedAt?: Date; cancelledAt?: Date; // Error handling failureReason?: string; } ``` --- ## Reglas de Negocio 1. **RN-030**: Los retiros requieren confirmación por email 2. **RN-031**: No se puede retirar más del balance disponible 3. **RN-032**: Retiro total requiere cerrar posiciones abiertas primero 4. **RN-033**: Límite diario de $25,000 USD en retiros 5. **RN-034**: Retiros a wallet son instantáneos 6. **RN-035**: Retiros externos tienen período de espera de 2-5 días 7. **RN-036**: Los retiros pueden cancelarse antes de ser procesados 8. **RN-037**: Cooldown de 24 horas para retiro después de agregar nuevo método de pago --- ## Criterios de Aceptación ```gherkin Escenario: Retiro a wallet exitoso DADO que el usuario tiene $1,000 disponibles en su cuenta Atlas CUANDO solicita retirar $500 a su wallet Y confirma por email ENTONCES el balance de la cuenta disminuye en $500 Y el balance del wallet aumenta en $500 Y no se cobra comisión Y recibe confirmación instantánea Escenario: Retiro externo exitoso DADO que el usuario tiene cuenta bancaria verificada Y tiene $5,000 disponibles en su cuenta CUANDO solicita retirar $1,000 a su banco Y confirma por email ENTONCES el retiro queda en estado "processing" Y se muestra fecha estimada de llegada Y recibe email cuando los fondos se envíen Escenario: Retiro supera límite diario DADO que el usuario ya retiró $20,000 hoy CUANDO intenta retirar $10,000 adicionales ENTONCES recibe error "Has alcanzado el límite diario de retiros" Y se muestra cuándo se reinicia el límite Escenario: Retiro con posiciones abiertas DADO que el usuario tiene $1,000 disponibles Y tiene $200 en margen usado por posiciones abiertas CUANDO intenta retirar $900 ENTONCES recibe error "Balance disponible insuficiente" Y ve que su balance disponible es $800 Y tiene opción de ver posiciones abiertas ``` --- ## API Endpoints | Método | Endpoint | Descripción | |--------|----------|-------------| | GET | /investment/withdrawals/available | Ver balance disponible | | POST | /investment/withdrawals/wallet | Retirar a wallet | | POST | /investment/withdrawals/payout | Retirar externo | | GET | /investment/withdrawals | Listar retiros | | GET | /investment/withdrawals/:id | Detalle de retiro | | POST | /investment/withdrawals/:id/confirm | Confirmar retiro | | DELETE | /investment/withdrawals/:id | Cancelar retiro | --- ## Notificaciones | Evento | Canal | Contenido | |--------|-------|-----------| | Retiro solicitado | Email | Link de confirmación | | Retiro confirmado | Email + Push | Confirmación y ETA | | Retiro procesado | Email + Push | Fondos en camino | | Retiro completado | Email | Fondos recibidos | | Retiro fallido | Email + Push | Razón + pasos a seguir | --- ## Referencias - [US-INV-008: Retirar fondos](../historias-usuario/US-INV-008-retirar-fondos.md) - [ET-INV-003: Stripe Integration](../especificaciones/ET-INV-003-stripe.md) - [RF-PAY-003: Wallet](../../OQI-005-payments-stripe/requerimientos/RF-PAY-003-wallet.md) --- **Autor:** Requirements-Analyst **Fecha:** 2025-12-05