feat: Agregar ejemplos de uso de feature flags en comandos y mejorar la configuración del proyecto
This commit is contained in:
311
README/FEATURE_FLAGS_COMANDOS.md
Normal file
311
README/FEATURE_FLAGS_COMANDOS.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 🎮 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. 🎮
|
||||
Reference in New Issue
Block a user