feat: Agregar ejemplos de uso de feature flags en comandos y mejorar la configuración del proyecto

This commit is contained in:
Shni
2025-10-31 21:18:46 -05:00
parent 89d475ba66
commit 76ce4e4e4d
5 changed files with 891 additions and 297 deletions

View 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. 🎮

View File

@@ -0,0 +1,218 @@
/**
* Feature Flag Wrapper para Comandos
* Wrapper universal que funciona tanto para comandos slash como mensajes
*/
import { ChatInputCommandInteraction, Message, MessageFlags } from "discord.js";
import { featureFlagService } from "../services/FeatureFlagService";
import { extractContext } from "../lib/featureFlagHelpers";
import logger from "../lib/logger";
import type Amayo from "../client";
/**
* Wrapper para proteger comandos con feature flags
* Funciona tanto para comandos slash como comandos de mensaje
*
* @example
* ```ts
* export const command: CommandSlash = {
* name: 'shop',
* run: withFeatureFlag('new_shop_system', async (interaction, client) => {
* // Tu código aquí
* })
* };
*
* export const command: CommandMessage = {
* name: 'shop',
* run: withFeatureFlag('new_shop_system', async (message, args, client) => {
* // Tu código aquí
* })
* };
* ```
*/
// Overload para comandos slash
export function withFeatureFlag(
flagName: string,
handler: (
interaction: ChatInputCommandInteraction,
client: Amayo
) => Promise<void>,
options?: {
fallbackMessage?: string;
silent?: boolean;
}
): (interaction: ChatInputCommandInteraction, client: Amayo) => Promise<void>;
// Overload para comandos de mensaje
export function withFeatureFlag(
flagName: string,
handler: (message: Message, args: string[], client: Amayo) => Promise<void>,
options?: {
fallbackMessage?: string;
silent?: boolean;
}
): (message: Message, args: string[], client: Amayo) => Promise<void>;
// Implementación
export function withFeatureFlag(
flagName: string,
handler: any,
options: {
fallbackMessage?: string;
silent?: boolean;
} = {}
): any {
return async function (...args: any[]) {
const firstArg = args[0];
// Determinar si es comando slash o mensaje
const isSlashCommand =
"options" in firstArg && "reply" in firstArg && "user" in firstArg;
const isMessageCommand = "content" in firstArg && "author" in firstArg;
if (!isSlashCommand && !isMessageCommand) {
logger.error("[FeatureFlag] Tipo de comando no soportado");
return;
}
const context = extractContext(firstArg);
const enabled = await featureFlagService.isEnabled(flagName, context);
if (!enabled) {
if (!options.silent) {
const message =
options.fallbackMessage ||
"⚠️ Esta funcionalidad no está disponible en este momento.";
if (isSlashCommand) {
const interaction = firstArg as ChatInputCommandInteraction;
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: message,
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: message,
flags: MessageFlags.Ephemeral,
});
}
} else {
const msg = firstArg as Message;
await msg.reply(message);
}
}
logger.debug(`[FeatureFlag] Comando bloqueado por flag "${flagName}"`);
return;
}
// Ejecutar el handler original
return handler(...args);
};
}
/**
* Check rápido para usar dentro de comandos
* Devuelve true/false sin responder automáticamente
*
* @example
* ```ts
* run: async (interaction, client) => {
* if (!await checkFeatureFlag('new_system', interaction)) {
* await interaction.reply('❌ No disponible');
* return;
* }
* // código...
* }
* ```
*/
export async function checkFeatureFlag(
flagName: string,
source: ChatInputCommandInteraction | Message
): Promise<boolean> {
const context = extractContext(source);
return await featureFlagService.isEnabled(flagName, context);
}
/**
* Guard que responde automáticamente si el flag está disabled
*
* @example
* ```ts
* run: async (interaction, client) => {
* if (!await guardFeatureFlag('new_system', interaction)) {
* return; // Ya respondió automáticamente
* }
* // código...
* }
* ```
*/
export async function guardFeatureFlag(
flagName: string,
source: ChatInputCommandInteraction | Message,
customMessage?: string
): Promise<boolean> {
const context = extractContext(source);
const enabled = await featureFlagService.isEnabled(flagName, context);
if (!enabled) {
const message =
customMessage ||
"⚠️ Esta funcionalidad está deshabilitada temporalmente.";
if ("options" in source && "reply" in source && "user" in source) {
// Es un comando slash
const interaction = source as ChatInputCommandInteraction;
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: message,
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: message,
flags: MessageFlags.Ephemeral,
});
}
} else {
// Es un mensaje
await source.reply(message);
}
}
return enabled;
}
/**
* Helper para A/B testing en comandos
*
* @example
* ```ts
* run: async (interaction, client) => {
* await abTestCommand('new_algorithm', interaction, {
* variant: async () => {
* // Nueva versión
* await interaction.reply('Usando algoritmo nuevo');
* },
* control: async () => {
* // Versión antigua
* await interaction.reply('Usando algoritmo antiguo');
* }
* });
* }
* ```
*/
export async function abTestCommand<T = void>(
flagName: string,
source: ChatInputCommandInteraction | Message,
variants: {
variant: () => Promise<T>;
control: () => Promise<T>;
}
): Promise<T> {
const context = extractContext(source);
const enabled = await featureFlagService.isEnabled(flagName, context);
return enabled ? variants.variant() : variants.control();
}

