Const type parameters en TypeScript

Avanzado
TypeScript
TypeScript
Actualizado: 17/04/2026

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 - Autor del tutorial

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.