# OQI-005: Wallet - Especificación Completa

**Módulo:** OQI-005 (pagos-stripe)
**Componentes:** WalletCard, WalletDepositModal, WalletWithdrawModal
**Servicio:** getWallet, depositToWallet, withdrawFromWallet, getWalletTransactions
**Fecha:** 2026-01-25

---

## 1. Arquitectura de Wallet

### Tipos de Wallets (DDL)
```typescript
type WalletType = 'trading' | 'investment' | 'earnings' | 'referral';
type WalletStatus = 'active' | 'frozen' | 'closed';
```

### Estados de Transacciones
```typescript
type TransactionType =
  | 'deposit'
  | 'withdrawal'
  | 'transfer_in'
  | 'transfer_out'
  | 'fee'
  | 'refund'
  | 'earning'
  | 'distribution'
  | 'bonus';

type TransactionStatus =
  | 'pending'
  | 'processing'
  | 'completed'
  | 'failed'
  | 'cancelled'
  | 'reversed';
```

### Modelo de Datos

**Wallet:**
```typescript
interface Wallet {
  id: string;
  tenantId: string;
  userId: string;
  walletType: WalletType;
  status: WalletStatus;
  balance: number;                    // Total
  availableBalance: number;           // Disponible para retirar
  pendingBalance: number;             // En procesamiento
  reservedBalance: number;            // Congelado
  currency: string;                   // "USD"
  dailyLimit: number;
  monthlyLimit: number;
  dailyUsed: number;
  monthlyUsed: number;
  totalDeposited: number;             // Acumulativo
  totalWithdrawn: number;             // Acumulativo
  lastActivityAt?: string;
  metadata?: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
}
```

**WalletTransaction:**
```typescript
interface WalletTransaction {
  id: string;
  tenantId: string;
  walletId: string;
  type: TransactionType;
  status: TransactionStatus;
  amount: number;                     // Monto principal
  balanceBefore: number;              // Saldo anterior
  balanceAfter: number;               // Saldo post-transacción
  fee: number;                        // Comisión aplicada
  netAmount: number;                  // amount - fee
  currency: string;                   // "USD"
  description: string;                // "Depósito via Stripe"
  referenceId?: string;               // ID pago/retiro
  referenceType?: string;             // "payment", "withdrawal"
  externalId?: string;                // ID externo (Stripe)
  metadata?: Record<string, unknown>;
  processedAt?: string;               // Fecha confirmación
  failureReason?: string;             // Motivo si falló
  createdAt: string;
  updatedAt: string;
}
```

---

## 2. WalletCard - Vista Principal

**Ubicación:** `/components/payments/WalletCard.tsx`

### Estructura Visual

```
┌─ WALLET CARD ────────────────────────────────────────┐
│                                                       │
│  [💼] Saldo Disponible                              │
│       $1,250.75                                      │
│                                                       │
│  ┌───────────────────┬───────────────────┐          │
│  │ Pendiente         │ Total en cuenta   │          │
│  │ $125.50           │ $1,376.25         │          │
│  └───────────────────┴───────────────────┘          │
│                                                       │
│  [🟢 Depositar] [🔗 Retirar]                        │
│                                                       │
│  Total Depositado: $5,000.00                         │
│  Total Retirado: $3,750.00                           │
│                                                       │
│  Transacciones Recientes (top 5)                    │
│  ┌──────────────────────────────────────────────┐   │
│  │ [↓] Depósito             Hace 2h              │   │
│  │     $500.00 → Saldo: $1,250.75               │   │
│  │                                              │   │
│  │ [↑] Retiro               Hace 1d             │   │
│  │     -$250.00 → Saldo: $750.75                │   │
│  │                                              │   │
│  │ [🎁] Recompensa          Hace 3d             │   │
│  │     +$50.00 → Saldo: $1,000.75               │   │
│  └──────────────────────────────────────────────┘   │
│                                                       │
│  [Ver todo >]                                        │
└───────────────────────────────────────────────────────┘
```

### Propiedades

