El operador satisfies
Cuando se anota una variable con un tipo directamente, TypeScript amplia el tipo inferido al tipo declarado, perdiendo información específica que podría ser útil en el resto del código:

type Color = "rojo" | "verde" | "azul"
type Paleta = Record<string, Color | number[]>
const paleta1: Paleta = {
rojo: [255, 0, 0],
verde: "verde",
azul: [0, 0, 255]
}
// TypeScript solo sabe que paleta1.rojo es Color | number[]
// paleta1.rojo.map(x => x * 2) // Error: 'map' no existe en Color | number[]
El operador satisfies resuelve este problema. Válida que una expresión cumple con un tipo determinado sin ampliar el tipo inferido:
const paleta = {
rojo: [255, 0, 0],
verde: "verde",
azul: [0, 0, 255]
} satisfies Paleta
paleta.rojo.map(x => x * 2) // Sin error: TypeScript sabe que es number[]
paleta.verde.toUpperCase() // Sin error: TypeScript sabe que es string
El operador satisfies válida sin borrar. La comprobación de tipo ocurre en tiempo de compilación, pero el tipo inferido que usa el compilador para el resto del código sigue siendo el más específico posible.
Validación de propiedades conocidas
Al igual que la anotación directa, satisfies detecta propiedades desconocidas en literales de objeto:
type ConfigBase = {
host: string
puerto: number
tls: boolean
}
// const config = {
// host: "localhost",
// puerto: 5432,
// tls: false,
// extra: true // Error: propiedad desconocida
// } satisfies ConfigBase
Pero cuando el objeto es válido, las propiedades mantienen sus tipos específicos:
const config = {
host: "localhost",
puerto: 5432,
tls: false
} satisfies ConfigBase
// config.host es "localhost" (literal), no string
// config.puerto es 5432 (literal), no number
Uso con tipos union
satisfies resulta especialmente útil con Record y tipos union donde cada propiedad puede tener tipos distintos:
type ValorConfig = string | number | boolean | string[]
type Configuración = Record<string, ValorConfig>
const config = {
apiUrl: "https://api.ejemplo.com",
maxReintentos: 3,
debug: true,
origenesPermitidos: ["https://app.ejemplo.com"]
} satisfies Configuración
config.apiUrl.startsWith("https") // Es string
config.maxReintentos.toFixed(2) // Es number
config.origenesPermitidos.join(", ") // Es string[]
Sin satisfies, al anotar config: Configuración, cada propiedad sería ValorConfig y se perderia la información de tipo específica.
Const assertions con as const
Por defecto, TypeScript infiere el tipo más amplio posible para los valores. Con const a nivel de variable ya se infieren literales para primitivos, pero los arrays y objetos siguen teniendo tipos amplios:
const colores = ["rojo", "verde", "azul"]
// tipo: string[]
const punto = { x: 10, y: 20 }
// tipo: { x: number; y: number }
La const assertion (as const) instruye a TypeScript para inferir el tipo más estrecho y de solo lectura para toda la expresión:
const colores = ["rojo", "verde", "azul"] as const
// tipo: readonly ["rojo", "verde", "azul"]
const punto = { x: 10, y: 20 } as const
// tipo: { readonly x: 10; readonly y: 20 }
Cada valor se convierte en su tipo literal y todas las propiedades se marcan como readonly.
Derivar tipos de union desde arrays
Uno de los usos más utiles de as const es derivar tipos de union a partir de arrays de constantes:
const ROLES = ["admin", "editor", "lector", "invitado"] as const
type Rol = typeof ROLES[number]
// "admin" | "editor" | "lector" | "invitado"
function asignarRol(usuario: string, rol: Rol): void {
console.log(`${usuario} tiene el rol ${rol}`)
}
asignarRol("Ana", "admin")
// asignarRol("Ana", "gestor") // Error: no es un rol válido
La fuente de verdad es el array. Si se añade un elemento, el tipo Rol se actualiza automáticamente sin tocar la definición de tipo.
Objetos tipo enum con as const
Las const assertions con objetos son frecuentemente preferidas sobre los enums en TypeScript moderno, porque generan JavaScript más simple:
const Direccion = {
Arriba: "ARRIBA",
Abajo: "ABAJO",
Izquierda: "IZQUIERDA",
Derecha: "DERECHA"
} as const
type Direccion = typeof Direccion[keyof typeof Direccion]
// "ARRIBA" | "ABAJO" | "IZQUIERDA" | "DERECHA"
function mover(dirección: Direccion): void {
console.log(`Moviendo: ${dirección}`)
}
mover(Direccion.Arriba)
mover("ABAJO")
// mover("DIAGONAL") // Error
A diferencia de los enums, los objetos con as const no generan código JavaScript adicional. El resultado es un objeto JavaScript plano con tipos literales.
Readonly profundo
as const aplica readonly de forma recursiva en objetos anidados:
const árbol = {
valor: 1,
izquierda: {
valor: 2,
hijos: [3, 4]
},
derecha: {
valor: 5,
hijos: [6]
}
} as const
// árbol.valor: 1 (readonly)
// árbol.izquierda.valor: 2 (readonly)
// árbol.izquierda.hijos: readonly [3, 4]
// árbol.valor = 10 // Error: readonly
// árbol.izquierda.hijos.push(5) // Error: readonly
Configuraciones inmutables
Los objetos de configuración se benefician de as const para garantizar inmutabilidad y tipos literales:
const CONFIG = {
API_URL: "https://api.ejemplo.com",
TIMEOUT: 5000,
REINTENTOS: 3,
ENTORNOS: ["desarrollo", "staging", "producción"]
} as const
type Entorno = typeof CONFIG.ENTORNOS[number]
// "desarrollo" | "staging" | "producción"
function desplegar(entorno: Entorno): void {
console.log(`Desplegando en ${entorno}, timeout: ${CONFIG.TIMEOUT}`)
}
desplegar("producción")
// desplegar("test") // Error
Combinando satisfies con as const
La combinación as const satisfies Tipo ofrece lo mejor de ambos mundos: tipos literales inmutables con validación de contrato:
type Tema = "claro" | "oscuro" | "sistema"
type ConfigTema = {
tema: Tema
radio: number
fuente: string
}
const configTema = {
tema: "oscuro",
radio: 8,
fuente: "Inter"
} as const satisfies ConfigTema
// configTema.tema es "oscuro" (literal), no Tema
// configTema.radio es 8 (literal), no number
// configTema.fuente es "Inter" (literal), no string
// Y TypeScript ha validado que cumple con ConfigTema
El orden importa: primero as const, luego satisfies. De esta forma el compilador aplica primero la inferencia literal y luego válida el cumplimiento del tipo.
Definiciones de rutas validadas
type Ruta = {
path: string
titulo: string
autenticada: boolean
}
type MapaRutas = Record<string, Ruta>
const RUTAS = {
INICIO: { path: "/", titulo: "Inicio", autenticada: false },
PERFIL: { path: "/perfil", titulo: "Mi perfil", autenticada: true },
AJUSTES: { path: "/ajustes", titulo: "Ajustes", autenticada: true }
} as const satisfies MapaRutas
// RUTAS.INICIO.path es "/" (literal)
// RUTAS.PERFIL.autenticada es true (literal)
// TypeScript válida que cada entrada cumple con Ruta
Mapa de errores tipado
type MensajeError = { código: number; mensaje: string }
type MapaErrores = Record<string, MensajeError>
const ERRORES = {
NO_ENCONTRADO: { código: 404, mensaje: "Recurso no encontrado" },
NO_AUTORIZADO: { código: 401, mensaje: "Sin autorizacion" },
SERVIDOR: { código: 500, mensaje: "Error interno del servidor" }
} as const satisfies MapaErrores
// ERRORES.NO_ENCONTRADO.código es 404 (literal), no number
// TypeScript sabe que "SERVIDOR" es una clave válida
type CodigoError = typeof ERRORES[keyof typeof ERRORES]["código"]
// 404 | 401 | 500
Configuración de colores validada
type OpcionesColor = {
primario: string
secundario: string
fondo: string
}
const coloresTema = {
primario: "#3178c6",
secundario: "#235a97",
fondo: "#ffffff"
} as const satisfies OpcionesColor
// coloresTema.primario es "#3178c6" (literal)
// Si se añade una propiedad extra, satisfies detecta el error
// Si se omite "fondo", satisfies detecta el error
Cuando usar cada herramienta
Cada una de las tres herramientas tiene un propósito diferente en el sistema de tipos:
type Rol = "admin" | "editor" | "lector"
type ConfigUsuario = { rol: Rol; activo: boolean }
// Anotación directa (:): el tipo de la variable ES ConfigUsuario
const u1: ConfigUsuario = { rol: "admin", activo: true }
// u1.rol es Rol (amplio), no "admin"
// as const: inmutabilidad total, todos los valores son literales
const u2 = { rol: "admin", activo: true } as const
// u2.rol es "admin", u2.activo es true
// Pero no se válida que "admin" sea un Rol válido
// satisfies: válida contrato, conserva inferencia específica
const u3 = { rol: "admin", activo: true } satisfies ConfigUsuario
// u3.rol es "admin" (literal), validado como Rol
// as const satisfies: validación + literales + readonly
const u4 = { rol: "admin", activo: true } as const satisfies ConfigUsuario
// u4.rol es "admin" (literal readonly), validado como Rol
// u4.activo es true (literal), no boolean
Anotación directa (:) es apropiada cuando:
- La variable debe tener exactamente ese tipo en el scope actual
- Se necesita asignabilidad posterior a otras variables del mismo tipo
- El tipo genérico es el adecuado para el contexto
satisfies es apropiado cuando:
- Se necesita validar que un objeto cumple un contrato
- Se quiere mantener el tipo inferido más específico para el autocompletado
- Se construyen objetos de configuración o constantes tipadas
as const es apropiado cuando:
- Se necesitan tipos literales e inmutabilidad
- Se quiere derivar tipos de union desde arrays o valores de objetos
- Se construyen constantes que actuan como enums
as const satisfies es apropiado cuando:
- Se necesitan las tres garantias: validación, literales e inmutabilidad
- Se definen constantes que deben cumplir un contrato estricto
- Se construyen mapas de configuración que sirven como fuente de verdad
Patrón avanzado: constantes exhaustivas
La combinación as const satisfies permite verificar que un mapa cubre todos los casos de una union:
type EstadoPedido = "pendiente" | "enviado" | "entregado" | "cancelado"
type MapaEstados = Record<EstadoPedido, { etiqueta: string; color: string }>
const ESTADOS = {
pendiente: { etiqueta: "Pendiente", color: "#f59e0b" },
enviado: { etiqueta: "Enviado", color: "#3b82f6" },
entregado: { etiqueta: "Entregado", color: "#10b981" },
cancelado: { etiqueta: "Cancelado", color: "#ef4444" }
} as const satisfies MapaEstados
// Si se omite un estado, TypeScript muestra un error
// Los valores son literales: ESTADOS.pendiente.color es "#f59e0b"
function obtenerEtiqueta(estado: EstadoPedido): string {
return ESTADOS[estado].etiqueta
}
Este patrón garantiza que cada vez que se añade un nuevo valor a la union EstadoPedido, el compilador obliga a actualizar el mapa ESTADOS, evitando casos no cubiertos.
type Permiso = "leer" | "escribir" | "eliminar" | "administrar"
type DescripcionPermisos = Record<Permiso, string>
const PERMISOS = {
leer: "Puede ver recursos",
escribir: "Puede crear y editar recursos",
eliminar: "Puede eliminar recursos",
administrar: "Acceso total al sistema"
} as const satisfies DescripcionPermisos
type PermisoDisponible = keyof typeof PERMISOS
// "leer" | "escribir" | "eliminar" | "administrar"
La interacción entre satisfies, as const y las anotaciones de tipo directas cubre todo el espectro de necesidades de validación y especificidad en TypeScript moderno, permitiendo elegir el nivel exacto de control que cada situación requiere.
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 el operador satisfies para validar tipos sin ampliar la inferencia, dominar as const para obtener tipos literales inmutables, combinar satisfies con as const para máxima seguridad y especificidad, y aplicar estos patrones en configuraciones, definiciones de rutas y objetos tipo enum.