Structure: - control-plane/: Registries, SIMCO directives, CI/CD templates - projects/: Gamilit, ERP-Suite, Trading-Platform, Betting-Analytics - shared/: Libs catalog, knowledge-base Key features: - Centralized port, domain, database, and service registries - 23 SIMCO directives + 6 fundamental principles - NEXUS agent profiles with delegation rules - Validation scripts for workspace integrity - Dockerfiles for all services - Path aliases for quick reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
Comunicación WebSocket
Versión: 1.0.0 Origen: projects/gamilit, projects/trading-platform Estado: Producción Última actualización: 2025-12-08
Descripción
Sistema de comunicación en tiempo real via Socket.IO:
- Conexiones WebSocket autenticadas con JWT
- Rooms por usuario para mensajes privados
- Broadcasting para eventos globales
- Tracking de usuarios conectados
- Multi-dispositivo (un usuario, múltiples sockets)
- Integración con sistema de notificaciones
Características
| Característica | Descripción |
|---|---|
| Autenticación | JWT en handshake |
| Rooms | Por usuario (user:{userId}) |
| Multi-socket | Un usuario puede tener múltiples conexiones |
| Broadcast | Eventos a todos los conectados |
| Typed events | Enum de eventos tipados |
| CORS | Configuración flexible |
| Transports | WebSocket + polling fallback |
Stack Tecnológico
backend:
framework: NestJS
library: Socket.IO
auth: JWT
frontend:
library: socket.io-client
packages:
- "@nestjs/websockets"
- "@nestjs/platform-socket.io"
- "socket.io"
- "socket.io-client"
Dependencias NPM
{
"@nestjs/websockets": "^10.x",
"@nestjs/platform-socket.io": "^10.x",
"socket.io": "^4.x",
"@nestjs/jwt": "^10.x"
}
Frontend:
{
"socket.io-client": "^4.x"
}
Tablas Requeridas
No requiere tablas adicionales. Usa autenticación existente (JWT).
Estructura del Módulo
websocket/
├── websocket.module.ts
├── gateways/
│ └── notifications.gateway.ts # Gateway principal
├── services/
│ └── websocket.service.ts # API para otros módulos
├── guards/
│ └── ws-jwt.guard.ts # Autenticación JWT
└── types/
└── websocket.types.ts # Eventos y tipos
Arquitectura
┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Frontend │
│ (User A) │ │ (User A) │
│ Device 1 │ │ Device 2 │
└────────┬────────┘ └────────┬────────┘
│ │
│ WebSocket │ WebSocket
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ Socket.IO Server │
│ ┌───────────────────────────────────────┐ │
│ │ Room: user:user-a-uuid │ │
│ │ - socket-id-1 │ │
│ │ - socket-id-2 │ │
│ └───────────────────────────────────────┘ │
│ │
│ userSockets Map: │
│ user-a-uuid → Set(socket-id-1, socket-id-2)│
└─────────────────────────────────────────────┘
Eventos Disponibles
Eventos del Servidor (Server → Client)
| Evento | Descripción | Payload |
|---|---|---|
authenticated |
Conexión autenticada | { success, userId, email } |
error |
Error en operación | { message } |
notification:new |
Nueva notificación | { notification, timestamp } |
notification:read |
Notificación leída | { notificationId, success } |
notification:deleted |
Notificación eliminada | { notificationId } |
notification:unread_count |
Contador actualizado | { unreadCount } |
achievement:unlocked |
Logro desbloqueado | { achievementId, title, ... } |
rank:updated |
Cambio de rango | { newRank, oldRank } |
xp:gained |
XP ganado | { amount, source, totalXp } |
leaderboard:updated |
Leaderboard actualizado | { leaderboard[] } |
Eventos del Cliente (Client → Server)
| Evento | Descripción | Payload |
|---|---|---|
notification:mark_read |
Marcar como leída | { notificationId } |
Uso Rápido
1. Emitir desde otro servicio
import { WebSocketService } from '@/modules/websocket';
@Injectable()
export class NotificationService {
constructor(private readonly wsService: WebSocketService) {}
async sendNotification(userId: string, notification: any) {
// Guardar en DB...
// Emitir en tiempo real
this.wsService.emitNotificationToUser(userId, notification);
}
}
2. Frontend - Conectar
import { io, Socket } from 'socket.io-client';
const socket: Socket = io('http://localhost:3000', {
path: '/socket.io/',
transports: ['websocket', 'polling'],
auth: {
token: localStorage.getItem('accessToken'),
},
});
// Conexión establecida
socket.on('authenticated', (data) => {
console.log('Conectado:', data.email);
});
// Escuchar notificaciones
socket.on('notification:new', (data) => {
showToast(data.notification.title);
updateNotificationBadge();
});
// Error de autenticación
socket.on('error', (data) => {
console.error('Error:', data.message);
});
// Desconexión
socket.on('disconnect', (reason) => {
console.log('Desconectado:', reason);
});
3. Frontend - Enviar eventos
// Marcar notificación como leída
socket.emit('notification:mark_read', { notificationId: 'uuid' });
4. Verificar si usuario está conectado
// En cualquier servicio
const isOnline = this.wsService.isUserConnected(userId);
if (isOnline) {
// Enviar por WebSocket (instantáneo)
this.wsService.emitNotificationToUser(userId, notification);
} else {
// Enviar por email o push (asíncrono)
await this.emailService.send(userId, notification);
}
Flujo de Autenticación
1. Cliente conecta con token JWT
│
▼
2. WsJwtGuard verifica token
│
├─► Token inválido → disconnect()
│
└─► Token válido
│
▼
3. Extraer userId, email, role del payload
│
▼
4. Adjuntar userData al socket
│
▼
5. Join room: user:{userId}
│
▼
6. Registrar en userSockets Map
│
▼
7. Emitir 'authenticated' al cliente
Patrones de Uso
Notificación a un usuario
// El usuario recibe en todos sus dispositivos conectados
this.wsService.emitNotificationToUser(userId, {
id: notification.id,
title: notification.title,
message: notification.message,
});
Notificación a múltiples usuarios
// Ejemplo: notificar a todos los miembros de un grupo
const memberIds = ['uuid1', 'uuid2', 'uuid3'];
this.wsService.emitNotificationToUsers(memberIds, {
type: 'group_message',
groupId: group.id,
message: 'Nuevo mensaje en el grupo',
});
Broadcast global
// Ejemplo: actualización del leaderboard
this.wsService.broadcastLeaderboardUpdate(newLeaderboard);
Eventos de gamificación
// Logro desbloqueado
this.wsService.emitAchievementUnlocked(userId, {
achievementId: 'ach-001',
title: 'Primer Login',
description: 'Has iniciado sesión por primera vez',
icon: '/icons/first-login.png',
});
// XP ganado
this.wsService.emitXpGained(userId, {
amount: 100,
source: 'daily_mission',
totalXp: 1500,
});
// Subida de rango
this.wsService.emitRankUpdated(userId, {
oldRank: 'Novato',
newRank: 'Aprendiz',
xpRequired: 2000,
});
Variables de Entorno
# WebSocket
WS_PORT=3000 # Puerto (mismo que HTTP)
WS_PATH=/socket.io/ # Path del endpoint
# CORS
CORS_ORIGIN=http://localhost:3000,http://localhost:5173
# JWT (compartido con auth)
JWT_SECRET=your-secret-key
Frontend React Hook
// hooks/useSocket.ts
import { useEffect, useRef, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
import { useAuth } from './useAuth';
export function useSocket() {
const { token } = useAuth();
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
if (!token) return;
socketRef.current = io(import.meta.env.VITE_API_URL, {
path: '/socket.io/',
transports: ['websocket', 'polling'],
auth: { token },
});
socketRef.current.on('connect', () => {
console.log('Socket connected');
});
socketRef.current.on('disconnect', (reason) => {
console.log('Socket disconnected:', reason);
});
return () => {
socketRef.current?.disconnect();
};
}, [token]);
const on = useCallback((event: string, handler: (data: any) => void) => {
socketRef.current?.on(event, handler);
return () => socketRef.current?.off(event, handler);
}, []);
const emit = useCallback((event: string, data: any) => {
socketRef.current?.emit(event, data);
}, []);
return { socket: socketRef.current, on, emit };
}
// Uso en componente
function NotificationBell() {
const { on } = useSocket();
const [unread, setUnread] = useState(0);
useEffect(() => {
return on('notification:unread_count', (data) => {
setUnread(data.unreadCount);
});
}, [on]);
return <Badge count={unread}><BellIcon /></Badge>;
}
Consideraciones de Escalabilidad
Múltiples instancias (Horizontal Scaling)
Para escalar horizontalmente con múltiples instancias del servidor:
// Usar Redis adapter para Socket.IO
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
Sticky Sessions
Con load balancer, configurar sticky sessions para que un cliente siempre llegue a la misma instancia:
upstream websocket {
ip_hash; # Sticky sessions por IP
server backend1:3000;
server backend2:3000;
}
Adaptaciones Necesarias
- Eventos: Definir eventos específicos de tu aplicación
- Rooms: Agregar rooms adicionales (grupos, chats, etc.)
- Auth: Ajustar extracción de datos del JWT
- Scaling: Configurar Redis adapter si múltiples instancias
- CORS: Ajustar orígenes permitidos
Referencias
Mantenido por: Sistema NEXUS Proyectos origen: Gamilit Platform, Trading Platform