El nuevo reporte de pedidos está listo. Las pruebas pasan y el despliegue terminó bien. Solo falta un detalle: soporte quiere probarlo con unas cuentas antes de que aparezca en el menú de todos los clientes.
Podrías dejar el cambio esperando en una rama, preparar una versión especial o coordinar otro despliegue para el día del lanzamiento. Pero el código ya puede estar en producción y seguir mostrando el reporte anterior. Lo que necesitas separar son dos decisiones: instalar una versión y decidir quién utiliza una funcionalidad.
Una feature flag, también llamada feature toggle, permite controlar esa segunda decisión. Al principio puede ser un booleano. Después puede considerar el entorno, el usuario o un grupo de cuentas. El if es pequeño; lo interesante es cómo decides su valor y cuánto tiempo vas a mantenerlo.
El primer interruptor cabe en unas pocas líneas
Desplegar coloca el código en el entorno donde se ejecutará. Habilitar hace que una funcionalidad quede disponible bajo ciertas condiciones. Una flag permite que ambos momentos sean distintos, siempre que el código ya incluya los caminos necesarios. Microsoft describe esta separación como la base de la gestión de funcionalidades.
Para verlo, basta con un programa que represente las dos versiones del reporte. Usaremos Node.js 22 o superior, TypeScript y pnpm. Los comandos de entorno de esta sección están escritos para una terminal Bash o Zsh.
Crea una carpeta de prueba vacía:
mkdir feature-flags-lab
cd feature-flags-lab
pnpm init
pnpm add -D typescript @types/node@22Guarda esto como basica.ts:
const nuevaVista = process.env.REPORTES_V2 === 'true';
function mostrarReporte(usarNuevaVista: boolean): string {
if (usarNuevaVista) {
return 'Reporte nuevo: pedidos agrupados por estado';
}
return 'Reporte clásico: listado de pedidos';
}
console.log(mostrarReporte(nuevaVista));La función solo devuelve mensajes para que puedas observar la elección sin levantar una aplicación. Compila y prueba ambos estados:
pnpm exec tsc --strict --target ES2022 --module NodeNext \
--types node --outDir dist basica.ts
REPORTES_V2=false node dist/basica.js
REPORTES_V2=true node dist/basica.jsObtendrás, respectivamente:
Reporte clásico: listado de pedidos
Reporte nuevo: pedidos agrupados por estadoLa comparación con 'true' es intencional. Las variables de entorno de Node.js contienen texto: Boolean('false') también da true. En este ejemplo, una variable ausente o con cualquier otro valor conserva la vista clásica.
Entonces, ¿una feature flag es una variable de entorno? Puede apoyarse en ella, pero no son lo mismo. La flag es la decisión sobre el comportamiento; la variable es una forma de entregar su configuración. Un puerto HTTP también puede venir del entorno y no por eso es una flag.
Aquí leemos el valor al arrancar. Editar un .env o cambiar la configuración externa no actualiza por sí solo ese proceso: tendrías que reiniciarlo con el nuevo valor. Para cambiar la decisión mientras la aplicación sigue funcionando necesitas otra forma de cargarla, como una configuración recargable o un SDK que reciba actualizaciones. Tampoco conviene incluir secretos en flags que terminarán en el navegador; configuración y credenciales tienen responsabilidades distintas.
Que un usuario no cambie de grupo al recargar
El interruptor global resuelve «todos o nadie». Soporte necesita algo más preciso: primero unas cuentas conocidas, después una parte de los usuarios y finalmente el resto.
Puedes activar una flag para una lista de identificadores o para un segmento, un grupo definido por atributos: cuentas de prueba, participantes de una beta o clientes de una región. Esos atributos deben proceder de información confiable de tu aplicación. En Flagsmith, por ejemplo, los segmentos se forman mediante reglas sobre atributos de identidades.
Para un despliegue por porcentaje, evita decidir con Math.random() < 0.2 en cada petición. Un mismo usuario podría ver el reporte nuevo, recargar y volver al antiguo. Las dos pantallas funcionarían; la experiencia sería bastante difícil de explicar.
Una alternativa es asignar a cada usuario una posición estable y compararla con el porcentaje habilitado. Si conserva su identificador y la regla no cambia, conserva su posición. Esa propiedad suele llamarse stickiness. Unleash documenta este mecanismo mediante un hash del identificador y del grupo de la flag; utiliza MurmurHash. Nuestro ejemplo usará SHA-256 porque está disponible en Node.js, sin intentar reproducir el algoritmo de un proveedor.
Crea rollout.ts:
import { createHash } from 'node:crypto';
type Entorno = 'development' | 'staging' | 'production';
type Flag = {
activa: boolean;
porcentaje: Record<Entorno, number>;
};
export function posicion(usuarioId: string): number {
const entrada = JSON.stringify(['reportes-v2', usuarioId]);
const hash = createHash('sha256').update(entrada).digest();
return (hash.readUInt32BE(0) / 2 ** 32) * 100;
}
export function habilitada(
usuarioId: string,
entorno: Entorno,
flag: Flag,
): boolean {
const porcentaje = flag.porcentaje[entorno];
if (!flag.activa || usuarioId.trim() === '') {
return false;
}
if (!Number.isFinite(porcentaje) || porcentaje < 0 || porcentaje > 100) {
return false;
}
return posicion(usuarioId) < porcentaje;
}posicion combina el nombre de la flag con el identificador, calcula un hash y usa sus primeros 32 bits para obtener un número desde 0 hasta menos de 100. La API createHash de Node.js devuelve los bytes necesarios. Aquí el hash sirve para distribuir usuarios de forma determinista, no para autenticarles ni ocultar información sensible.
La evaluación deja la funcionalidad apagada si falta una identidad o si el porcentaje es inválido. Para este reporte, conservar la vista clásica es un valor de respaldo razonable. En otra función tendrás que decidirlo según sus consecuencias: false no significa automáticamente «seguro» para cualquier flag.
Ahora crea demo.ts para comparar dos porcentajes sobre los mismos usuarios ficticios:
import { habilitada, posicion } from './rollout.js';
const flag = {
activa: true,
porcentaje: { development: 100, staging: 100, production: 20 },
};
const ampliada = {
...flag,
porcentaje: { ...flag.porcentaje, production: 50 },
};
const usuarios = Array.from({ length: 10 }, (_, i) => `usuario-${i + 1}`);
console.table(usuarios.map((id) => ({
id,
posicion: posicion(id).toFixed(2),
al20: habilitada(id, 'production', flag),
al50: habilitada(id, 'production', ampliada),
})));
console.log('Apagada:', habilitada('usuario-1', 'production', {
...flag,
activa: false,
}));El import termina en .js porque apunta al archivo que generará TypeScript. Compila y ejecuta:
pnpm exec tsc --strict --target ES2022 --module NodeNext \
--types node --outDir dist rollout.ts demo.ts
node dist/demo.jsEn la ejecución de este ejemplo, usuario-1 obtuvo una posición aproximada de 34.11: queda fuera al 20 % y dentro al 50 %. usuario-2, con 0.05, entra en ambos. Repite el comando: los grupos se mantienen. El redondeo solo facilita leer la tabla; la decisión utiliza el valor completo.
Con estos diez identificadores entran tres usuarios al 20 %, no exactamente dos. El porcentaje define un umbral sobre la distribución, no una cuota exacta de personas, ni garantiza ese mismo porcentaje de peticiones: algunos usuarios generan más tráfico que otros. En muestras pequeñas la diferencia se nota especialmente.
Al subir el umbral de 20 a 50 conservas a quienes ya estaban dentro y añades otros. Al bajarlo puedes retirar usuarios del grupo. Esto depende de mantener el algoritmo, el nombre de la flag y los identificadores: cambiar cualquiera de ellos puede reasignar personas.
Elige además la unidad adecuada. Si todo un equipo comparte pedidos, quizá convenga usar el identificador de la organización en lugar del de cada persona. No uses el índice de una lista que cambia de orden. Para visitantes anónimos necesitarías una identidad estable con las consideraciones de privacidad correspondientes; este ejemplo requiere usuarios identificados.
Los porcentajes de development, staging y production muestran que cada entorno puede tener una política distinta. Son configuraciones locales y ficticias: no se sincronizan entre servidores ni se actualizan remotamente. En una API real, el identificador se obtiene de la sesión autenticada y la decisión debe respetarse en el servidor. Ocultar un botón no sustituye la autorización de la operación.
Subir el porcentaje requiere algo más que mover un control
Podrías empezar por cuentas internas, pasar a un grupo pequeño de clientes y ampliar cuando el comportamiento sea el esperado. Los porcentajes concretos dependen del tráfico, del impacto de un fallo y de lo que necesitas observar. Pasar de 1 % a 100 % porque transcurrieron diez minutos no demuestra que la función esté lista.
Antes de ampliar, define qué mirarás: errores, latencia y alguna señal del resultado que buscas. En nuestro reporte, también comprobarías que los datos mostrados sean correctos. Relaciona esas señales con la variante utilizada; los promedios globales pueden esconder un problema que afecta a un grupo pequeño. El artículo sobre logs, métricas y trazas explica cómo conectar esas observaciones.
Esto se relaciona con los canary releases: exponer un cambio primero a una población reducida para observarlo antes de extenderlo. Una flag puede controlar esa exposición dentro de la misma versión de la aplicación. También puedes hacer un canary desplegando una versión distinta en una parte de las instancias y enviándole tráfico. Son mecanismos complementarios; un rollout por usuarios no garantiza aislar el consumo de CPU o la base de datos compartida. Microsoft desarrolla estas decisiones en sus prácticas de despliegue seguro.
Si la configuración llega de un servicio externo, comprueba qué ocurre cuando no responde y cuánto tarda un cambio en propagarse. Algunos SDK conservan una copia local: eso ayuda a seguir funcionando, pero una desactivación puede no ser instantánea. Mantén una decisión coherente durante la operación cuando mezclar versiones a mitad del trabajo pueda romperla.
Apagar la flag no deshace lo que ya ocurrió
Supón que el reporte nuevo genera un archivo con columnas incorrectas. Apagar la flag puede dirigir las siguientes solicitudes al generador antiguo. No retira los archivos ya descargados ni cancela necesariamente los trabajos que están en ejecución.
Con cambios de datos, el límite es más importante. Una flag no revierte una migración de esquema, un cobro o un mensaje enviado. Si la nueva ruta escribe un formato que la antigua no puede leer, volver a seleccionarla puede provocar otro fallo. Necesitas compatibilidad entre versiones y un plan de recuperación para esos efectos; cuando intervienen reintentos, la idempotencia ayuda con un problema distinto: evitar repetir una operación.
Ese rollback lógico cambia el camino que ejecutará la aplicación. No equivale a devolver todo el sistema a un estado anterior. Antes de habilitar la función, conviene poder responder: «si la apago, ¿qué deja de pasar y qué queda pendiente de reparar?».
También importa dónde colocas la comprobación. Si ejecutas el trabajo nuevo y consultas la flag solo para decidir qué resultado mostrar, ya produjo sus efectos. Evalúa antes de entrar en la ruta que quieres controlar y comprueba que la alternativa siga funcionando.
No todas las flags deberían quedarse a vivir en el código
El propósito ayuda a decidir cuánto tiempo mantenerlas. Estos nombres son habituales, aunque cada plataforma organiza sus tipos de forma algo distinta:
- Release flags: separan la entrega de una funcionalidad de su activación. Suelen ser temporales: terminado el lanzamiento, se retiran la condición y la ruta antigua.
- Experiment flags: asignan variantes para comparar un resultado. Necesitan grupos estables, una hipótesis y mediciones apropiadas; repartir usuarios por sí solo no constituye un experimento bien diseñado.
- Operational flags: controlan comportamiento operativo, como pausar un cálculo costoso o deshabilitar una integración degradada. Algunas son transitorias; un interruptor de emergencia puede tener sentido de forma permanente, con pruebas y responsables claros.
Unleash distingue, por ejemplo, flags de lanzamiento, experimentación y operación, y trata los kill switches como un tipo separado. Sus tipos y tiempos de vida son convenciones del producto, no plazos universales.
Lo permanente debe ser una decisión. Una flag olvidada no se convierte en un mecanismo operativo solo porque lleve dos años en el repositorio.
Asigna un responsable y una condición para retirarla. Prueba la ruta activada, la desactivada y el comportamiento cuando la configuración no está disponible. Las flags que interactúan añaden combinaciones; identifica cuáles soportas en lugar de asumir que probar cada una por separado cubre cualquier mezcla.
Cuando termines el rollout, retira el código antiguo y la consulta de la flag de las versiones que siguen ejecutándose antes de eliminar su configuración. Revisa también workers y tareas programadas que puedan conservar versiones anteriores. La guía de Unleash sobre flags en código desarrolla estas decisiones de pruebas y limpieza.
Cuándo dejar de mantener tu propio sistema de flags
Para un interruptor por entorno, una configuración sencilla puede bastar. Si necesitas segmentos compartidos, cambios sin reiniciar, historial, permisos para activar funcionalidades y coordinación entre servicios, el trabajo ya no está en escribir otro if. Está en operar ese sistema con fiabilidad.
Hay herramientas que cubren esa necesidad. Estas son algunas diferencias útiles, verificadas en su documentación, sin convertir la elección en un ranking:
- Unleash ofrece estrategias por entorno, segmentos y rollouts graduales. Su documentación distingue el código fuente desde la versión 8, bajo AGPLv3, de las imágenes Docker oficiales, bajo Apache 2.0, y de la edición Enterprise. No conviene resumir todo como una única licencia: revisa el componente y la versión que utilizarás. Disponibilidad y licencias.
- Flagsmith combina flags y configuración remota, con identidades y segmentos. Tiene opciones alojadas y autogestionadas; el repositorio principal utiliza BSD-3-Clause, mientras las prestaciones comerciales requieren revisar su edición. Proyecto oficial y licencia.
- LaunchDarkly es una plataforma comercial con targeting y rollouts progresivos. Sus planes delimitan prestaciones y uso. Que sus SDK de JavaScript tengan licencia Apache 2.0 no convierte el servicio completo en software autogestionable bajo esa licencia.
- OpenFeature cumple otro papel: define una API de evaluación independiente del proveedor. Un provider conecta esa API con el sistema que obtiene o evalúa las flags. No es por sí solo un panel para administrarlas ni un backend de configuración. El SDK de JavaScript es Apache 2.0; la especificación de providers explica la separación. Ayuda a desacoplar la integración, pero no garantiza que dos proveedores tengan las mismas reglas o seleccionen los mismos grupos.
El ejemplo de este artículo enseña la evaluación local, no implementa esas plataformas. Si incorporas una, sigue usando identificadores y atributos mínimos, protege las credenciales de administración y controla quién puede cambiar la configuración. Activar una flag también cambia producción.
Decide también cuándo quitar el interruptor
Para volver al reporte: si solo quieres cambiar una etiqueta y puedes desplegarla directamente, mantener dos versiones quizá añada más trabajo del que evita. Si necesitas probar una experiencia con cuentas concretas, observarla y ampliar su alcance sin preparar otro binario, una flag puede darte ese margen.
Antes de añadirla, deja claras tres cosas: quién recibirá el cambio, qué observarás antes de ampliarlo y qué ocurrirá al apagarlo. Añade una cuarta si es temporal: cuándo vas a borrarla. El lanzamiento termina mejor cuando el equipo puede entender la única ruta que queda, no cuando nadie recuerda por qué todavía existen dos.
