---
id: "RF-PAY-008"
title: "Sistema de Depositos via SPEI"
type: "Requirement"
status: "Draft"
priority: "Alta"
epic: "OQI-005"
project: "trading-platform"
version: "1.0.0"
created_date: "2026-01-04"
updated_date: "2026-01-04"
---

# RF-PAY-008: Sistema de Depositos via SPEI

**Version:** 1.0.0
**Fecha:** 2026-01-04
**Estado:** Draft
**Prioridad:** P1 (Alta)
**Story Points:** 8
**Epica:** [OQI-005](../_MAP.md)

---

## Descripcion

El sistema debe permitir a los usuarios en Mexico realizar depositos utilizando SPEI (Sistema de Pagos Electronicos Interbancarios), el sistema de transferencias bancarias instantaneas del pais. Se integrara con un agregador de pagos (Stripe Mexico, Conekta u OpenPay) para generar CLABEs virtuales unicas por usuario y reconciliar automaticamente los depositos recibidos.

---

## Objetivo de Negocio

- Capturar mercado mexicano con metodo de pago preferido (85% de transferencias)
- Reducir costos de transaccion vs tarjetas (1-2% vs 3.5%)
- Eliminar chargebacks (SPEI es irreversible)
- Ofrecer alternativa para usuarios sin tarjeta de credito
- Aumentar ticket promedio (SPEI permite montos mayores)

---

## Casos de Uso

1. **Deposito Simple:** Usuario transfiere $5,000 MXN desde su banca en linea a su CLABE virtual
2. **Primer Deposito:** Usuario obtiene su CLABE unica y realiza primera transferencia
3. **Deposito Recurrente:** Usuario guarda CLABE como beneficiario frecuente en su banco
4. **Reconciliacion:** Sistema detecta deposito y acredita USD equivalente automaticamente
5. **Notificacion:** Usuario recibe push/email confirmando deposito procesado

---

## Integracion con Agregadores

### Opciones de Agregador

| Agregador | CLABE Virtual | Fee | Tiempo Integracion |
|-----------|---------------|-----|---------------------|
| Stripe MX | Si | 1% + $3 MXN | 2-3 semanas |
| Conekta | Si | 1.2% + $4 MXN | 1-2 semanas |
| OpenPay | Si | 0.9% + $2.5 MXN | 2-3 semanas |

**Recomendado:** Stripe MX por consistencia con otros metodos de pago.

---

## Requisitos Funcionales

### RF-PAY-008.1: Generacion de CLABE Virtual

**DEBE:**
1. Generar CLABE de 18 digitos unica por usuario
2. Asociar CLABE a `userId` en tabla `spei_references`
3. CLABE permanente (no expira)
4. Mostrar en UI con opcion de copiar
5. Incluir nombre del beneficiario estandarizado

**Formato CLABE:**
```
Banco (3) + Plaza (3) + Cuenta (11) + Digito verificador (1)

Ejemplo: 646180157000001234
         ^^^                  Banco: STP (646)
            ^^^               Plaza: 180
               ^^^^^^^^^^^    Cuenta unica por usuario
                          ^   Digito verificador
```

**Modelo de datos:**
```typescript
@Entity({ name: 'spei_references', schema: 'billing' })
class SpeiReference {
  id: string;              // UUID
  userId: string;          // FK a users (UNIQUE)
  walletId: string;        // FK a wallets
  clabe: string;           // CLABE 18 digitos (UNIQUE)
  beneficiaryName: string; // "TRADING PLATFORM/USER123"
  bankName: string;        // "STP" o banco del agregador
  isActive: boolean;       // true
  createdAt: Date;
  updatedAt: Date;
}
```

### RF-PAY-008.2: Recepcion de Depositos

**DEBE:**
1. Recibir webhook del agregador cuando llega transferencia
2. Validar firma del webhook
3. Identificar usuario por CLABE destino
4. Crear registro en `spei_deposits`
5. Procesar conversion y acreditacion

**Datos del webhook:**
```json
{
  "event": "spei.incoming_transfer",
  "data": {
    "id": "tr_xxx",
    "clabe_destino": "646180157000001234",
    "clabe_origen": "012180001234567890",
    "monto": 5000.00,
    "concepto": "DEPOSITO TRADING",
    "referencia_numerica": "1234567",
    "nombre_ordenante": "JUAN PEREZ",
    "rfc_ordenante": "XAXX010101000",
    "fecha_operacion": "2026-01-04T10:30:00Z"
  }
}
```

### RF-PAY-008.3: Conversion MXN a USD

**DEBE:**
1. Obtener tipo de cambio actual MXN/USD
2. Usar tipo de cambio del Banco de Mexico + spread
3. Calcular USD equivalente
4. Acreditar al wallet en USD
5. Registrar tipo de cambio usado

