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>
20 KiB
20 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| RF-PAY-009 | Sistema de Transferencias P2P | Requirement | Draft | Media | OQI-005 | trading-platform | 1.0.0 | 2026-01-04 | 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
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
- Envio por Email: Usuario A envia $50 USD a usuario B usando su email
- Envio por Username: Usuario A envia $100 USD a @trader_pro
- Envio por Wallet ID: Usuario A pega wallet ID y envia $25 USD
- Con Mensaje: Usuario A envia $10 USD con nota "Para el cafe de ayer"
- Recepcion: Usuario B recibe notificacion y ve fondos en wallet
- Historial: Usuario revisa todas las transferencias enviadas/recibidas
Requisitos Funcionales
RF-PAY-009.1: Identificacion de Destinatario
DEBE:
- Buscar usuario por email exacto
- Buscar usuario por username (con o sin @)
- Buscar usuario por wallet ID (UUID corto de 8 caracteres)
- Mostrar preview del destinatario antes de confirmar
- 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:
- Validar saldo suficiente en wallet del remitente
- Validar monto dentro de limites ($1 - $5,000)
- Validar limite diario acumulado ($10,000)
- Permitir mensaje opcional (max 140 caracteres)
- Requerir 2FA para montos > $500 USD
Modelo de datos:
@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:
- Debitar wallet del remitente atomicamente
- Acreditar wallet del destinatario atomicamente
- Crear registros en
wallet_transactionspara ambos - Marcar transferencia como
completed - Emitir eventos para notificaciones
Transaccion atomica:
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:
- Requerir 2FA para transferencias > $500 USD
- Soportar TOTP (Google Authenticator)
- Soportar SMS como fallback
- Codigo valido por 5 minutos
- 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:
- Push notification instantanea al destinatario
- Email de confirmacion a ambas partes
- In-app notification en historial
- 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:
- Mostrar transferencias enviadas y recibidas
- Filtrar por tipo (enviadas/recibidas/todas)
- Filtrar por rango de fechas
- Buscar por destinatario/remitente
- 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
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
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
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
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
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
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
# 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