312 lines
7.1 KiB
Markdown
312 lines
7.1 KiB
Markdown
|
|
# 🎮 Feature Flags - Uso en Comandos Slash y Mensajes
|
|||
|
|
|
|||
|
|
## 🚀 Uso Universal
|
|||
|
|
|
|||
|
|
El sistema funciona **idénticamente** para comandos slash y comandos de mensaje.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📦 3 Formas de Usar
|
|||
|
|
|
|||
|
|
### 1️⃣ Wrapper (Recomendado - Más Limpio)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { withFeatureFlag } from "@/core/lib/featureFlagCommandWrapper";
|
|||
|
|
import { CommandSlash } from "@/core/types/commands";
|
|||
|
|
|
|||
|
|
// COMANDO SLASH
|
|||
|
|
export const command: CommandSlash = {
|
|||
|
|
name: 'shop',
|
|||
|
|
description: 'Abre la tienda',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: withFeatureFlag('new_shop_system', async (interaction, client) => {
|
|||
|
|
// Tu código aquí - solo se ejecuta si el flag está enabled
|
|||
|
|
await interaction.reply('🛒 Tienda!');
|
|||
|
|
}, {
|
|||
|
|
fallbackMessage: '🔧 Tienda en mantenimiento'
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// COMANDO DE MENSAJE
|
|||
|
|
export const command: CommandMessage = {
|
|||
|
|
name: 'shop',
|
|||
|
|
type: 'message',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: withFeatureFlag('new_shop_system', async (message, args, client) => {
|
|||
|
|
// Mismo código, funciona igual
|
|||
|
|
await message.reply('🛒 Tienda!');
|
|||
|
|
}, {
|
|||
|
|
fallbackMessage: '🔧 Tienda en mantenimiento'
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2️⃣ Guard (Respuesta Automática)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { guardFeatureFlag } from "@/core/lib/featureFlagCommandWrapper";
|
|||
|
|
|
|||
|
|
// COMANDO SLASH
|
|||
|
|
export const command: CommandSlash = {
|
|||
|
|
name: 'mine',
|
|||
|
|
description: 'Minea recursos',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (interaction, client) => {
|
|||
|
|
// Guard responde automáticamente si está disabled
|
|||
|
|
if (!await guardFeatureFlag('new_mining', interaction)) {
|
|||
|
|
return; // Ya respondió al usuario
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Tu código aquí
|
|||
|
|
await interaction.reply('⛏️ Minando...');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// COMANDO DE MENSAJE - EXACTAMENTE IGUAL
|
|||
|
|
export const command: CommandMessage = {
|
|||
|
|
name: 'mine',
|
|||
|
|
type: 'message',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (message, args, client) => {
|
|||
|
|
// Mismo guard funciona para mensajes
|
|||
|
|
if (!await guardFeatureFlag('new_mining', message)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await message.reply('⛏️ Minando...');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3️⃣ Check Manual (Más Control)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { checkFeatureFlag } from "@/core/lib/featureFlagCommandWrapper";
|
|||
|
|
|
|||
|
|
// COMANDO SLASH
|
|||
|
|
export const command: CommandSlash = {
|
|||
|
|
name: 'inventory',
|
|||
|
|
description: 'Tu inventario',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 5,
|
|||
|
|
run: async (interaction, client) => {
|
|||
|
|
const useNewUI = await checkFeatureFlag('inventory_v2', interaction);
|
|||
|
|
|
|||
|
|
if (useNewUI) {
|
|||
|
|
await interaction.reply('📦 Inventario v2');
|
|||
|
|
} else {
|
|||
|
|
await interaction.reply('📦 Inventario v1');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// COMANDO DE MENSAJE - IGUAL
|
|||
|
|
export const command: CommandMessage = {
|
|||
|
|
name: 'inventory',
|
|||
|
|
type: 'message',
|
|||
|
|
cooldown: 5,
|
|||
|
|
run: async (message, args, client) => {
|
|||
|
|
const useNewUI = await checkFeatureFlag('inventory_v2', message);
|
|||
|
|
|
|||
|
|
if (useNewUI) {
|
|||
|
|
await message.reply('📦 Inventario v2');
|
|||
|
|
} else {
|
|||
|
|
await message.reply('📦 Inventario v1');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔥 A/B Testing
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { abTestCommand } from "@/core/lib/featureFlagCommandWrapper";
|
|||
|
|
|
|||
|
|
// COMANDO SLASH
|
|||
|
|
export const command: CommandSlash = {
|
|||
|
|
name: 'attack',
|
|||
|
|
description: 'Ataca',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (interaction, client) => {
|
|||
|
|
await abTestCommand('new_combat', interaction, {
|
|||
|
|
variant: async () => {
|
|||
|
|
// Nueva versión (50% usuarios)
|
|||
|
|
await interaction.reply('⚔️ Daño nuevo: 100');
|
|||
|
|
},
|
|||
|
|
control: async () => {
|
|||
|
|
// Versión antigua (50% usuarios)
|
|||
|
|
await interaction.reply('⚔️ Daño viejo: 50');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// COMANDO DE MENSAJE - IGUAL
|
|||
|
|
export const command: CommandMessage = {
|
|||
|
|
name: 'attack',
|
|||
|
|
type: 'message',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (message, args, client) => {
|
|||
|
|
await abTestCommand('new_combat', message, {
|
|||
|
|
variant: async () => {
|
|||
|
|
await message.reply('⚔️ Daño nuevo: 100');
|
|||
|
|
},
|
|||
|
|
control: async () => {
|
|||
|
|
await message.reply('⚔️ Daño viejo: 50');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💡 Ejemplo Real: Comando Universal
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { checkFeatureFlag } from "@/core/lib/featureFlagCommandWrapper";
|
|||
|
|
import { CommandSlash, CommandMessage } from "@/core/types/commands";
|
|||
|
|
|
|||
|
|
// Función de negocio (reutilizable)
|
|||
|
|
async function executeShop(source: any) {
|
|||
|
|
const useNewShop = await checkFeatureFlag('new_shop_system', source);
|
|||
|
|
|
|||
|
|
const items = useNewShop
|
|||
|
|
? ['⚔️ Espada Legendaria', '🛡️ Escudo Épico']
|
|||
|
|
: ['Espada', 'Escudo'];
|
|||
|
|
|
|||
|
|
const response = `🛒 **Tienda**\n${items.join('\n')}`;
|
|||
|
|
|
|||
|
|
// Detectar tipo y responder
|
|||
|
|
if ('options' in source) {
|
|||
|
|
await source.reply(response);
|
|||
|
|
} else {
|
|||
|
|
await source.reply(response);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// COMANDO SLASH
|
|||
|
|
export const shopSlash: CommandSlash = {
|
|||
|
|
name: 'shop',
|
|||
|
|
description: 'Tienda',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (interaction, client) => {
|
|||
|
|
await executeShop(interaction);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// COMANDO DE MENSAJE
|
|||
|
|
export const shopMessage: CommandMessage = {
|
|||
|
|
name: 'shop',
|
|||
|
|
type: 'message',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: async (message, args, client) => {
|
|||
|
|
await executeShop(message);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 Configurar Flags
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Crear flag
|
|||
|
|
/featureflags create name:new_shop_system status:disabled target:global
|
|||
|
|
|
|||
|
|
# Habilitar
|
|||
|
|
/featureflags update flag:new_shop_system status:enabled
|
|||
|
|
|
|||
|
|
# Rollout 25% de usuarios
|
|||
|
|
/featureflags rollout flag:new_shop_system strategy:percentage percentage:25
|
|||
|
|
|
|||
|
|
# A/B testing (50/50)
|
|||
|
|
/featureflags rollout flag:new_combat strategy:percentage percentage:50
|
|||
|
|
|
|||
|
|
# Ver estadísticas
|
|||
|
|
/featureflags stats flag:new_shop_system
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✨ Ventajas del Sistema
|
|||
|
|
|
|||
|
|
✅ **Un solo código** para ambos tipos de comandos
|
|||
|
|
✅ **No rompe** comandos existentes
|
|||
|
|
✅ **Rollouts progresivos** sin redeploys
|
|||
|
|
✅ **Kill switches** instantáneos
|
|||
|
|
✅ **A/B testing** automático
|
|||
|
|
✅ **Estadísticas** de uso en tiempo real
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Casos de Uso
|
|||
|
|
|
|||
|
|
### Migración Gradual
|
|||
|
|
```typescript
|
|||
|
|
run: async (interaction, client) => {
|
|||
|
|
const useNew = await checkFeatureFlag('new_system', interaction);
|
|||
|
|
|
|||
|
|
if (useNew) {
|
|||
|
|
await newSystem(interaction);
|
|||
|
|
} else {
|
|||
|
|
await oldSystem(interaction);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Kill Switch
|
|||
|
|
```bash
|
|||
|
|
# Si hay un bug crítico
|
|||
|
|
/featureflags update flag:problematic_feature status:maintenance
|
|||
|
|
# Inmediatamente deshabilitado sin redeploy
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Beta Testing
|
|||
|
|
```bash
|
|||
|
|
# Solo para guilds específicos
|
|||
|
|
/featureflags create name:beta_features status:rollout target:guild
|
|||
|
|
/featureflags rollout flag:beta_features strategy:whitelist
|
|||
|
|
# Luego añadir IDs de guilds en el config
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Eventos Temporales
|
|||
|
|
```typescript
|
|||
|
|
// Crear con fechas
|
|||
|
|
await featureFlagService.setFlag({
|
|||
|
|
name: 'halloween_event',
|
|||
|
|
status: 'enabled',
|
|||
|
|
startDate: new Date('2025-10-25'),
|
|||
|
|
endDate: new Date('2025-11-01')
|
|||
|
|
});
|
|||
|
|
// Se auto-desactiva el 1 de noviembre
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 Integración en tu Bot
|
|||
|
|
|
|||
|
|
Simplemente usa los helpers en cualquier comando:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { withFeatureFlag } from '@/core/lib/featureFlagCommandWrapper';
|
|||
|
|
|
|||
|
|
export const command: CommandSlash = {
|
|||
|
|
name: 'tu_comando',
|
|||
|
|
description: 'Descripción',
|
|||
|
|
type: 'slash',
|
|||
|
|
cooldown: 10,
|
|||
|
|
run: withFeatureFlag('tu_flag', async (interaction, client) => {
|
|||
|
|
// Tu código existente aquí
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Eso es todo.** El sistema funciona transparentemente para ambos tipos de comandos. 🎮
|