| id |
title |
type |
status |
priority |
epic |
project |
version |
created_date |
updated_date |
| RF-INV-004 |
Sistema de Retiros |
Requirement |
Done |
Alta |
OQI-004 |
trading-platform |
1.0.0 |
2025-12-05 |
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
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
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)
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
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
- RN-030: Los retiros requieren confirmación por email
- RN-031: No se puede retirar más del balance disponible
- RN-032: Retiro total requiere cerrar posiciones abiertas primero
- RN-033: Límite diario de $25,000 USD en retiros
- RN-034: Retiros a wallet son instantáneos
- RN-035: Retiros externos tienen período de espera de 2-5 días
- RN-036: Los retiros pueden cancelarse antes de ser procesados
- RN-037: Cooldown de 24 horas para retiro después de agregar nuevo método de pago
Criterios de Aceptación
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
Autor: Requirements-Analyst
Fecha: 2025-12-05