Saltar al contenido principal

Shared Packages - Paquetes Compartidos

Los Shared Packages son el código reutilizable que todos los microservicios usan. En lugar de duplicar código, se centraliza aquí.


¿Por qué Shared Packages?

Sin paquetes compartidos (código duplicado):

auth-service/
└── guards/jwt-auth.guard.ts
user-service/
└── guards/jwt-auth.guard.ts ❌ Mismo código
wallet-service/
└── guards/jwt-auth.guard.ts ❌ Mismo código

Con paquetes compartidos:

packages/
└── guards/jwt-auth.guard.ts ✅ Una sola versión

auth-service/
└── usa → packages/guards
user-service/
└── usa → packages/guards
wallet-service/
└── usa → packages/guards

Beneficios:

  • ✅ Una sola fuente de verdad
  • ✅ Actualizaciones en un solo lugar
  • ✅ Consistencia entre servicios
  • ✅ Menos bugs (mismo código testeado)

Estructura Completa

packages/
├── analytics/ # Sistema de analytics y métricas
├── chat/ # Sistema de chat compartido
├── controllers/ # Controladores reutilizables
├── dtos/ # Data Transfer Objects
├── email/ # Servicio de email
├── examples/ # Ejemplos de uso
├── gateways/ # WebSocket gateways
├── guards/ # Guards de autenticación/autorización
├── health/ # Health checks
├── interceptors/ # Interceptores (analytics, audit)
├── jwt/ # Módulo JWT
├── logs/ # Sistema de logging
├── middleware/ # Middlewares
├── rate-limiting/ # Rate limiting avanzado
├── redis/ # Servicio de Redis
├── schemas/ # 30+ Mongoose schemas
├── services/ # Servicios compartidos
├── tasks/ # Tareas programadas
└── utils/ # Utilidades

🛡️ Guards (Seguridad)

Los Guards son la primera línea de defensa. Deciden si una request puede continuar o debe ser rechazada.

JWT Auth Guard

// Uso en cualquier servicio
@UseGuards(JwtAuthGuard)
@Get('profile')
async getProfile(@User() user) {
// Solo llega aquí si JWT es válido
return user;
}

Funcionamiento interno:

KYC Guard

Verifica que el usuario tenga KYC aprobado:

@UseGuards(JwtAuthGuard, KycGuard)
@Post('withdraw')
async withdraw(@User() user, @Body() dto: WithdrawDto) {
// Solo usuarios con KYC aprobado
}

Implementación:

@Injectable()
export class KycGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;

// Verificar KYC en DB
const kyc = await this.kycService.getStatus(user.id);

if (kyc.status !== 'approved') {
throw new ForbiddenException('KYC verification required');
}

// Verificar que no haya expirado
if (kyc.expiresAt && new Date() > kyc.expiresAt) {
throw new ForbiddenException('KYC expired');
}

return true;
}
}

PIN Guard

Verifica el PIN de 6 dígitos para operaciones sensibles:

@UseGuards(JwtAuthGuard, PinGuard)
@Post('send-transaction')
async sendTransaction(@User() user, @Body() dto: SendDto) {
// Requiere PIN correcto
}

Sistema de PIN:

  • PIN de 6 dígitos
  • Hash con bcrypt
  • Máx 5 intentos / 15 minutos
  • Bloqueo automático si se excede

Rate Limit Guard

Limita requests por IP/usuario:

@UseGuards(RateLimitGuard)
@Post('login')
async login(@Body() dto: LoginDto) {
// Máx X intentos por tiempo
}

Device Auth Guard

Verifica que el dispositivo esté registrado:

@UseGuards(JwtAuthGuard, DeviceAuthGuard)
@Post('sensitive-action')
async sensitiveAction(@Headers() headers, @User() user) {
// Solo dispositivos confiables
}

Headers requeridos:

