El problema de la ampliación en genéricos
Cuando una función genérica recibe un literal, TypeScript amplia el tipo inferido al tipo general correspondiente salvo que el valor esté congelado con as const. Para una función que devuelve sus argumentos, el resultado habitual es poco específico.
function capturar<T>(valor: T): T {
return valor
}
const ruta = capturar(["usuarios", "nuevo"])
// tipo inferido: string[]
const config = capturar({ modo: "oscuro", idioma: "es" })
// tipo inferido: { modo: string; idioma: string }
El usuario puede forzar la inferencia estrecha añadiendo as const a cada llamada, pero esto contamina el código de llamada y es fácil olvidarlo.
const ruta = capturar(["usuarios", "nuevo"] as const)
// tipo: readonly ["usuarios", "nuevo"]
const config = capturar({ modo: "oscuro", idioma: "es" } as const)
// tipo: { readonly modo: "oscuro"; readonly idioma: "es" }
En librerías que dependen de valores conocidos en compilación (rutas de API, permisos, claves de estado), la ampliación pierde información crítica para el autocompletado y la comprobación de tipos.
El modificador const en parámetros de tipo
TypeScript permite anteponer la palabra clave const a un parámetro de tipo genérico. El compilador trata el argumento inferido como si el llamante hubiera escrito as const.
function capturar<const T>(valor: T): T {
return valor
}
const ruta = capturar(["usuarios", "nuevo"])
// tipo: readonly ["usuarios", "nuevo"]
const config = capturar({ modo: "oscuro", idioma: "es" })
// tipo: { readonly modo: "oscuro"; readonly idioma: "es" }
El comportamiento aplica de forma recursiva a arrays, tuplas y literales de objeto anidados, igual que una aserción as const. El llamante recibe todos los beneficios sin añadir ninguna sintaxis adicional.
El modificador const solo afecta a la inferencia del parámetro de tipo, no al valor en tiempo de ejecución. La variable sigue siendo mutable salvo que el tipo de retorno sea inmutable.
Cuándo aplica y cuándo no
El modificador es relevante en funciones que consumen literales que el usuario pretende mantener estrechos. No tiene efecto si el llamante usa una variable con tipo ya ampliado.
function etiquetas<const T extends readonly string[]>(valores: T): T {
return valores
}
etiquetas(["alta", "media", "baja"])
// T: readonly ["alta", "media", "baja"]
const niveles = ["alta", "media", "baja"]
etiquetas(niveles)
// T: string[], ya estaba ampliado antes de llamar
Caso práctico: validar rutas tipadas
Un caso donde const brilla es en APIs que aceptan rutas tipadas y validan en compilación la presencia de parámetros. El modificador permite inferir el literal exacto que se pasó.
type ExtraerParams<Ruta extends string> =
Ruta extends `${string}:${infer Param}/${infer Resto}`
? Param | ExtraerParams<`/${Resto}`>
: Ruta extends `${string}:${infer Param}`
? Param
: never
function definirRuta<const R extends string>(ruta: R, handler: (params: Record<ExtraerParams<R>, string>) => void) {
return { ruta, handler }
}
definirRuta("/usuarios/:id/pedidos/:pedidoId", (params) => {
// params: { id: string; pedidoId: string }
console.log(params.id, params.pedidoId)
})
Sin const, el compilador ampliaria "/usuarios/:id/..." a string y ExtraerParams devolveria never, perdiendo la comprobación fina sobre los parámetros.
Combinación con satisfies
El modificador const en el parámetro y el operador satisfies en el llamante cumplen objetivos complementarios: el primero preserva la forma literal del argumento, el segundo comprueba que un valor cumple un contrato sin ampliarlo.
type MetodoHttp = "GET" | "POST" | "PUT" | "DELETE"
type Endpoint = {
metodo: MetodoHttp
ruta: string
}
function registrar<const E extends readonly Endpoint[]>(endpoints: E): E {
return endpoints
}
const rutas = registrar([
{ metodo: "GET", ruta: "/salud" },
{ metodo: "POST", ruta: "/eventos" }
] satisfies readonly Endpoint[])
// rutas[0].metodo: "GET" literal, no MetodoHttp
La combinación permite escribir bibliotecas con firmas expresivas y llamantes que no necesitan as const ni casts. El resultado es un tipo final con la máxima especificidad posible.
Librerías que se benefician
Varias librerías del ecosistema adoptaron const type parameters para simplificar sus APIs:
- Routers tipados como Hono o tRPC para inferir parámetros desde una ruta literal
- Validadores como Zod o Valibot al construir esquemas a partir de enums literales
- Clientes de bases de datos que aceptan nombres de columna como literales
- Máquinas de estado donde los nombres de estado y eventos se declaran inline
El beneficio no es únicamente ergonómico. Al evitar que el usuario recuerde aplicar as const, la superficie de error se reduce y las sugerencias del IDE pasan a ofrecer valores exactos en vez de string.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender cómo TypeScript amplía los argumentos de tipo en funciones genéricas. Usar el modificador const en parámetros de tipo para preservar literales y tuplas. Aplicar const type parameters en APIs que reciben configuraciones, rutas y listas constantes. Combinar const type parameters con as const y satisfies para obtener tipos máximamente específicos sin sacrificar ergonomía.