```typescript
interface WalletCardProps {
  wallet: Wallet;
  recentTransactions?: WalletTransaction[];
  onDeposit?: () => void;
  onWithdraw?: () => void;
  onViewHistory?: () => void;
  loading?: boolean;
}
```

### Funcionalidades

| Feature | Implementado | Nota |
|---------|-------------|------|
| Mostrar saldo disponible | ✅ | Valor principal destacado |
| Mostrar saldo pendiente | ✅ | Grid inferior |
| Mostrar saldo total | ✅ | Incluye pendiente + disponible |
| Total depositado (acumulativo) | ✅ | Desde inception |
| Total retirado (acumulativo) | ✅ | Desde inception |
| Transacciones recientes (top 5) | ✅ | Con iconos por tipo |
| Botón Depositar | ✅ | Abre WalletDepositModal |
| Botón Retirar | ✅ | Abre WalletWithdrawModal, deshabilitado si saldo=0 |
| Iconos por tipo transacción | ✅ | Deposit (verde), Withdrawal (rojo), Reward (púrpura), etc |
| Formato moneda | ✅ | Intl.NumberFormat con currency |
| Fecha relativa | ✅ | "Hace 2h", "Hace 1d" |

### Iconografía de Transacciones

```typescript
const transactionIcons: Record<TransactionType, React.ReactNode> = {
  deposit: <ArrowDownCircle className="w-5 h-5 text-green-400" />,      // Dinero entra
  withdrawal: <ArrowUpCircle className="w-5 h-5 text-red-400" />,       // Dinero sale
  reward: <Gift className="w-5 h-5 text-purple-400" />,                 // Bonus/Reward
  refund: <RefreshCw className="w-5 h-5 text-blue-400" />,              // Devolución
  purchase: <ShoppingCart className="w-5 h-5 text-orange-400" />,       // Gasto
};
```

### Colores de Saldo

```typescript
const getAmountColor = (type: TransactionType) => {
  // Positivo (verde): deposit, reward, refund, earning
  // Negativo (rojo): withdrawal, purchase, transfer_out, fee
  return (type === 'withdrawal' || type === 'purchase')
    ? 'text-red-400'
    : 'text-green-400';
};
```

---

## 3. WalletDepositModal - Agregar Fondos

**Ubicación:** `/components/payments/WalletDepositModal.tsx`

### Flujo

```
Usuario abre modal
    ↓
Carga métodos de pago desde usePaymentStore
    ↓
Auto-selecciona método predeterminado
    ↓
Usuario:
├── Ingresa monto (o click en preset)
├── Selecciona método de pago
└── Click "Depositar $XXX"
    ↓
Validaciones:
├── Monto >= $10
└── Método seleccionado
    ↓
POST /payments/wallet/deposit
    ↓
Si success:
├── Muestra CheckCircle
├── "Depósito Exitoso"
├── "$XXX se han agregado a tu wallet"
└── Cierra modal después 2s
```

### Interfaz

```
┌─ DEPOSITAR FONDOS ─────────────────────────────┐
│ [X]                                            │
│                                                │
│ Monto a depositar                              │
│ [$ |  100  | USD]                              │
│                                                │
│ Preset amounts:                                │
│ [$50] [$100] [$250] [$500] [$1000]            │
│                                                │
│ Método de pago                                 │
│ ○ Visa •••• 4242  (Expires 12/26)             │
│   [Default]                                    │
│ ○ Mastercard •••• 5555  (Expires 08/25)      │
│                                                │
│ Error message (if any)                         │
│ ⚠️ El monto mínimo es $10                      │
│                                                │
│ [Depositar $100]                               │
│                                                │
│ Los depósitos se procesan de forma segura      │
│ a través de Stripe                             │
└────────────────────────────────────────────────┘
```

### Propiedades

```typescript
interface WalletDepositModalProps {
  isOpen: boolean;
  onClose: () => void;
  onSuccess?: () => void;
  currency?: string;  // Default: "USD"
}
```

### Validaciones