X-Device-Id: fingerprint_hash
X-Device-Type: mobile|desktop
X-Device-Name: iPhone 12 Pro

Roles Guard

Verifica roles de usuario (admin, user, etc.):

@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN')
@Get('admin/users')
async getAllUsers() {
// Solo admins
}

📊 Analytics (Métricas y Estadísticas)

Sistema completo de analytics que rastrea TODO en el sistema.

Analytics Interceptor

Se ejecuta automáticamente en cada request:

@UseInterceptors(AnalyticsInterceptor)
export class UserController {
// Todos los endpoints registran analytics
}

Datos capturados:

{
endpoint: '/users/me',
method: 'GET',
userId: 'user_123',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
responseTime: 45, // ms
statusCode: 200,
timestamp: '2025-10-20T15:30:00Z'
}

Dashboard Analytics

Endpoint para obtener métricas:

GET /analytics/dashboard

Response:
{
totalUsers: 10000,
activeUsers24h: 2500,
totalTransactions: 50000,
volumeUSD: 5000000,
topEndpoints: [
{ endpoint: '/wallets/balance', calls: 15000 },
{ endpoint: '/auth/login', calls: 8000 }
],
errorRate: 0.02 // 2%
}

Endpoint Analytics

Métricas por endpoint específico:

GET /analytics/endpoints/users/me

Response:
{
endpoint: '/users/me',
totalCalls: 5000,
avgResponseTime: 42, // ms
p95ResponseTime: 120,
errorRate: 0.01,
callsByHour: [...]
}

💬 Chat System (Sistema de Chat)

Sistema de chat bidireccional entre usuarios y admins.

Chat Service

class ChatService {
async sendMessage(threadId: string, message: string, senderId: string) {
// 1. Guardar mensaje en DB
const msg = await this.chatRepository.create({
threadId,
senderId,
message,
timestamp: new Date()
});

// 2. Publicar en Redis Pub/Sub
await this.redis.publish(`chat:${threadId}`, {
type: 'new_message',
data: msg
});

// 3. WebSocket notificará a ambas partes
return msg;
}

async createThread(userId: string, subject: string) {
return await this.chatRepository.createThread({
userId,
subject,
status: 'open',
createdAt: new Date()
});
}

async assignThread(threadId: string, adminId: string) {
await this.chatRepository.update(threadId, {
assignedTo: adminId,
status: 'assigned'
});
}
}

Chat Gateway (WebSocket)

@WebSocketGateway({ namespace: '/chat' })
export class ChatGateway {
@SubscribeMessage('send_message')
async handleMessage(client: Socket, payload: any) {
const userId = client.data.userId;

await this.chatService.sendMessage(
payload.threadId,
payload.message,
userId
);
}

@SubscribeMessage('typing')
async handleTyping(client: Socket, payload: any) {
// Notificar a la otra parte que está escribiendo
client.to(payload.threadId).emit('user_typing', {
userId: client.data.userId
});
}
}

🔒 Rate Limiting (Control de Abuso)

Sistema avanzado de rate limiting multicapa.

Niveles de Rate Limiting

Rate Limit Decorator

@RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5 // 5 requests
})
@Post('change-password')
async changePassword() {
// Máx 5 cambios de password cada 15 min
}

Implementación en Redis

async checkRateLimit(key: string, max: number, windowMs: number) {
const current = await this.redis.incr(key);

if (current === 1) {
// Primera request, setear expiración
await this.redis.expire(key, Math.ceil(windowMs / 1000));
}

if (current > max) {
throw new TooManyRequestsException(
`Rate limit exceeded. Try again in ${windowMs}ms`
);
}

return {
current,
remaining: max - current,
reset: Date.now() + windowMs
};
}

Estrategias por Tipo

Por IP (prevenir DDoS):

rate_limit:ip:192.168.1.1 → counter

Por Usuario (prevenir abuso):

rate_limit:user:user_123 → counter

