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>
327 lines
10 KiB
Markdown
327 lines
10 KiB
Markdown
---
|
|
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
|