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>
648 lines
20 KiB
Markdown
648 lines
20 KiB
Markdown
---
|
|
id: "RF-PAY-009"
|
|
title: "Sistema de Transferencias P2P"
|
|
type: "Requirement"
|
|
status: "Draft"
|
|
priority: "Media"
|
|
epic: "OQI-005"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2026-01-04"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# RF-PAY-009: Sistema de Transferencias P2P
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2026-01-04
|
|
**Estado:** Draft
|
|
**Prioridad:** P2 (Media)
|
|
**Story Points:** 5
|
|
**Epica:** [OQI-005](../_MAP.md)
|
|
|
|
---
|
|
|
|
## Descripcion
|
|
|
|
El sistema debe permitir a los usuarios realizar transferencias de fondos peer-to-peer (P2P) entre wallets internos de la plataforma, de manera instantanea y sin comisiones. Los usuarios podran enviar dinero a otros usuarios identificandolos por email, username o wallet ID.
|
|
|
|
---
|
|
|
|
## Objetivo de Negocio
|
|
|
|
- Fomentar comunidad activa y engagement entre usuarios
|
|
- Facilitar pagos entre traders (apuestas amistosas, compartir senales)
|
|
- Reducir friccion para onboarding de nuevos usuarios (amigos invitan)
|
|
- Retener fondos dentro del ecosistema (no salen a bancos)
|
|
- Habilitar futuros marketplaces internos (venta de cursos, senales)
|
|
|
|
---
|
|
|
|
## Casos de Uso
|
|
|
|
1. **Envio por Email:** Usuario A envia $50 USD a usuario B usando su email
|
|
2. **Envio por Username:** Usuario A envia $100 USD a @trader_pro
|
|
3. **Envio por Wallet ID:** Usuario A pega wallet ID y envia $25 USD
|
|
4. **Con Mensaje:** Usuario A envia $10 USD con nota "Para el cafe de ayer"
|
|
5. **Recepcion:** Usuario B recibe notificacion y ve fondos en wallet
|
|
6. **Historial:** Usuario revisa todas las transferencias enviadas/recibidas
|
|
|
|
---
|
|
|
|
## Requisitos Funcionales
|
|
|
|
### RF-PAY-009.1: Identificacion de Destinatario
|
|
|
|
**DEBE:**
|
|
1. Buscar usuario por email exacto
|
|
2. Buscar usuario por username (con o sin @)
|
|
3. Buscar usuario por wallet ID (UUID corto de 8 caracteres)
|
|
4. Mostrar preview del destinatario antes de confirmar
|
|
5. Validar que destinatario tiene cuenta activa
|
|
|
|
**Preview de destinatario:**
|
|
```
|
|
Enviar a:
|
|
+---------------------------+
|
|
| @trader_pro |
|
|
| Juan P****z | (nombre parcialmente oculto)
|
|
| Miembro desde 2024 |
|
|
| [x] Cuenta verificada |
|
|
+---------------------------+
|
|
```
|
|
|
|
### RF-PAY-009.2: Creacion de Transferencia
|
|
|
|
**DEBE:**
|
|
1. Validar saldo suficiente en wallet del remitente
|
|
2. Validar monto dentro de limites ($1 - $5,000)
|
|
3. Validar limite diario acumulado ($10,000)
|
|
4. Permitir mensaje opcional (max 140 caracteres)
|
|
5. Requerir 2FA para montos > $500 USD
|
|
|
|
**Modelo de datos:**
|
|
```typescript
|
|
@Entity({ name: 'p2p_transfers', schema: 'billing' })
|
|
class P2PTransfer {
|
|
id: string; // UUID
|
|
shortId: string; // ID corto 8 chars para referencia
|
|
senderId: string; // FK a users
|
|
senderWalletId: string; // FK a wallets
|
|
receiverId: string; // FK a users
|
|
receiverWalletId: string; // FK a wallets
|
|
amount: Decimal; // Monto en USD
|
|
message?: string; // Mensaje opcional (140 chars max)
|
|
status: P2PTransferStatus; // pending, completed, failed, reversed
|
|
requires2FA: boolean; // true si amount > 500
|
|
verified2FA: boolean; // true si 2FA completado
|
|
createdAt: Date;
|
|
completedAt?: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
enum P2PTransferStatus {
|
|
PENDING = 'pending', // Esperando 2FA
|
|
COMPLETED = 'completed', // Transferido exitosamente
|
|
FAILED = 'failed', // Error en proceso
|
|
REVERSED = 'reversed' // Revertido por admin
|
|
}
|
|
```
|
|
|
|
### RF-PAY-009.3: Procesamiento de Transferencia
|
|
|
|
**DEBE:**
|
|
1. Debitar wallet del remitente atomicamente
|
|
2. Acreditar wallet del destinatario atomicamente
|
|
3. Crear registros en `wallet_transactions` para ambos
|
|
4. Marcar transferencia como `completed`
|
|
5. Emitir eventos para notificaciones
|
|
|
|
**Transaccion atomica:**
|
|
```typescript
|
|
await db.transaction(async (tx) => {
|
|
// 1. Lock wallets en orden consistente (evitar deadlock)
|
|
const [senderWallet, receiverWallet] = await Promise.all([
|
|
tx.wallet.findOne({ id: senderWalletId }, { lock: true }),
|
|
tx.wallet.findOne({ id: receiverWalletId }, { lock: true })
|
|
]);
|
|
|
|
// 2. Validar saldo
|
|
if (senderWallet.balance < amount) {
|
|
throw new InsufficientFundsError();
|
|
}
|
|
|
|
// 3. Debitar remitente
|
|
await tx.walletTransaction.create({
|
|
walletId: senderWallet.id,
|
|
type: 'debit',
|
|
amount: -amount,
|
|
reference: transferId,
|
|
description: `Transferencia P2P a ${receiverUsername}`
|
|
});
|
|
await tx.wallet.decrement(senderWallet.id, 'balance', amount);
|
|
|
|
// 4. Acreditar destinatario
|
|
await tx.walletTransaction.create({
|
|
walletId: receiverWallet.id,
|
|
type: 'credit',
|
|
amount: amount,
|
|
reference: transferId,
|
|
description: `Transferencia P2P de ${senderUsername}`
|
|
});
|
|
await tx.wallet.increment(receiverWallet.id, 'balance', amount);
|
|
|
|
// 5. Actualizar estado
|
|
await tx.p2pTransfer.update(transferId, {
|
|
status: 'completed',
|
|
completedAt: new Date()
|
|
});
|
|
});
|
|
```
|
|
|
|
### RF-PAY-009.4: Verificacion 2FA
|
|
|
|
**DEBE:**
|
|
1. Requerir 2FA para transferencias > $500 USD
|
|
2. Soportar TOTP (Google Authenticator)
|
|
3. Soportar SMS como fallback
|
|
4. Codigo valido por 5 minutos
|
|
5. Maximo 3 intentos fallidos
|
|
|
|
**Flujo 2FA:**
|
|
```
|
|
1. Usuario crea transferencia de $600
|
|
2. Backend crea transfer con status: PENDING, requires2FA: true
|
|
3. Frontend muestra modal de 2FA
|
|
4. Usuario ingresa codigo
|
|
5. Backend valida codigo
|
|
6. Si valido: procesar transferencia
|
|
7. Si invalido: incrementar intentos, bloquear si > 3
|
|
```
|
|
|
|
### RF-PAY-009.5: Notificaciones
|
|
|
|
**DEBE:**
|
|
1. Push notification instantanea al destinatario
|
|
2. Email de confirmacion a ambas partes
|
|
3. In-app notification en historial
|
|
4. Mostrar mensaje del remitente si existe
|
|
|
|
**Contenido de notificacion (destinatario):**
|
|
```
|
|
@juan_trader te envio $50.00 USD
|
|
|
|
"Para el cafe de ayer"
|
|
|
|
Tu nuevo saldo: $150.00 USD
|
|
```
|
|
|
|
**Contenido de notificacion (remitente):**
|
|
```
|
|
Transferencia exitosa
|
|
|
|
Enviaste $50.00 USD a @trader_pro
|
|
Referencia: ABC12345
|
|
Tu nuevo saldo: $200.00 USD
|
|
```
|
|
|
|
### RF-PAY-009.6: Historial de Transferencias
|
|
|
|
**DEBE:**
|
|
1. Mostrar transferencias enviadas y recibidas
|
|
2. Filtrar por tipo (enviadas/recibidas/todas)
|
|
3. Filtrar por rango de fechas
|
|
4. Buscar por destinatario/remitente
|
|
5. Mostrar mensaje asociado
|
|
|
|
**Vista de historial:**
|
|
```
|
|
+--------------------------------------------+
|
|
| Transferencias P2P |
|
|
+--------------------------------------------+
|
|
| [Todas] [Enviadas] [Recibidas] |
|
|
+--------------------------------------------+
|
|
| 04 Ene 2026 @trader_pro |
|
|
| Enviado -$50.00 USD "Para el cafe" |
|
|
+--------------------------------------------+
|
|
| 03 Ene 2026 @maria_fx |
|
|
| Recibido +$100.00 USD "Gracias!" |
|
|
+--------------------------------------------+
|
|
| 02 Ene 2026 @carlos_btc |
|
|
| Enviado -$25.00 USD |
|
|
+--------------------------------------------+
|
|
```
|
|
|
|
---
|
|
|
|
## Flujo de Transferencia P2P
|
|
|
|
```
|
|
+-------------+ +-------------+ +-------------+ +-------------+
|
|
| Remitente | | Frontend | | Backend | | Destinatario|
|
|
+------+------+ +------+------+ +------+------+ +------+------+
|
|
| | | |
|
|
| Click "Enviar | | |
|
|
| Dinero" | | |
|
|
|------------------>| | |
|
|
| | | |
|
|
| Ingresa: | | |
|
|
| - @trader_pro | | |
|
|
| - $50 USD | | |
|
|
| - "Para el cafe" | | |
|
|
|------------------>| | |
|
|
| | | |
|
|
| | GET /users/lookup | |
|
|
| | ?q=@trader_pro | |
|
|
| |------------------>| |
|
|
| |<------------------| |
|
|
| | { username, | |
|
|
| | displayName, | |
|
|
| | walletId } | |
|
|
| | | |
|
|
|<------------------| | |
|
|
| Preview: | | |
|
|
| "Juan P****z" | | |
|
|
| | | |
|
|
| Confirma | | |
|
|
|------------------>| | |
|
|
| | | |
|
|
| | POST /p2p/transfer| |
|
|
| | { receiverId, | |
|
|
| | amount: 50, | |
|
|
| | message } | |
|
|
| |------------------>| |
|
|
| | | |
|
|
| | | 1. Validate user |
|
|
| | | 2. Validate |
|
|
| | | balance |
|
|
| | | 3. Validate |
|
|
| | | limits |
|
|
| | | 4. Check 2FA req |
|
|
| | | ($50 < $500) |
|
|
| | | NO 2FA needed |
|
|
| | | |
|
|
| | | BEGIN TX |
|
|
| | | - Debit sender |
|
|
| | | - Credit receiver |
|
|
| | | - Create records |
|
|
| | | COMMIT TX |
|
|
| | | |
|
|
| |<------------------| |
|
|
| | { status: | |
|
|
| | "completed", | |
|
|
| | transferId } | |
|
|
| | | |
|
|
|<------------------| |------------------>|
|
|
| "Enviado!" | | Push: "Recibiste |
|
|
| | | $50 de @juan" |
|
|
| | | |
|
|
|<------------------| |------------------>|
|
|
| Email: | | Email: |
|
|
| Confirmacion | | Confirmacion |
|
|
| | | |
|
|
```
|
|
|
|
---
|
|
|
|
## Flujo con 2FA (Monto > $500)
|
|
|
|
```
|
|
+-------------+ +-------------+ +-------------+
|
|
| Remitente | | Frontend | | Backend |
|
|
+------+------+ +------+------+ +------+------+
|
|
| | |
|
|
| Enviar $600 a | |
|
|
| @trader_pro | |
|
|
|------------------>| |
|
|
| | |
|
|
| | POST /p2p/transfer|
|
|
| | { amount: 600 } |
|
|
| |------------------>|
|
|
| | |
|
|
| | | Detecta: amount >
|
|
| | | $500, requiere 2FA
|
|
| | |
|
|
| | | Crear transfer:
|
|
| | | status: PENDING
|
|
| | | requires2FA: true
|
|
| | |
|
|
| |<------------------|
|
|
| | { transferId, |
|
|
| | requires2FA: |
|
|
| | true } |
|
|
| | |
|
|
|<------------------| |
|
|
| Modal: "Ingresa | |
|
|
| codigo 2FA" | |
|
|
| | |
|
|
| Ingresa: 123456 | |
|
|
|------------------>| |
|
|
| | |
|
|
| | POST /p2p/transfer|
|
|
| | /{id}/verify |
|
|
| | { code: 123456 } |
|
|
| |------------------>|
|
|
| | |
|
|
| | | Validar TOTP
|
|
| | | Procesar transfer
|
|
| | |
|
|
| |<------------------|
|
|
| | { status: |
|
|
| | "completed" } |
|
|
| | |
|
|
|<------------------| |
|
|
| "Enviado!" | |
|
|
| | |
|
|
```
|
|
|
|
---
|
|
|
|
## Reglas de Negocio
|
|
|
|
### RN-001: Limites de Transferencia
|
|
|
|
| Limite | Valor |
|
|
|--------|-------|
|
|
| Monto minimo | $1.00 USD |
|
|
| Monto maximo por transferencia | $5,000.00 USD |
|
|
| Limite diario acumulado | $10,000.00 USD |
|
|
| Limite mensual | Sin limite |
|
|
|
|
### RN-002: Comisiones
|
|
|
|
| Concepto | Fee |
|
|
|----------|-----|
|
|
| Transferencia P2P | **0% (gratis)** |
|
|
| Sin fees ocultos | - |
|
|
|
|
### RN-003: Seguridad 2FA
|
|
|
|
| Monto | Requiere 2FA |
|
|
|-------|--------------|
|
|
| $0 - $500 | No |
|
|
| $500.01+ | Si |
|
|
|
|
### RN-004: Restricciones
|
|
|
|
- No se puede enviar a si mismo
|
|
- Ambas cuentas deben estar activas
|
|
- No se permite enviar a cuentas suspendidas
|
|
- Wallet del remitente debe tener saldo suficiente
|
|
- No se pueden revertir transferencias (excepto admin)
|
|
|
|
### RN-005: Mensajes
|
|
|
|
- Mensaje opcional
|
|
- Maximo 140 caracteres
|
|
- No se permiten URLs
|
|
- Filtro de palabras prohibidas
|
|
- Mensaje visible para ambas partes
|
|
|
|
---
|
|
|
|
## API Endpoints
|
|
|
|
### Buscar Usuario
|
|
|
|
```yaml
|
|
GET /api/v1/users/lookup:
|
|
description: Buscar usuario para transferencia P2P
|
|
query:
|
|
q: string (email, username o walletId)
|
|
response:
|
|
id: string
|
|
username: string
|
|
displayName: string (parcialmente oculto)
|
|
walletId: string
|
|
verified: boolean
|
|
memberSince: string
|
|
```
|
|
|
|
### Crear Transferencia
|
|
|
|
```yaml
|
|
POST /api/v1/p2p/transfers:
|
|
description: Crear transferencia P2P
|
|
body:
|
|
receiverId: string
|
|
amount: number
|
|
message?: string
|
|
response:
|
|
transferId: string
|
|
shortId: string
|
|
status: string
|
|
requires2FA: boolean
|
|
amount: number
|
|
receiver:
|
|
username: string
|
|
displayName: string
|
|
```
|
|
|
|
### Verificar 2FA
|
|
|
|
```yaml
|
|
POST /api/v1/p2p/transfers/{id}/verify:
|
|
description: Verificar 2FA para transferencia pendiente
|
|
body:
|
|
code: string (6 digitos)
|
|
response:
|
|
transferId: string
|
|
status: "completed"
|
|
completedAt: string
|
|
```
|
|
|
|
### Historial de Transferencias
|
|
|
|
```yaml
|
|
GET /api/v1/p2p/transfers:
|
|
description: Listar transferencias P2P del usuario
|
|
query:
|
|
type: string (sent | received | all)
|
|
page: number
|
|
limit: number
|
|
dateFrom: string
|
|
dateTo: string
|
|
response:
|
|
data:
|
|
- id: string
|
|
shortId: string
|
|
type: string (sent | received)
|
|
amount: number
|
|
otherUser:
|
|
username: string
|
|
displayName: string
|
|
message: string
|
|
status: string
|
|
createdAt: string
|
|
pagination:
|
|
total: number
|
|
page: number
|
|
pages: number
|
|
```
|
|
|
|
### Detalle de Transferencia
|
|
|
|
```yaml
|
|
GET /api/v1/p2p/transfers/{id}:
|
|
description: Obtener detalle de una transferencia
|
|
response:
|
|
id: string
|
|
shortId: string
|
|
type: string
|
|
amount: number
|
|
message: string
|
|
status: string
|
|
sender:
|
|
username: string
|
|
displayName: string
|
|
receiver:
|
|
username: string
|
|
displayName: string
|
|
createdAt: string
|
|
completedAt: string
|
|
```
|
|
|
|
---
|
|
|
|
## Seguridad
|
|
|
|
### Prevencion de Fraude
|
|
|
|
- Rate limit: maximo 10 transferencias por hora
|
|
- Alerta si multiples transferencias al mismo destinatario
|
|
- Bloqueo temporal si patron sospechoso
|
|
- Monitoreo de transferencias circulares (A→B→A)
|
|
- Limite diario reduce riesgo de robo de cuenta
|
|
|
|
### Validaciones
|
|
|
|
```typescript
|
|
async function validateP2PTransfer(
|
|
senderId: string,
|
|
receiverId: string,
|
|
amount: number
|
|
): Promise<ValidationResult> {
|
|
// 1. No enviar a si mismo
|
|
if (senderId === receiverId) {
|
|
throw new Error('No puedes enviarte dinero a ti mismo');
|
|
}
|
|
|
|
// 2. Destinatario existe y activo
|
|
const receiver = await userRepo.findOne({ id: receiverId });
|
|
if (!receiver || receiver.status !== 'active') {
|
|
throw new Error('Usuario no encontrado o inactivo');
|
|
}
|
|
|
|
// 3. Saldo suficiente
|
|
const wallet = await walletRepo.findOne({ userId: senderId });
|
|
if (wallet.balance < amount) {
|
|
throw new Error(`Saldo insuficiente. Disponible: $${wallet.balance}`);
|
|
}
|
|
|
|
// 4. Dentro de limites
|
|
if (amount < 1 || amount > 5000) {
|
|
throw new Error('Monto debe ser entre $1 y $5,000 USD');
|
|
}
|
|
|
|
// 5. Limite diario
|
|
const todayTotal = await getDailyTransferTotal(senderId);
|
|
if (todayTotal + amount > 10000) {
|
|
throw new Error(`Excedes limite diario. Disponible hoy: $${10000 - todayTotal}`);
|
|
}
|
|
|
|
return { valid: true, requires2FA: amount > 500 };
|
|
}
|
|
```
|
|
|
|
### Auditoria
|
|
|
|
- Log de todas las transferencias
|
|
- Guardar IP y User-Agent del remitente
|
|
- Registro de intentos fallidos de 2FA
|
|
- Alertas a seguridad por patrones anomalos
|
|
|
|
---
|
|
|
|
## Manejo de Errores
|
|
|
|
| Error | Codigo | Mensaje Usuario |
|
|
|-------|--------|-----------------|
|
|
| Usuario no encontrado | 404 | No encontramos un usuario con ese email/username |
|
|
| Cuenta inactiva | 403 | Este usuario no puede recibir transferencias |
|
|
| Saldo insuficiente | 400 | Saldo insuficiente. Tienes $X disponible |
|
|
| Monto muy bajo | 400 | El monto minimo es $1 USD |
|
|
| Monto muy alto | 400 | El monto maximo por transferencia es $5,000 USD |
|
|
| Limite diario | 400 | Has alcanzado tu limite diario de $10,000 |
|
|
| Auto-transferencia | 400 | No puedes enviarte dinero a ti mismo |
|
|
| 2FA invalido | 401 | Codigo 2FA incorrecto. Te quedan X intentos |
|
|
| 2FA expirado | 401 | Codigo expirado. Solicita uno nuevo |
|
|
| Rate limit | 429 | Demasiadas transferencias. Espera 1 hora |
|
|
|
|
---
|
|
|
|
## Configuracion Requerida
|
|
|
|
```env
|
|
# Limites P2P
|
|
P2P_TRANSFER_MIN_USD=1
|
|
P2P_TRANSFER_MAX_USD=5000
|
|
P2P_TRANSFER_DAILY_LIMIT_USD=10000
|
|
P2P_2FA_THRESHOLD_USD=500
|
|
|
|
# Rate Limits
|
|
P2P_MAX_TRANSFERS_PER_HOUR=10
|
|
P2P_MAX_TRANSFERS_PER_DAY=50
|
|
|
|
# 2FA
|
|
P2P_2FA_CODE_EXPIRY_MINUTES=5
|
|
P2P_2FA_MAX_ATTEMPTS=3
|
|
|
|
# Mensajes
|
|
P2P_MESSAGE_MAX_LENGTH=140
|
|
```
|
|
|
|
---
|
|
|
|
## Metricas de Negocio
|
|
|
|
### KPIs a Rastrear
|
|
|
|
- **Transferencias P2P/dia:** Numero de transferencias completadas
|
|
- **Volumen P2P/dia:** Total transferido en USD
|
|
- **Usuarios activos P2P:** Usuarios unicos que envian/reciben
|
|
- **Ticket promedio:** Monto promedio por transferencia
|
|
- **Ratio envio/recepcion:** Balance de actividad
|
|
- **Tasa de conversion 2FA:** % de transferencias >$500 completadas
|
|
|
|
---
|
|
|
|
## Criterios de Aceptacion
|
|
|
|
- [ ] Usuario puede buscar destinatario por email
|
|
- [ ] Usuario puede buscar destinatario por username
|
|
- [ ] Usuario puede buscar destinatario por wallet ID
|
|
- [ ] Preview muestra nombre parcialmente oculto
|
|
- [ ] Transferencia de $50 se procesa sin 2FA
|
|
- [ ] Transferencia de $600 requiere 2FA
|
|
- [ ] Saldo se actualiza instantaneamente en ambos wallets
|
|
- [ ] Mensaje opcional se muestra a destinatario
|
|
- [ ] Push notification llega al destinatario
|
|
- [ ] Email de confirmacion llega a ambas partes
|
|
- [ ] Historial muestra transferencias enviadas y recibidas
|
|
- [ ] Limites de monto se validan correctamente
|
|
- [ ] Limite diario se calcula correctamente
|
|
- [ ] No se permite enviar a si mismo
|
|
- [ ] No se permite enviar a cuenta suspendida
|
|
|
|
---
|
|
|
|
## Especificacion Tecnica Relacionada
|
|
|
|
- [ET-PAY-009: P2P Transfers](../especificaciones/ET-PAY-009-p2p.md)
|
|
|
|
## Historias de Usuario Relacionadas
|
|
|
|
- [US-PAY-030: Enviar Dinero P2P](../historias-usuario/US-PAY-030-enviar-p2p.md)
|
|
- [US-PAY-031: Recibir Dinero P2P](../historias-usuario/US-PAY-031-recibir-p2p.md)
|
|
- [US-PAY-032: Ver Historial P2P](../historias-usuario/US-PAY-032-historial-p2p.md)
|