# 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

```yaml
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

```json
{
  "@nestjs/websockets": "^10.x",
  "@nestjs/platform-socket.io": "^10.x",
  "socket.io": "^4.x",
  "@nestjs/jwt": "^10.x"
}
```

Frontend:
```json
{
  "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

```typescript
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

```typescript
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

```typescript
// Marcar notificación como leída
socket.emit('notification:mark_read', { notificationId: 'uuid' });
```

### 4. Verificar si usuario está conectado

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// Ejemplo: actualización del leaderboard
this.wsService.broadcastLeaderboardUpdate(newLeaderboard);
```

### Eventos de gamificación

```typescript
// 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

```env
# 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

```typescript
// 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:

```typescript
// 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:

```nginx
upstream websocket {
    ip_hash;  # Sticky sessions por IP
    server backend1:3000;
    server backend2:3000;
}
```

---

## Adaptaciones Necesarias

1. **Eventos**: Definir eventos específicos de tu aplicación
2. **Rooms**: Agregar rooms adicionales (grupos, chats, etc.)
3. **Auth**: Ajustar extracción de datos del JWT
4. **Scaling**: Configurar Redis adapter si múltiples instancias
5. **CORS**: Ajustar orígenes permitidos

---

## Referencias

- [Socket.IO Documentation](https://socket.io/docs/v4/)
- [NestJS WebSockets](https://docs.nestjs.com/websockets/gateways)
- [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/)

---

**Mantenido por:** Sistema NEXUS
**Proyectos origen:** Gamilit Platform, Trading Platform