| Validación | Error | Nota |
|-----------|-------|------|
| amount < 10 | "El monto mínimo es $10" | parseFloat |
| !selectedMethod | "Selecciona un metodo de pago" | Required field |
| payment methods vacío | "No tienes metodos de pago guardados" | Link a settings |
| API error | "Error al procesar el deposito. Intenta de nuevo." | catch generic |

### Comportamiento

```typescript
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  const numAmount = parseFloat(amount);

  if (isNaN(numAmount) || numAmount < 10) {
    setError('El monto minimo es $10');
    return;
  }

  if (!selectedMethod) {
    setError('Selecciona un metodo de pago');
    return;
  }

  setLoading(true);
  setError(null);

  try {
    await depositToWallet(numAmount, selectedMethod);
    setSuccess(true);
    setTimeout(() => {
      onSuccess?.();
      onClose();
    }, 2000);  // 2 segundos antes de cerrar
  } catch (err) {
    setError('Error al procesar el deposito. Intenta de nuevo.');
  } finally {
    setLoading(false);
  }
};
```

### Montos Preestablecidos

```typescript
const PRESET_AMOUNTS = [50, 100, 250, 500, 1000];
```

### Endpoint Backend

```
POST /payments/wallet/deposit

Body:
{
  amount: number,           // Monto en USD
  paymentMethodId: string   // ID del método guardado
}

Response:
{
  transaction: {
    id: string,
    amount: number,
    status: 'pending',      // O 'processing'
    ...
  },
  clientSecret?: string     // Para Payment Intent (si aplica)
}
```

---

## 4. WalletWithdrawModal - Retirar Fondos

**Ubicación:** `/components/payments/WalletWithdrawModal.tsx`

### Flujo

```
Usuario abre modal
    ↓
Muestra saldo disponible
    ↓
Usuario:
├── Ingresa monto a retirar
├── Selecciona destino (cuenta bancaria)
└── Click "Solicitar Retiro"
    ↓
Validaciones:
├── Monto >= $10
├── Monto <= saldo disponible
└── Cuenta bancaria no vacía
    ↓
Calcula comisión (1%, min $1)
    ↓
Muestra resumen:
├── Monto: $XXX
├── Comisión: -$Y
└── Recibirás: $ZZZ
    ↓
POST /payments/wallet/withdraw
    ↓
Si success:
├── Muestra CheckCircle
├── "Retiro Solicitado"
├── "Tu retiro de $XXX está siendo procesado"
├── "El tiempo de procesamiento es de 1-3 días hábiles"
└── Cierra modal después 2s
```

### Interfaz

```
┌─ RETIRAR FONDOS ────────────────────────────────┐
│ [X]                                            │
│                                                │
│ Saldo disponible                               │
│ $1,250.75 USD                                  │
│                                                │
│ Monto a retirar                                │
│ [$ |     | USD]  [MAX]                         │
│                                                │
│ Cuenta bancaria destino                        │
│ [🏦 |  Bank Account ID  ]                      │
│ Puedes configurar tus cuentas bancarias        │
│ en el portal de Stripe                         │
│                                                │
│ ⚠️ Importante:                                 │
│    • Los retiros tardan 1-3 días hábiles      │
│    • Se aplicará una comisión del 1% (min $1) │
│    • El monto mínimo de retiro es $10         │
│                                                │
│ Error message (if any)                        │
│                                                │
│ Resumen (si monto >= 10):                     │
│ ┌─────────────────────────────────────────┐   │
│ │ Monto:        $500.00                  │   │
│ │ Comisión (1%): -$5.00                  │   │
│ │ ────────────────────────────────────── │   │
│ │ Recibirás:    $495.00                  │   │
│ └─────────────────────────────────────────┘   │
│                                                │
│ [Solicitar Retiro]                            │
└────────────────────────────────────────────────┘
```

### Propiedades

```typescript
interface WalletWithdrawModalProps {
  isOpen: boolean;
  onClose: () => void;
  onSuccess?: () => void;
  availableBalance: number;
  currency?: string;  // Default: "USD"
}
```

### Validaciones

