---
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