View File

@@ -0,0 +1,361 @@
/**
* Ejemplos de uso de Feature Flags en comandos
* Funciona para comandos slash Y comandos de mensaje
*/
import { ChatInputCommandInteraction, Message } from "discord.js";
import { CommandSlash, CommandMessage } from "../../types/commands";
import {
withFeatureFlag,
checkFeatureFlag,
guardFeatureFlag,
abTestCommand,
} from "../lib/featureFlagCommandWrapper";
import type Amayo from "../client";
// ============================================================================
// PATRÓN 1: Usando withFeatureFlag (wrapper) - RECOMENDADO
// ============================================================================
/**
* Comando Slash con Feature Flag
* El wrapper bloquea automáticamente si el flag está disabled
*/
export const shopSlashCommand: CommandSlash = {
name: "shop",
description: "Abre la tienda",
type: "slash",
cooldown: 10,
// Envuelve el handler con el wrapper
run: withFeatureFlag(
"new_shop_system",
async (interaction: ChatInputCommandInteraction, client: Amayo) => {
// Este código solo se ejecuta si el flag está enabled
await interaction.reply("🛒 Bienvenido a la tienda!");
},
{
fallbackMessage: "🔧 La tienda está en mantenimiento.",
}
),
};
/**
* Comando de Mensaje con Feature Flag
* El mismo wrapper funciona para comandos de mensaje
*/
export const shopMessageCommand: CommandMessage = {
name: "shop",
type: "message",
cooldown: 10,
description: "Abre la tienda",
// El mismo wrapper funciona aquí
run: withFeatureFlag(
"new_shop_system",
async (message: Message, args: string[], client: Amayo) => {
// Este código solo se ejecuta si el flag está enabled
await message.reply("🛒 Bienvenido a la tienda!");
},
{
fallbackMessage: "🔧 La tienda está en mantenimiento.",
}
),
};
// ============================================================================
// PATRÓN 2: Usando guardFeatureFlag (check con respuesta automática)
// ============================================================================
/**
* Comando Slash con guard
*/
export const mineSlashCommand: CommandSlash = {
name: "mine",
description: "Minea recursos",
type: "slash",
cooldown: 10,
run: async (interaction, client) => {
// Guard que responde automáticamente si está disabled
if (!(await guardFeatureFlag("new_mining_system", interaction))) {
return; // Ya respondió automáticamente
}
// Código del comando
await interaction.reply("⛏️ Minando...");
},
};
/**
* Comando de Mensaje con guard
*/
export const mineMessageCommand: CommandMessage = {
name: "mine",
type: "message",
cooldown: 10,
run: async (message, args, client) => {
// El mismo guard funciona para mensajes
if (!(await guardFeatureFlag("new_mining_system", message))) {
return;
}
await message.reply("⛏️ Minando...");
},
};
// ============================================================================
// PATRÓN 3: Usando checkFeatureFlag (check manual)
// ============================================================================
/**
* Comando Slash con check manual
* Útil cuando necesitas lógica custom
*/
export const inventorySlashCommand: CommandSlash = {
name: "inventory",
description: "Muestra tu inventario",
type: "slash",
cooldown: 5,
run: async (interaction, client) => {
const useNewUI = await checkFeatureFlag("inventory_ui_v2", interaction);
if (useNewUI) {
// Nueva UI
await interaction.reply({
content: "📦 **Inventario v2**\n- Item 1\n- Item 2",
ephemeral: true,
});
} else {
// UI antigua
await interaction.reply({
content: "📦 Inventario: Item 1, Item 2",
ephemeral: true,
});
}
},
};
/**
* Comando de Mensaje con check manual
*/
export const inventoryMessageCommand: CommandMessage = {
name: "inventory",
type: "message",
cooldown: 5,
aliases: ["inv", "items"],
run: async (message, args, client) => {
const useNewUI = await checkFeatureFlag("inventory_ui_v2", message);
if (useNewUI) {
await message.reply("📦 **Inventario v2**\n- Item 1\n- Item 2");
} else {
await message.reply("📦 Inventario: Item 1, Item 2");
}
},
};
// ============================================================================
// PATRÓN 4: A/B Testing
// ============================================================================
/**
* Comando Slash con A/B testing
*/
export const combatSlashCommand: CommandSlash = {
name: "attack",
description: "Ataca a un enemigo",
type: "slash",
cooldown: 10,
run: async (interaction, client) => {
await abTestCommand("improved_combat_algorithm", interaction, {
variant: async () => {
// 50% de usuarios ven el nuevo algoritmo
const damage = Math.floor(Math.random() * 100) + 50;
await interaction.reply(`⚔️ Daño (nuevo): ${damage}`);
},
control: async () => {
// 50% ven el algoritmo antiguo
const damage = Math.floor(Math.random() * 50) + 25;
await interaction.reply(`⚔️ Daño (antiguo): ${damage}`);
},
});
},
};
/**
* Comando de Mensaje con A/B testing
*/
export const combatMessageCommand: CommandMessage = {
name: "attack",
type: "message",
cooldown: 10,
run: async (message, args, client) => {
await abTestCommand("improved_combat_algorithm", message, {
variant: async () => {
const damage = Math.floor(Math.random() * 100) + 50;
await message.reply(`⚔️ Daño (nuevo): ${damage}`);
},
control: async () => {
const damage = Math.floor(Math.random() * 50) + 25;
await message.reply(`⚔️ Daño (antiguo): ${damage}`);
},
});
},
};
// ============================================================================
// PATRÓN 5: Múltiples flags (migrando sistema antiguo a nuevo)
// ============================================================================
/**
* Comando que migra gradualmente de un sistema a otro
*/
export const economySlashCommand: CommandSlash = {
name: "balance",
description: "Muestra tu balance",
type: "slash",
cooldown: 5,
run: async (interaction, client) => {
const useNewEconomy = await checkFeatureFlag(
"economy_system_v2",
interaction
);
const usePremiumFeatures = await checkFeatureFlag(
"premium_features",
interaction
);
if (useNewEconomy) {
// Sistema nuevo de economía
const balance = 5000;
const streak = usePremiumFeatures ? "🔥 Racha: 7 días" : "";
await interaction.reply(
`💰 Balance: ${balance} monedas\n${streak}`.trim()
);
} else {
// Sistema antiguo
const balance = 5000;
await interaction.reply(`💰 Tienes ${balance} monedas`);
}
},
};
/**
* Lo mismo pero para comando de mensaje
*/
export const economyMessageCommand: CommandMessage = {
name: "balance",
type: "message",
cooldown: 5,
aliases: ["bal", "money"],
run: async (message, args, client) => {
const useNewEconomy = await checkFeatureFlag("economy_system_v2", message);
const usePremiumFeatures = await checkFeatureFlag(
"premium_features",
message
);
if (useNewEconomy) {
const balance = 5000;
const streak = usePremiumFeatures ? "🔥 Racha: 7 días" : "";
await message.reply(`💰 Balance: ${balance} monedas\n${streak}`.trim());
} else {
const balance = 5000;
await message.reply(`💰 Tienes ${balance} monedas`);
}
},
};
// ============================================================================
// PATRÓN 6: Comando universal (un solo run para ambos)
// ============================================================================
/**
* Helper para detectar tipo de comando
*/
function isSlashCommand(
source: ChatInputCommandInteraction | Message
): source is ChatInputCommandInteraction {
return "options" in source && "user" in source;
}
/**
* Función de negocio universal
*/
async function showProfile(
source: ChatInputCommandInteraction | Message,
userId: string
) {
const useNewProfile = await checkFeatureFlag("profile_v2", source);
const profileText = useNewProfile
? `👤 **Perfil v2**\nUsuario: <@${userId}>\nNivel: 10`
: `👤 Perfil: <@${userId}> - Nivel 10`;
if (isSlashCommand(source)) {
await source.reply(profileText);
} else {
await source.reply(profileText);
}
}
/**
* Comando Slash que usa la función universal
*/
export const profileSlashCommand: CommandSlash = {
name: "profile",
description: "Muestra tu perfil",
type: "slash",
cooldown: 5,
run: async (interaction, client) => {
await showProfile(interaction, interaction.user.id);
},
};
/**
* Comando de Mensaje que usa la misma función universal
*/
export const profileMessageCommand: CommandMessage = {
name: "profile",
type: "message",
cooldown: 5,
aliases: ["perfil", "me"],
run: async (message, args, client) => {
await showProfile(message, message.author.id);
},
};
// ============================================================================
// RESUMEN DE PATRONES
// ============================================================================
/*
* PATRÓN 1: withFeatureFlag()
* - Más limpio y declarativo
* - Bloquea automáticamente si disabled
* - Recomendado para comandos simples
*
* PATRÓN 2: guardFeatureFlag()
* - Check con respuesta automática
* - Control total del flujo
* - Bueno para lógica compleja
*
* PATRÓN 3: checkFeatureFlag()
* - Check manual sin respuesta
* - Para if/else personalizados
* - Migración gradual de sistemas
*
* PATRÓN 4: abTestCommand()
* - A/B testing directo
* - Ejecuta función u otra según flag
* - Ideal para comparar versiones
*
* PATRÓN 5: Múltiples flags
* - Combina varios checks
* - Features progresivas
* - Sistemas modulares
*
* PATRÓN 6: Función universal
* - Un solo código para ambos tipos
* - Reutilización máxima
* - Mantenimiento simplificado
*/

