enum en TypeScript y sus alternativas: as const y uniones de literales

El enum es de lo poco de TypeScript que no desaparece al compilar: genera código. Por eso Node 24, que ejecuta los .ts borrando los tipos, no lo acepta. Vemos qué produce, un agujero de los enum numéricos y las dos alternativas que se usan hoy.

Un enum y lo que genera

enum Color {
  Rojo,
  Verde,
  Azul,
}

enum Estado {
  Activo = "activo",
  Pausado = "pausado",
}

console.log(Color.Verde, Color[1], Estado.Pausado);

Al compilarlo a JavaScript, los tipos desaparecen, pero los enum no. Se convierten en objetos construidos con una función:

"use strict";
var Color;
(function (Color) {
    Color[Color["Rojo"] = 0] = "Rojo";
    Color[Color["Verde"] = 1] = "Verde";
    Color[Color["Azul"] = 2] = "Azul";
})(Color || (Color = {}));
var Estado;
(function (Estado) {
    Estado["Activo"] = "activo";
    Estado["Pausado"] = "pausado";
})(Estado || (Estado = {}));
console.log(Color.Verde, Color[1], Estado.Pausado);

El enum numérico crea el objeto en los dos sentidos: Color.Verde es 1 y Color[1] es "Verde".

Node 24 no lo ejecuta

Node 24 ejecuta TypeScript quitando las anotaciones de tipo, sin traducir nada. Un enum no se puede quitar sin más, así que falla:

node con-enum.ts
salidanode:internal/modules/run_main:107
    triggerUncaughtException(
    ^

file:///w/enum/con-enum.ts:1
  > enum Color {
      Rojo,
      Verde,
      Azul,
  > }

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
  code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
}

Node.js v24.22.0

Se puede activar la traducción con una opción experimental, que avisa de que puede cambiar:

node --experimental-transform-types con-enum.ts
salida(node:45) ExperimentalWarning: Transform Types is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
1 Verde pausado

Y la opción erasableSyntaxOnly de TypeScript avisa antes de ejecutar, en todos los enum del proyecto:

npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es --erasableSyntaxOnly con-enum.ts
tsccon-enum.ts(1,6): error TS1294: Esta sintaxis no se permite cuando "erasableSyntaxOnly" está habilitado.
con-enum.ts(7,6): error TS1294: Esta sintaxis no se permite cuando "erasableSyntaxOnly" está habilitado.

El agujero de los enum numéricos

Desde TypeScript 5.0, pasar el literal 42 donde se espera un Color es un error. Pero un number cualquiera sigue entrando:

enum Color {
  Rojo,
  Verde,
  Azul,
}
function pintar(c: Color): string {
  return `color número ${c}`;
}
console.log(pintar(Color.Azul));

const leidoDeUnFormulario: number = 42;
console.log(pintar(leidoDeUnFormulario)); // ¿42? No hay ningún color 42, y TypeScript lo acepta
npx tsc --strict --target esnext --module esnext --locale es numerico.ts
node numerico.js
salidacolor número 2
color número 42

Compila sin errores y pinta un «color número 42» que no existe. Un número leído de un formulario o de una API pasa sin comprobación.

Alternativa 1: un objeto as const

Un objeto normal con as const conserva los valores exactos. De él se saca una unión con typeof y keyof. Se usa igual que un enum, pero es JavaScript puro:

// La alternativa: un objeto normal «congelado» con as const, y una unión sacada de él.
const Estado = {
  Activo: "activo",
  Pausado: "pausado",
  Cancelado: "cancelado",
} as const;

type Estado = (typeof Estado)[keyof typeof Estado]; // "activo" | "pausado" | "cancelado"

function etiqueta(e: Estado): string {
  switch (e) {
    case Estado.Activo:
      return "En curso";
    case Estado.Pausado:
      return "En pausa";
    case Estado.Cancelado:
      return "Cancelada";
  }
}

console.log(etiqueta(Estado.Pausado));
console.log(etiqueta("cancelado")); // también vale el texto tal cual
console.log(Object.values(Estado));  // se puede recorrer: es un objeto de verdad

Ábrelo en el editor y cámbialo →

npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es constante.ts
node constante.ts
salidaEn pausa
Cancelada
[ 'activo', 'pausado', 'cancelado' ]

Y no acepta valores que no están:

const Estado = { Activo: "activo", Pausado: "pausado" } as const;
type Estado = (typeof Estado)[keyof typeof Estado];
function etiqueta(e: Estado): string {
  return e;
}
etiqueta("borrado");
tscconstante-error.ts(6,10): error TS2345: No se puede asignar un argumento de tipo ""borrado"" al parámetro de tipo "Estado".

Alternativa 2: una unión de literales

Si no necesitas recorrer los valores ni darles nombre, una unión de textos basta:

// Si no hace falta recorrer los valores, basta con una unión de literales.
type Talla = "chica" | "mediana" | "grande";

const precios: Record<Talla, number> = { chica: 45, mediana: 55, grande: 65 };

function cobrar(t: Talla, cantidad: number): string {
  return `$${(precios[t] * cantidad).toFixed(2)} MXN`;
}
console.log(cobrar("mediana", 3));

Ábrelo en el editor y cámbialo →

npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es union.ts
node union.ts
salida$165.00 MXN
enumObjeto as constUnión de literales
Genera códigoSíEl objeto, que es JavaScript normalNo
Node 24 lo ejecuta tal cualNoSíSí
Se pueden recorrer los valoresSíSí, con Object.valuesNo
Acepta un number cualquieraSí, si es numéricoNoNo
Acepta el texto directamenteNo: hay que escribir Estado.ActivoSíSí

Todo el código de esta página se comprobó con TypeScript 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. Documentación oficial: Objects vs Enums en el manual de TypeScript.