workspace-v1/shared/catalog/websocket/README.md
rckrdmrd cb4c0681d3 feat(workspace): Add new projects and update architecture
New projects created:
- michangarrito (marketplace mobile)
- template-saas (SaaS template)
- clinica-dental (dental ERP)
- clinica-veterinaria (veterinary ERP)

Architecture updates:
- Move catalog from core/ to shared/
- Add MCP servers structure and templates
- Add git management scripts
- Update SUBREPOSITORIOS.md with 15 new repos
- Update .gitignore for new projects

Repository infrastructure:
- 4 main repositories
- 11 subrepositorios
- Gitea remotes configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:43:28 -06:00

450 lines
11 KiB
Markdown

# 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