Por Email (prevenir spam en registro):

rate_limit:email:test@example.com → counter

Por Endpoint (proteger endpoints críticos):

rate_limit:endpoint:change_password:user_123 → counter

📝 Logs & Audit (Auditoría)

Sistema completo de logging y auditoría.

Audit Interceptor

Registra automáticamente acciones críticas:

@UseInterceptors(AuditInterceptor)
export class UserController {
@Post('change-password')
async changePassword(@User() user, @Body() dto: ChangePasswordDto) {
// Se auditará automáticamente
}
}

Log generado:

{
userId: 'user_123',
action: 'PASSWORD_CHANGED',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
timestamp: '2025-10-20T15:30:00Z',
metadata: {
method: 'POST',
endpoint: '/users/change-password'
}
}

Acciones Auditadas

  • ✅ Login/Logout
  • ✅ Cambio de password
  • ✅ Cambio de email
  • ✅ Activar/Desactivar 2FA
  • ✅ Crear wallet
  • ✅ Enviar transacción
  • ✅ Cambiar datos de perfil
  • ✅ Acciones de admin

Logger Service

class LoggerService {
log(message: string, context?: string) {
// Info level
}

error(message: string, trace?: string, context?: string) {
// Error level
}

warn(message: string, context?: string) {
// Warning level
}

debug(message: string, context?: string) {
// Debug level (solo desarrollo)
}
}

📧 Email Service

Envío de emails con templates:

class EmailService {
async sendWelcomeEmail(user: User) {
await this.send({
to: user.email,
subject: 'Welcome to SwapBits!',
template: 'welcome',
context: {
name: user.firstName,
verificationUrl: `${this.config.frontendUrl}/verify/${user.verificationToken}`
}
});
}

async sendTransactionNotification(user: User, tx: Transaction) {
await this.send({
to: user.email,
subject: `Transaction ${tx.type}: ${tx.amount} ${tx.coin}`,
template: 'transaction',
context: {
type: tx.type,
amount: tx.amount,
coin: tx.coin,
txHash: tx.txHash
}
});
}
}

Templates disponibles:

  • welcome: Email de bienvenida
  • verify-email: Verificación de email
  • reset-password: Recuperación de contraseña
  • transaction: Notificación de transacción
  • kyc-approved: KYC aprobado
  • kyc-rejected: KYC rechazado

🗄️ Schemas (Modelos MongoDB)

30+ schemas de Mongoose compartidos por todos los servicios.

Schemas Principales

User Schema:

@Schema()
class User {
@Prop({ required: true, unique: true })
email: string;

@Prop({ required: true })
password: string;

@Prop({ default: false })
emailVerified: boolean;

@Prop({ default: false })
twoFactorEnabled: boolean;

@Prop()
twoFactorSecret?: string;

@Prop({ type: String, enum: ['pending', 'approved', 'rejected'] })
kycStatus: string;
}

Wallet Schema:

@Schema()
class Wallet {
@Prop({ required: true, ref: 'User' })
userId: Types.ObjectId;

@Prop({ required: true })
address: string;

@Prop({ required: true })
coin: string;

@Prop({ required: true })
type: string; // EVM, BTC, SOL, etc.

@Prop({ required: true })
privateKeyHash: string; // Nunca en texto plano

@Prop({ default: 0 })
balance: number;
}

Transaction Schema:

@Schema()
class Transaction {
@Prop({ required: true, ref: 'Wallet' })
walletId: Types.ObjectId;

@Prop({ required: true })
txHash: string;

@Prop({ required: true, enum: ['sent', 'received'] })
type: string;

@Prop({ required: true })
amount: number;

@Prop({ required: true })
coin: string;

@Prop()
from?: string;

@Prop()
to?: string;

@Prop({ default: 'pending', enum: ['pending', 'confirmed', 'failed'] })
status: string;

@Prop({ default: 0 })
confirmations: number;
}

