Tipos literales de string, number y boolean
Un tipo literal restringe una variable a un valor específico en lugar de aceptar cualquier valor de su tipo base. Mientras que string admite cualquier cadena, el tipo literal "norte" solo admite exactamente esa cadena:

let dirección: "norte" | "sur" | "este" | "oeste"
dirección = "norte" // Valido
dirección = "sur" // Valido
// dirección = "arriba" // Error: Type '"arriba"' is not assignable
Tipos literales de string
Los tipos literales de cadena son los más utilizados. Permiten definir conjuntos cerrados de opciones válidas:
type MetodoHTTP = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
function realizarPeticion(url: string, método: MetodoHTTP): string {
return `${método} ${url}`
}
realizarPeticion("/api/usuarios", "GET") // Valido
realizarPeticion("/api/usuarios", "POST") // Valido
// realizarPeticion("/api/usuarios", "TRACE") // Error: no es un MetodoHTTP válido
Son especialmente útiles en configuraciones y opciones de componentes:
type Tema = "claro" | "oscuro" | "sistema"
type Tamano = "pequeño" | "mediano" | "grande"
type Variante = "primario" | "secundario" | "peligro" | "exito"
type OpcionesBoton = {
texto: string
variante: Variante
tamano: Tamano
deshabilitado: boolean
}
function crearBoton(opciones: OpcionesBoton): string {
return `<button class="${opciones.variante} ${opciones.tamano}">${opciones.texto}</button>`
}
const boton = crearBoton({
texto: "Guardar",
variante: "primario",
tamano: "mediano",
deshabilitado: false
})
Tipos literales numéricos
Los tipos literales numéricos restringen los valores a números específicos:
type DiaSemana = 1 | 2 | 3 | 4 | 5 | 6 | 7
type Puerto = 80 | 443 | 3000 | 8080
function nombreDia(dia: DiaSemana): string {
const nombres = ["Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sabado", "Domingo"]
return nombres[dia - 1]
}
console.log(nombreDia(1)) // "Lunes"
console.log(nombreDia(5)) // "Viernes"
// nombreDia(8) // Error: Argument of type '8' is not assignable to parameter of type 'DiaSemana'
También sirven para representar códigos de estado o niveles fijos:
type CodigoHTTP = 200 | 201 | 204 | 400 | 401 | 403 | 404 | 500
type NivelLog = 0 | 1 | 2 | 3
function interpretarCodigo(código: CodigoHTTP): string {
if (código >= 200 && código < 300) return "Exito"
if (código >= 400 && código < 500) return "Error del cliente"
return "Error del servidor"
}
Tipos literales booleanos
Los tipos literales booleanos true y false permiten restringir una propiedad a un valor booleano exacto:
type RespuestaExitosa<T> = {
exito: true
datos: T
}
type RespuestaFallida = {
exito: false
error: string
código: number
}
type Respuesta<T> = RespuestaExitosa<T> | RespuestaFallida
function procesarRespuesta(respuesta: Respuesta<string[]>): string {
if (respuesta.exito === true) {
return `Datos: ${respuesta.datos.join(", ")}`
}
return `Error ${respuesta.código}: ${respuesta.error}`
}
Inferencia de literales con const y as const
Cuando se declara una variable con const, TypeScript infiere el tipo literal exacto. Con let, infiere el tipo amplio:
const modo = "producción" // Tipo: "producción"
let modoVariable = "producción" // Tipo: string
La aserción as const permite obtener tipos literales en contextos donde TypeScript inferiría tipos amplios:
const config = {
host: "localhost",
puerto: 3000,
modo: "desarrollo"
} as const
// Tipo: { readonly host: "localhost"; readonly puerto: 3000; readonly modo: "desarrollo" }
// Sin as const, el tipo sería { host: string; puerto: number; modo: string }
Esto es especialmente útil con arrays, que se convierten en tuplas de solo lectura:
const colores = ["rojo", "verde", "azul"] as const
// Tipo: readonly ["rojo", "verde", "azul"]
type Color = typeof colores[number]
// Tipo: "rojo" | "verde" | "azul"
function esColorValido(valor: string): valor is Color {
return (colores as readonly string[]).includes(valor)
}
Uniones discriminadas
Las uniones discriminadas (también llamadas tagged unions) son un patrón que combina tipos de unión con un campo discriminante común. Este campo tiene un tipo literal diferente en cada variante, lo que permite a TypeScript identificar exactamente qué variante se está usando:
type Circulo = {
forma: "circulo"
radio: number
}
type Rectangulo = {
forma: "rectangulo"
ancho: number
alto: number
}
type Triangulo = {
forma: "triangulo"
base: number
altura: number
}
type Figura = Circulo | Rectangulo | Triangulo
El campo forma actúa como discriminante. Cada variante tiene un valor literal distinto para este campo, y TypeScript lo usa para estrechar el tipo:
function calcularArea(figura: Figura): number {
switch (figura.forma) {
case "circulo":
// TypeScript sabe que figura es Circulo
return Math.PI * figura.radio ** 2
case "rectangulo":
// TypeScript sabe que figura es Rectangulo
return figura.ancho * figura.alto
case "triangulo":
// TypeScript sabe que figura es Triangulo
return (figura.base * figura.altura) / 2
}
}
console.log(calcularArea({ forma: "circulo", radio: 5 })) // 78.54
console.log(calcularArea({ forma: "rectangulo", ancho: 4, alto: 6 })) // 24
Gestión de estados con uniones discriminadas
Las uniones discriminadas son ideales para modelar estados de una aplicación:
type EstadoCarga = {
estado: "cargando"
}
type EstadoExito<T> = {
estado: "exito"
datos: T
}
type EstadoError = {
estado: "error"
mensaje: string
reintentable: boolean
}
type EstadoPeticion<T> = EstadoCarga | EstadoExito<T> | EstadoError
function renderizar(estado: EstadoPeticion<string[]>): string {
switch (estado.estado) {
case "cargando":
return "Cargando..."
case "exito":
return `Elementos: ${estado.datos.join(", ")}`
case "error":
const reintento = estado.reintentable ? " (puede reintentar)" : ""
return `Error: ${estado.mensaje}${reintento}`
}
}
const cargando: EstadoPeticion<string[]> = { estado: "cargando" }
const exito: EstadoPeticion<string[]> = { estado: "exito", datos: ["a", "b"] }
const error: EstadoPeticion<string[]> = { estado: "error", mensaje: "Timeout", reintentable: true }
console.log(renderizar(cargando)) // "Cargando..."
console.log(renderizar(exito)) // "Elementos: a, b"
console.log(renderizar(error)) // "Error: Timeout (puede reintentar)"
Acciones en reducers
Las uniones discriminadas encajan perfectamente en patrones de tipo reducer:
type Tarea = {
id: number
texto: string
completada: boolean
}
type AccionAgregar = {
tipo: "AGREGAR"
texto: string
}
type AccionCompletar = {
tipo: "COMPLETAR"
id: number
}
type AccionEliminar = {
tipo: "ELIMINAR"
id: number
}
type AccionEditar = {
tipo: "EDITAR"
id: number
texto: string
}
type Accion = AccionAgregar | AccionCompletar | AccionEliminar | AccionEditar
function reducer(estado: Tarea[], accion: Accion): Tarea[] {
switch (accion.tipo) {
case "AGREGAR":
return [...estado, { id: Date.now(), texto: accion.texto, completada: false }]
case "COMPLETAR":
return estado.map(t => t.id === accion.id ? { ...t, completada: !t.completada } : t)
case "ELIMINAR":
return estado.filter(t => t.id !== accion.id)
case "EDITAR":
return estado.map(t => t.id === accion.id ? { ...t, texto: accion.texto } : t)
}
}
Pattern matching exhaustivo
El pattern matching exhaustivo garantiza que todos los casos posibles de una unión discriminada están cubiertos. TypeScript lo comprueba mediante el tipo never: si después de manejar todos los casos el tipo residual no es never, hay un caso sin cubrir:
type Operación =
| { tipo: "sumar"; a: number; b: number }
| { tipo: "restar"; a: number; b: number }
| { tipo: "multiplicar"; a: number; b: number }
| { tipo: "dividir"; a: number; b: number }
function calcular(op: Operación): number {
switch (op.tipo) {
case "sumar":
return op.a + op.b
case "restar":
return op.a - op.b
case "multiplicar":
return op.a * op.b
case "dividir":
if (op.b === 0) throw new Error("Division por cero")
return op.a / op.b
default: {
const _exhaustivo: never = op
throw new Error(`Operación no soportada: ${_exhaustivo}`)
}
}
}
Si se añade una nueva variante a Operación sin actualizar el switch, TypeScript emitirá un error en el default porque el tipo residual ya no es never.
Se puede extraer la comprobación exhaustiva a una función auxiliar reutilizable:
function exhaustivo(valor: never): never {
throw new Error(`Caso no contemplado: ${JSON.stringify(valor)}`)
}
type Permiso = "leer" | "escribir" | "administrar"
function describirPermiso(permiso: Permiso): string {
switch (permiso) {
case "leer":
return "Solo lectura"
case "escribir":
return "Lectura y escritura"
case "administrar":
return "Acceso total"
default:
return exhaustivo(permiso)
}
}
Exhaustividad con if-else
El pattern matching exhaustivo no se limita a switch. También funciona con cadenas de if-else:
type Moneda = "EUR" | "USD" | "GBP"
function simbolo(moneda: Moneda): string {
if (moneda === "EUR") return "e"
if (moneda === "USD") return "$"
if (moneda === "GBP") return "lb"
const _check: never = moneda
throw new Error(`Moneda desconocida: ${_check}`)
}
Ejemplo completo: sistema de eventos
Un ejemplo que combina uniones discriminadas, tipos literales y pattern matching exhaustivo:
type EventoUsuario =
| { tipo: "registro"; email: string; nombre: string }
| { tipo: "login"; email: string; ip: string }
| { tipo: "logout"; sesionId: string }
| { tipo: "cambio_password"; email: string; exitoso: boolean }
type EventoSistema =
| { tipo: "inicio"; versión: string }
| { tipo: "error"; código: number; mensaje: string }
| { tipo: "mantenimiento"; duracionMinutos: number }
type Evento = EventoUsuario | EventoSistema
function formatearEvento(evento: Evento): string {
switch (evento.tipo) {
case "registro":
return `Nuevo usuario: ${evento.nombre} (${evento.email})`
case "login":
return `Login: ${evento.email} desde ${evento.ip}`
case "logout":
return `Logout: sesion ${evento.sesionId}`
case "cambio_password":
const estado = evento.exitoso ? "exitoso" : "fallido"
return `Cambio password ${estado}: ${evento.email}`
case "inicio":
return `Sistema iniciado v${evento.versión}`
case "error":
return `Error ${evento.código}: ${evento.mensaje}`
case "mantenimiento":
return `Mantenimiento programado: ${evento.duracionMinutos} minutos`
default:
return exhaustivo(evento)
}
}
function exhaustivo(valor: never): never {
throw new Error(`Caso no contemplado: ${JSON.stringify(valor)}`)
}
const eventos: Evento[] = [
{ tipo: "inicio", versión: "2.0" },
{ tipo: "registro", email: "ana@ejemplo.com", nombre: "Ana" },
{ tipo: "login", email: "ana@ejemplo.com", ip: "192.168.1.1" },
{ tipo: "error", código: 500, mensaje: "Base de datos no disponible" }
]
for (const evento of eventos) {
console.log(formatearEvento(evento))
}
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
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 los tipos literales de string, number y boolean en TypeScript. Crear uniones de literales para representar conjuntos finitos de valores. Dominar las uniones discriminadas con campos discriminantes comunes. Aplicar pattern matching exhaustivo con switch y never. Usar as const para inferir tipos literales automáticamente.