trading-platform/docs/02-definicion-modulos/OQI-005-payments-stripe/requerimientos/RF-PAY-008-spei.md
rckrdmrd a7cca885f0 feat: Major platform documentation and architecture updates
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>
2026-01-07 05:33:35 -06:00

596 lines
19 KiB
Markdown

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