Otros Schemas

  • Session: Sesiones activas
  • Order: Órdenes de trading
  • ChatThread: Hilos de chat
  • ChatMessage: Mensajes de chat
  • AuditLog: Logs de auditoría
  • Notification: Notificaciones
  • Device: Dispositivos registrados
  • RateLimit: Rate limits
  • ApiKey: API keys
  • Webhook: Webhooks recibidos
  • Y 20 más...

🔧 Utils (Utilidades)

HTTP Helpers

class HttpHelpers {
static success(data: any, message?: string) {
return {
success: true,
message: message || 'Success',
data
};
}

static error(message: string, code?: string) {
return {
success: false,
message,
code: code || 'ERROR'
};
}
}

Event Codes

Códigos de eventos estandarizados:

export const EventCodes = {
// Auth
AUTH_LOGIN_SUCCESS: 'AUTH_001',
AUTH_LOGIN_FAILED: 'AUTH_002',
AUTH_2FA_REQUIRED: 'AUTH_003',

// Transactions
TX_CREATED: 'TX_001',
TX_CONFIRMED: 'TX_002',
TX_FAILED: 'TX_003',

// KYC
KYC_STARTED: 'KYC_001',
KYC_APPROVED: 'KYC_002',
KYC_REJECTED: 'KYC_003',

// Y muchos más...
};

🏥 Health Checks

Sistema de health checks para monitoreo:

@Controller('health')
export class HealthController {
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('mongodb'),
() => this.redis.pingCheck('redis'),
() => this.disk.checkStorage('storage', { threshold: 0.9 })
]);
}
}

Response:

{
"status": "ok",
"info": {
"mongodb": { "status": "up" },
"redis": { "status": "up" },
"storage": { "status": "up", "used": 0.65 }
}
}

📦 Cómo se Usan

En un Microservicio

// En auth-service/package.json
{
"dependencies": {
"@swapbits/guards": "file:../../packages/guards",
"@swapbits/schemas": "file:../../packages/schemas",
"@swapbits/redis": "file:../../packages/redis",
"@swapbits/jwt": "file:../../packages/jwt"
}
}

// En auth-service/src/auth.module.ts
import { JwtModule } from '@swapbits/jwt';
import { RedisModule } from '@swapbits/redis';
import { JwtAuthGuard, KycGuard } from '@swapbits/guards';

@Module({
imports: [JwtModule, RedisModule],
providers: [JwtAuthGuard, KycGuard]
})
export class AuthModule {}

Ventajas del Sistema de Packages

Beneficios:

  1. Consistencia: Todos usan el mismo código
  2. Mantenimiento: Actualizar en un solo lugar
  3. Testing: Testear una vez, usar en todos
  4. Escalabilidad: Agregar servicio nuevo es fácil
  5. Desacoplamiento: Packages son independientes

Resumen de Packages

PackageFunciónUsado en
guardsAutenticación/AutorizaciónTodos los servicios
schemasModelos MongoDBTodos los servicios
redisCliente Redis compartidoWS, User, Auth
jwtJWT tokensAuth, User, Admin
analyticsMétricasTodos los servicios
chatSistema de chatWS, Admin, User
rate-limitingRate limitsTodos los servicios
logsLogging/AuditTodos los servicios
emailEnvío de emailsAuth, User, Bank
healthHealth checksTodos los servicios

Para Desarrolladores

Los Shared Packages son el pegamento del sistema. Sin ellos, habría código duplicado por todos lados.

Regla de oro: Si el código se usa en 2+ servicios, va en Packages.

Actualizar un package:

  1. Hacer cambio en packages/
  2. Los servicios lo verán automáticamente (file: link)
  3. No necesitas publicar a npm

Agregar un nuevo package:

  1. Crear carpeta en packages/
  2. Agregar package.json con nombre @swapbits/xxx
  3. Importar en servicios que lo necesiten