**Calculo:**
```
USD_Amount = MXN_Amount / (Exchange_Rate * (1 + spread))

Ejemplo (spread 0.5%):
$5,000 MXN / (17.50 * 1.005) = $284.21 USD
```

### RF-PAY-008.4: Reconciliacion Automatica

**DEBE:**
1. Procesar depositos automaticamente (sin intervencion manual)
2. Manejar depositos duplicados (idempotencia)
3. Rechazar depositos de CLABEs no registradas
4. Alertar sobre depositos anomalos (montos muy altos)
5. Generar reporte diario de reconciliacion

**Estados de Deposito:**
```
RECEIVED   → Webhook recibido, validando
PROCESSING → Conversion en proceso
COMPLETED  → Acreditado al wallet
FAILED     → Error en procesamiento
REJECTED   → CLABE no encontrada / limite excedido
```

### RF-PAY-008.5: Notificaciones

**DEBE:**
1. Push notification inmediata al recibir deposito
2. Email de confirmacion con detalles
3. Mostrar en historial de transacciones
4. Incluir tipo de cambio usado

**Contenido de notificacion:**
```
Asunto: Deposito SPEI recibido - $5,000 MXN

Has recibido un deposito SPEI:
- Monto: $5,000.00 MXN
- Equivalente: $284.21 USD (TC: 17.59)
- Referencia: 1234567
- Fecha: 4 de enero 2026, 10:30 AM

Tu nuevo saldo es: $534.21 USD
```

---

## Modelo de Datos

### Tabla: spei_deposits

```typescript
@Entity({ name: 'spei_deposits', schema: 'billing' })
class SpeiDeposit {
  id: string;                    // UUID
  userId: string;                // FK a users
  walletId: string;              // FK a wallets
  speiReferenceId: string;       // FK a spei_references
  status: SpeiDepositStatus;     // received, processing, completed, failed, rejected

  // Datos SPEI
  clabeOrigen: string;           // CLABE del ordenante
  clabeDestino: string;          // CLABE del usuario
  montoMxn: Decimal;             // Monto en MXN
  concepto: string;              // Concepto de la transferencia
  referenciaNumerica: string;    // Referencia numerica (7 digitos)
  nombreOrdenante: string;       // Nombre del que envia
  rfcOrdenante?: string;         // RFC del ordenante
  fechaOperacion: Date;          // Fecha/hora de la operacion

  // Conversion
  montoUsd: Decimal;             // Monto convertido a USD
  tipoCambio: Decimal;           // Tipo de cambio usado
  spreadAplicado: Decimal;       // Spread aplicado (0.005)

  // Proveedor
  providerRef: string;           // ID en Stripe/Conekta/OpenPay
  providerData?: object;         // Datos raw del proveedor

  // Timestamps
  createdAt: Date;
  processedAt?: Date;
  updatedAt: Date;
}

enum SpeiDepositStatus {
  RECEIVED = 'received',
  PROCESSING = 'processing',
  COMPLETED = 'completed',
  FAILED = 'failed',
  REJECTED = 'rejected'
}
```

---

## Flujo de Deposito SPEI

```
+-------------+     +-------------+     +-------------+     +-------------+     +-------------+
|   Usuario   |     |  Banco MX   |     |  Agregador  |     |   Backend   |     |   Wallet    |
+------+------+     +------+------+     +------+------+     +------+------+     +------+------+
       |                   |                   |                   |                   |
       | 1. Obtener CLABE  |                   |                   |                   |
       |------------------>|                   |                   |                   |
       |                   |                   |                   |                   |
       |<------------------|                   |                   |                   |
       | CLABE:            |                   |                   |                   |
       | 646180157000001234|                   |                   |                   |
       |                   |                   |                   |                   |
       | 2. Accede a banca |                   |                   |                   |
       | en linea          |                   |                   |                   |
       |------------------>|                   |                   |                   |
       |                   |                   |                   |                   |
       | 3. Registra CLABE |                   |                   |                   |
       | como beneficiario |                   |                   |                   |
       |------------------>|                   |                   |                   |
       |                   |                   |                   |                   |
       | 4. Transfiere     |                   |                   |                   |
       | $5,000 MXN        |                   |                   |                   |
       |------------------>|                   |                   |                   |
       |                   |                   |                   |                   |
       |                   | 5. SPEI procesa   |                   |                   |
       |                   | transferencia     |                   |                   |
       |                   | (5-30 min)        |                   |                   |
       |                   |------------------>|                   |                   |
       |                   |                   |                   |                   |
       |                   |                   | 6. Webhook:       |                   |
       |                   |                   | spei.incoming     |                   |
       |                   |                   |------------------>|                   |
       |                   |                   |                   |                   |
       |                   |                   |                   | 7. Validar CLABE  |
       |                   |                   |                   | en spei_references|
       |                   |                   |                   |                   |
       |                   |                   |                   | 8. Crear          |
       |                   |                   |                   | spei_deposit      |
       |                   |                   |                   | status: RECEIVED  |
       |                   |                   |                   |                   |
       |                   |                   |                   | 9. Obtener TC     |
       |                   |                   |                   | del dia           |
       |                   |                   |                   |                   |
       |                   |                   |                   | 10. Convertir     |
       |                   |                   |                   | $5000 MXN =       |
       |                   |                   |                   | $284.21 USD       |
       |                   |                   |                   |                   |
       |                   |                   |                   | 11. Acreditar     |
       |                   |                   |                   |------------------>|
       |                   |                   |                   |<------------------|
       |                   |                   |                   | wallet.balance    |
       |                   |                   |                   | += $284.21        |
       |                   |                   |                   |                   |
       |                   |                   |                   | 12. Update status |
       |                   |                   |                   | COMPLETED         |
       |                   |                   |                   |                   |
       |<------------------|-------------------|-------------------|                   |
       | Push: "Deposito   |                   |                   |                   |
       | recibido!         |                   |                   |                   |
       | +$284.21 USD"     |                   |                   |                   |
       |                   |                   |                   |                   |
       |<------------------|-------------------|-------------------|                   |
       | Email: Detalles   |                   |                   |                   |
       | del deposito      |                   |                   |                   |
       |                   |                   |                   |                   |
```