View File

@@ -1,296 +0,0 @@
/**
* Ejemplo de Uso de Feature Flags en Comandos
*
* Este archivo muestra varios patrones de uso del sistema de feature flags
*/
import { CommandInteraction, Message } from "discord.js";
import {
RequireFeature,
featureGuard,
isFeatureEnabledForInteraction,
abTest,
extractContext,
requireAllFeatures,
} from "../core/lib/featureFlagHelpers";
// ============================================================================
// Ejemplo 1: Usar decorador @RequireFeature
// ============================================================================
export class ShopCommand {
/**
* El decorador RequireFeature bloquea automáticamente la ejecución
* si el flag no está habilitado y responde al usuario
*/
@RequireFeature("new_shop_system", {
fallbackMessage: "🔧 El nuevo sistema de tienda estará disponible pronto.",
})
async execute(interaction: CommandInteraction) {
// Este código solo se ejecuta si el flag está habilitado
await interaction.reply("¡Bienvenido a la nueva tienda!");
}
}
// ============================================================================
// Ejemplo 2: Usar featureGuard (más control)
// ============================================================================
export async function handleMiningCommand(interaction: CommandInteraction) {
// featureGuard devuelve true/false y opcionalmente responde al usuario
if (
!(await featureGuard("new_mining_system", interaction, {
replyIfDisabled: true,
customMessage: "⛏️ El nuevo sistema de minería está en mantenimiento.",
}))
) {
return; // Sale si el flag está deshabilitado
}
// Código del nuevo sistema de minería
await interaction.reply("⛏️ Iniciando minería con el nuevo sistema...");
}
// ============================================================================
// Ejemplo 3: Check manual (para lógica condicional)
// ============================================================================
export async function handleInventoryCommand(interaction: CommandInteraction) {
const useNewUI = await isFeatureEnabledForInteraction(
"inventory_ui_v2",
interaction
);
if (useNewUI) {
// Muestra el inventario con la nueva UI
await showInventoryV2(interaction);
} else {
// Muestra el inventario con la UI antigua
await showInventoryV1(interaction);
}
}
async function showInventoryV2(interaction: CommandInteraction) {
await interaction.reply("📦 Inventario (UI v2)");
}
async function showInventoryV1(interaction: CommandInteraction) {
await interaction.reply("📦 Inventario (UI v1)");
}
// ============================================================================
// Ejemplo 4: A/B Testing
// ============================================================================
export async function handleCombatCommand(interaction: CommandInteraction) {
const context = extractContext(interaction);
// A/B testing: mitad de usuarios usa el algoritmo nuevo, mitad el viejo
const result = await abTest("improved_combat_algorithm", context, {
variant: async () => {
// Nueva versión del algoritmo
return calculateDamageV2();
},
control: async () => {
// Versión antigua del algoritmo
return calculateDamageV1();
},
});
await interaction.reply(`⚔️ Daño calculado: ${result}`);
}
function calculateDamageV2(): number {
// Lógica nueva
return Math.floor(Math.random() * 100) + 50;
}
function calculateDamageV1(): number {
// Lógica antigua
return Math.floor(Math.random() * 50) + 25;
}
// ============================================================================
// Ejemplo 5: Múltiples flags (acceso premium)
// ============================================================================
export async function handlePremiumFeature(interaction: CommandInteraction) {
const context = extractContext(interaction);
// Requiere que TODOS los flags estén habilitados
const hasAccess = await requireAllFeatures(
["premium_features", "beta_access", "advanced_commands"],
context
);
if (!hasAccess) {
await interaction.reply({
content: "❌ No tienes acceso a esta funcionalidad premium.",
flags: ["Ephemeral"],
});
return;
}
await interaction.reply("✨ Funcionalidad premium activada!");
}
// ============================================================================
// Ejemplo 6: Migrando de sistema antiguo a nuevo gradualmente
// ============================================================================
export async function handleEconomyCommand(interaction: CommandInteraction) {
const useNewSystem = await isFeatureEnabledForInteraction(
"economy_system_v2",
interaction
);
if (useNewSystem) {
// Nuevo sistema de economía
await newEconomySystem.processTransaction(interaction);
} else {
// Sistema antiguo (mantener por compatibilidad durante el rollout)
await oldEconomySystem.processTransaction(interaction);
}
}
// Simulación de sistemas
const newEconomySystem = {
async processTransaction(interaction: CommandInteraction) {
await interaction.reply("💰 Transacción procesada (Sistema v2)");
},
};
const oldEconomySystem = {
async processTransaction(interaction: CommandInteraction) {
await interaction.reply("💰 Transacción procesada (Sistema v1)");
},
};
// ============================================================================
// Ejemplo 7: Eventos temporales con fechas
// ============================================================================
export async function handleHalloweenEvent(interaction: CommandInteraction) {
// El flag 'halloween_2025' tiene startDate y endDate configurados
// Se habilitará automáticamente durante el período del evento
if (
!(await featureGuard("halloween_2025", interaction, {
replyIfDisabled: true,
customMessage:
"🎃 El evento de Halloween no está activo en este momento.",
}))
) {
return;
}
await interaction.reply("🎃 ¡Bienvenido al evento de Halloween 2025!");
}
// ============================================================================
// Ejemplo 8: Kill Switch para emergencias
// ============================================================================
export async function handleProblematicFeature(
interaction: CommandInteraction
) {
// Si hay un bug crítico, el administrador puede cambiar el flag a 'maintenance'
// inmediatamente sin necesidad de redeploy
if (
!(await featureGuard("experimental_feature", interaction, {
replyIfDisabled: true,
customMessage:
"🔧 Esta funcionalidad está en mantenimiento temporalmente.",
}))
) {
return;
}
// Código que podría tener bugs
await experimentalLogic(interaction);
}
async function experimentalLogic(interaction: CommandInteraction) {
await interaction.reply("🧪 Funcionalidad experimental activada");
}
// ============================================================================
// Ejemplo 9: Beta Testing por Guild (servidor)
// ============================================================================
export async function handleBetaCommand(interaction: CommandInteraction) {
// El flag 'beta_features' está configurado con:
// - target: 'guild'
// - rolloutStrategy: 'whitelist'
// - rolloutConfig: { targetIds: ['guild_id_1', 'guild_id_2'] }
const context = extractContext(interaction);
if (
!(await featureGuard("beta_features", interaction, {
replyIfDisabled: true,
customMessage:
"🔒 Tu servidor no tiene acceso a las funcionalidades beta.",
}))
) {
return;
}
await interaction.reply(
"🧪 Funcionalidades beta activadas para este servidor!"
);
}
// ============================================================================
// Ejemplo 10: Rollout progresivo por porcentaje
// ============================================================================
export async function handleNewGameMode(interaction: CommandInteraction) {
// El flag 'new_game_mode' está configurado con:
// - status: 'rollout'
// - rolloutStrategy: 'percentage'
// - rolloutConfig: { percentage: 25 }
//
// Esto significa que el 25% de usuarios verán el nuevo modo de juego
// de forma determinista (el mismo usuario siempre verá lo mismo)
if (!(await featureGuard("new_game_mode", interaction))) {
return;
}
await interaction.reply("🎮 ¡Nuevo modo de juego desbloqueado!");
}
// ============================================================================
// Ejemplo 11: Usando en Message Commands (comandos de texto)
// ============================================================================
export async function handleTextCommand(message: Message, args: string[]) {
// También funciona con comandos de texto tradicionales
const context = extractContext(message);
const useNewParser = await isFeatureEnabledForInteraction(
"new_command_parser",
message
);
if (useNewParser) {
await parseCommandV2(message, args);
} else {
await parseCommandV1(message, args);
}
}
async function parseCommandV2(message: Message, args: string[]) {
await message.reply("Comando parseado con parser v2");
}
async function parseCommandV1(message: Message, args: string[]) {
await message.reply("Comando parseado con parser v1");
}
// ============================================================================
// RESUMEN DE PATRONES
// ============================================================================
/*
* 1. @RequireFeature - Para bloquear métodos enteros fácilmente
* 2. featureGuard - Para checks con respuesta automática al usuario
* 3. isFeatureEnabled - Para lógica condicional if/else
* 4. abTest - Para A/B testing
* 5. requireAllFeatures - Para requerir múltiples flags (AND)
* 6. requireAnyFeature - Para requerir al menos uno (OR)
* 7. withFeature - Para ejecutar código con fallback opcional
*
* Configuración de flags vía comando:
* /featureflags create name:flag_name status:disabled target:global
* /featureflags update flag:flag_name status:enabled
* /featureflags rollout flag:flag_name strategy:percentage percentage:25
*/

View File

@@ -11,5 +11,5 @@
"skipLibCheck": true, "skipLibCheck": true,
"resolveJsonModule": true "resolveJsonModule": true
}, },
"include": ["src", "test/examples"] "include": ["src", "test/examples", "test/examples"]
} }