| Validación | Error | Nota |
|-----------|-------|------|
| amount < 10 | "El monto minimo de retiro es $10" | parseFloat |
| amount > availableBalance | "El monto excede tu saldo disponible" | Comparación |
| !bankAccount | "Ingresa el ID de tu cuenta bancaria" | Required field |
| Comisión aplicada | - | Math.max(amount * 0.01, 1) |

### Cálculo de Comisión

```typescript
// Comisión: 1% del monto, mínimo $1
const commission = Math.max(parseFloat(amount) * 0.01, 1);
const received = parseFloat(amount) - commission;
```

### Comportamiento

```typescript
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  const numAmount = parseFloat(amount);

  if (isNaN(numAmount) || numAmount < 10) {
    setError('El monto minimo de retiro es $10');
    return;
  }

  if (numAmount > availableBalance) {
    setError('El monto excede tu saldo disponible');
    return;
  }

  if (!bankAccount.trim()) {
    setError('Ingresa el ID de tu cuenta bancaria');
    return;
  }

  setLoading(true);
  setError(null);

  try {
    await withdrawFromWallet(numAmount, {
      type: 'bank_account',
      accountId: bankAccount.trim(),
    });
    setSuccess(true);
    setTimeout(() => {
      onSuccess?.();
      onClose();
    }, 2000);
  } catch (err) {
    setError('Error al procesar el retiro. Verifica los datos e intenta de nuevo.');
  } finally {
    setLoading(false);
  }
};
```

### Botón MAX

```typescript
const handleMaxAmount = () => {
  setAmount(String(availableBalance));
};
```

### Endpoint Backend

```
POST /payments/wallet/withdraw

Body:
{
  amount: number,
  destination: {
    type: 'bank_account',
    accountId: string    // ID cuenta bancaria en Stripe Connect
  }
}

Response:
{
  id: string,
  amount: number,
  fee: number,          // Comisión
  netAmount: number,    // Monto neto después comisión
  status: 'pending',    // Procesamiento 1-3 días
  ...
}
```

### Avisos

```
⚠️ Importante:
   • Los retiros tardan 1-3 días hábiles en procesarse
   • Se aplicará una comisión del 1% (min $1)
   • El monto mínimo de retiro es $10
```

---

## 5. TransactionHistory - Historial Completo

**Ubicación:** `/components/payments/TransactionHistory.tsx`

**Integración:** Billing.tsx no lo usa actualmente (pero está disponible)

### Funcionalidades

```
┌─ TRANSACTION HISTORY ──────────────────────────────┐
│                                                    │
│ [Filter: All Transactions ▼] [Refresh] [Export]  │
│                                                    │
│ ┌──────────────────────────────────────────────┐  │
│ │ [↓] Depósito                    Hace 2h    │  │
│ │     $500.00 → Completed                    │  │
│ │     Ref: pm_1234...                        │  │
│ │                                            │  │
│ │ [↑] Retiro                      Hace 1d    │  │
│ │     $250.00 → Processing                   │  │
│ │     Ref: tr_5678...                        │  │
│ │                                            │  │
│ │ [🎁] Recompensa                 Hace 3d    │  │
│ │     $50.00 → Completed                     │  │
│ └──────────────────────────────────────────────┘  │
│                                                    │
│ [← Anterior] [Siguiente →]  Página 1 de 3         │
└────────────────────────────────────────────────────┘
```

### Props

```typescript
interface TransactionHistoryProps {
  walletId?: string;
  filterType?: TransactionType;        // 'all', 'deposit', etc
  itemsPerPage?: number;               // Default: 10
  showPagination?: boolean;
  showFilter?: boolean;
  showExport?: boolean;
  onTransactionClick?: (transaction: Transaction) => void;
  compact?: boolean;
}
```

### Filtros Disponibles

```typescript
type TransactionType = 'deposit' | 'withdrawal' | 'payment' | 'refund' | 'transfer' | 'all';
```

### Funcionalidades

| Feature | Implementado |
|---------|-------------|
| Filtrar por tipo | ✅ |
| Paginación | ✅ |
| Búsqueda | ❌ |
| Exportar CSV | ✅ |
| Iconos por tipo | ✅ |
| Iconos por estado | ✅ |
| Fecha relativa | ✅ |
| Referencia ID | ✅ (compact=false) |
| Detalle modal | ❌ |