---

## Reglas de Negocio

### RN-001: Limites de Deposito SPEI

| Limite | Valor |
|--------|-------|
| Deposito minimo | $100 MXN |
| Deposito maximo por transaccion | $500,000 MXN |
| Deposito maximo diario | $1,000,000 MXN |
| Sin limite mensual | - |

### RN-002: Tipo de Cambio

- Usar tipo de cambio FIX del Banco de Mexico
- Actualizar tipo de cambio cada 15 minutos
- Aplicar spread de **0.5%** sobre tipo de cambio
- Cachear tipo de cambio para consistencia intraday
- Mostrar tipo de cambio antes de depositar

### RN-003: Horarios SPEI

| Horario | Disponibilidad |
|---------|----------------|
| Lunes-Viernes 6:00-17:30 | Tiempo real (5-15 min) |
| Lunes-Viernes 17:30-20:00 | Mismo dia (antes 20:00) |
| Sabados 6:00-14:00 | Tiempo real |
| Domingos/Festivos | Siguiente dia habil |

### RN-004: Validacion de Depositos

- CLABE destino debe existir en `spei_references`
- Monto debe estar dentro de limites
- Referencia numerica no debe repetirse (idempotencia)
- Depositos fuera de horario se procesan en siguiente ventana

### RN-005: Reconciliacion

- Depositos se reconcilian automaticamente por CLABE
- No se requiere referencia especifica del usuario
- Concepto de transferencia se guarda para auditoria
- Reportes diarios a las 20:00 CST

---

## Integracion con Stripe Mexico

### Configuracion

```typescript
// Crear cuenta SPEI reference
const speiReference = await stripe.customers.createSource(
  customerId,
  {
    type: 'clabe',
    clabe: {
      // Stripe genera automaticamente
    }
  }
);

// Resultado
{
  id: "src_xxx",
  type: "clabe",
  clabe: {
    clabe: "646180157000001234",
    bank_name: "STP",
    beneficiary_name: "STRIPE/TRADING PLATFORM"
  }
}
```

### Webhook Handler

```typescript
@Post('/webhooks/stripe/spei')
async handleSpeiWebhook(
  @Body() payload: StripeWebhookPayload,
  @Headers('stripe-signature') signature: string
) {
  // 1. Validar firma
  const event = stripe.webhooks.constructEvent(
    payload,
    signature,
    process.env.STRIPE_SPEI_WEBHOOK_SECRET
  );

  // 2. Procesar segun tipo de evento
  switch (event.type) {
    case 'source.chargeable':
      await this.processSpeiDeposit(event.data.object);
      break;
    case 'source.failed':
      await this.handleSpeiFailure(event.data.object);
      break;
  }

  return { received: true };
}
```

---

## API Endpoints

### Obtener CLABE

```yaml
GET /api/v1/spei/clabe:
  description: Obtener CLABE virtual del usuario
  response:
    clabe: string
    bankName: string
    beneficiaryName: string
    limits:
      min: number
      max: number
      dailyMax: number
    exchangeRate:
      rate: number
      validUntil: string
```

### Historial de Depositos SPEI

```yaml
GET /api/v1/spei/deposits:
  description: Listar depositos SPEI del usuario
  query:
    page: number
    limit: number
    status: string
    dateFrom: string
    dateTo: string
  response:
    data:
      - id: string
        status: string
        montoMxn: number
        montoUsd: number
        tipoCambio: number
        fechaOperacion: string
        nombreOrdenante: string
    pagination:
      total: number
      page: number
      pages: number
```

### Tipo de Cambio Actual

```yaml
GET /api/v1/spei/exchange-rate:
  description: Obtener tipo de cambio MXN/USD actual
  response:
    rate: number
    spread: number
    effectiveRate: number
    validUntil: string
    source: string
```

---

## Seguridad

### Validacion de Webhooks

```typescript
function validateStripeSpeiSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expectedSig}`)
  );
}
```

### Prevencion de Fraude

- Monitorear depositos de misma CLABE origen (lavado)
- Alerta si deposito > $100,000 MXN
- Verificar RFC del ordenante vs usuario registrado
- Bloquear cuenta si patron sospechoso

### Idempotencia

- Usar `referenciaNumerica` + `fechaOperacion` como clave unica
- Ignorar webhooks duplicados
- Registrar todos los intentos para auditoria

---

## Manejo de Errores

| Error | Codigo | Mensaje Usuario |
|-------|--------|-----------------|
| CLABE no encontrada | 404 | Error interno. Contacta soporte. |
| Monto bajo limite | 400 | El monto minimo de deposito es $100 MXN |
| Monto sobre limite | 400 | El monto maximo por transferencia es $500,000 MXN |
| Limite diario excedido | 400 | Has excedido el limite diario de $1,000,000 MXN |
| Error de conversion | 500 | Error procesando tipo de cambio. Intenta mas tarde. |
| Proveedor no disponible | 503 | Sistema SPEI temporalmente no disponible |

---

## Webhooks Recibidos

| Evento | Accion |
|--------|--------|
| `source.chargeable` | Crear spei_deposit, procesar conversion |
| `source.failed` | Marcar como FAILED, notificar usuario |
| `source.canceled` | Marcar como REJECTED |

---

## Configuracion Requerida

```env
# Stripe Mexico
STRIPE_MX_SECRET_KEY=sk_xxx
STRIPE_SPEI_WEBHOOK_SECRET=whsec_xxx

# Conekta (alternativo)
CONEKTA_API_KEY=xxx
CONEKTA_WEBHOOK_SECRET=xxx

# Tipo de Cambio
EXCHANGE_RATE_API_URL=https://api.banxico.org.mx/SieAPIRest/service/v1/series/SF43718/datos/oportuno
EXCHANGE_RATE_BANXICO_TOKEN=xxx
EXCHANGE_RATE_CACHE_TTL_MINUTES=15
EXCHANGE_RATE_SPREAD=0.005

# Limites SPEI
SPEI_DEPOSIT_MIN_MXN=100
SPEI_DEPOSIT_MAX_MXN=500000
SPEI_DEPOSIT_MAX_DAILY_MXN=1000000
```

---

## Metricas de Negocio

### KPIs a Rastrear

- **Depositos SPEI/dia:** Numero de depositos procesados
- **Volumen MXN/dia:** Total depositado en MXN
- **Volumen USD/dia:** Total convertido a USD
- **Ticket promedio:** Monto promedio por deposito
- **Tiempo procesamiento:** Promedio entre recepcion y acreditacion
- **Tasa de error:** % de depositos fallidos/rechazados

---

## Criterios de Aceptacion

- [ ] Usuario obtiene CLABE unica de 18 digitos
- [ ] CLABE se muestra con opcion de copiar
- [ ] Depositos se detectan via webhook del agregador
- [ ] Conversion MXN a USD aplica tipo de cambio correcto
- [ ] Spread de 0.5% se aplica correctamente
- [ ] Wallet se acredita en USD automaticamente
- [ ] Push notification se envia al confirmar deposito
- [ ] Email de confirmacion incluye todos los detalles
- [ ] Depositos duplicados se manejan correctamente (idempotencia)
- [ ] Limites de monto se validan correctamente
- [ ] Historial de depositos SPEI es visible en UI
- [ ] Tipo de cambio se actualiza cada 15 minutos

---

## Especificacion Tecnica Relacionada

- [ET-PAY-008: SPEI Integration](../especificaciones/ET-PAY-008-spei.md)

## Historias de Usuario Relacionadas

- [US-PAY-025: Obtener CLABE](../historias-usuario/US-PAY-025-obtener-clabe.md)
- [US-PAY-026: Depositar via SPEI](../historias-usuario/US-PAY-026-depositar-spei.md)
- [US-PAY-027: Ver Historial SPEI](../historias-usuario/US-PAY-027-historial-spei.md)