### Exportar a CSV

```typescript
const handleExport = () => {
  const headers = ['Date', 'Type', 'Description', 'Amount', 'Status', 'Reference'];
  const rows = transactions.map((tx) => [
    new Date(tx.createdAt).toLocaleString(),
    tx.type,
    tx.description,
    `${tx.amount >= 0 ? '+' : ''}${tx.amount.toFixed(2)} ${tx.currency}`,
    tx.status,
    tx.reference || '',
  ]);

  const csv = [headers, ...rows].map((row) => row.join(',')).join('\n');
  const blob = new Blob([csv], { type: 'text/csv' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `transactions-${new Date().toISOString().split('T')[0]}.csv`;
  a.click();
  URL.revokeObjectURL(url);
};
```

### Endpoint Backend

```
GET /payments/wallet/transactions

Params:
{
  type?: string,      // 'deposit', 'withdrawal', etc
  page?: number,      // Default: 1
  limit?: number      // Default: 10
}

Response:
{
  transactions: WalletTransaction[],
  total: number
}
```

---

## 6. Integración en Billing Page

**Componente:** `Billing.tsx` → Tab "Wallet"

```typescript
{activeTab === 'wallet' && (
  <div>
    {wallet ? (
      <WalletCard
        wallet={wallet}
        recentTransactions={walletTransactions}
        onDeposit={() => setShowDepositModal(true)}
        onWithdraw={() => setShowWithdrawModal(true)}
        onViewHistory={() => {}}  // No implementado
        loading={loadingWallet}
      />
    ) : (
      <div className="text-center py-12 bg-gray-800 rounded-xl border border-gray-700">
        <h3 className="font-medium text-white mb-2">
          Wallet no disponible
        </h3>
        <p className="text-gray-400">
          Suscríbete a un plan para activar tu wallet
        </p>
      </div>
    )}
  </div>
)}

<WalletDepositModal
  isOpen={showDepositModal}
  onClose={() => setShowDepositModal(false)}
  onSuccess={() => {
    setShowDepositModal(false);
    fetchWallet();  // Refresca saldo
  }}
/>

<WalletWithdrawModal
  isOpen={showWithdrawModal}
  onClose={() => setShowWithdrawModal(false)}
  onSuccess={() => {
    setShowWithdrawModal(false);
    fetchWallet();  // Refresca saldo
  }}
  availableBalance={wallet?.availableBalance || 0}
/>
```

---

## 7. Endpoints Wallet Service

| Endpoint | Método | Descripción |
|----------|--------|------------|
| `/payments/wallet` | GET | Obtener wallet actual |
| `/payments/wallet/deposit` | POST | Depositar fondos |
| `/payments/wallet/withdraw` | POST | Retirar fondos |
| `/payments/wallet/transactions` | GET | Historial transacciones |

---

## 8. Estado de Implementación

### Completo ✅
- WalletCard (visualización)
- WalletDepositModal (flujo completo)
- WalletWithdrawModal (flujo completo)
- TransactionHistory (tabla + paginación + export)
- Integración en Billing page

### Parcial ⚠️
- Actualización automática de saldo (requiere refetch manual)
- Comisión hardcoded en frontend (1%)

### No Implementado ❌
- Historial cambios de estado
- Cancelación de retiros pendientes
- Alertas de límites diarios/mensuales
- Congelación automática de transacciones sospechosas
- 2FA para retiros > $X

---

## 9. Consideraciones de UX

### Seguridad
- ✅ Confirmación modal antes de eliminar
- ✅ Avisos de comisión y tiempos
- ✅ Validación campos requeridos
- ✅ Manejo de errores con mensajes claros

### Accesibilidad
- ✅ Labels asociados a inputs
- ✅ Botones identificables
- ✅ Iconos + texto
- ⚠️ Colores no deben ser único indicador (completado)

### Performance
- ✅ Paginación (no carga todo)
- ✅ Debounce en búsqueda
- ✅ Lazy loading de historiales
- ⚠️ Real-time updates (no implementado)

