TypeScript trae de serie una serie de tipos utilitarios: transforman un tipo que ya tienes en otro, sin copiarlo a mano. Si tu Usuario cambia, todo lo que derivaste de él cambia solo. Estos son los que se usan a diario.
También te puede interesar
Un tipo de partida y sus variantes
interface Usuario {
id: number;
nombre: string;
correo: string;
telefono?: string;
creado: Date;
}
// Partial: todo opcional. Ideal para «cambia solo lo que venga».
function actualizar(u: Usuario, cambios: Partial<Omit<Usuario, "id" | "creado">>): Usuario {
return { ...u, ...cambios };
}
// Pick: solo algunas propiedades. Lo que se enseña en una lista pública.
type UsuarioPublico = Pick<Usuario, "id" | "nombre">;
// Omit: todas menos algunas. Lo que manda el formulario de alta (el id y la fecha los pone el servidor).
type NuevoUsuario = Omit<Usuario, "id" | "creado">;
// Required: lo contrario de Partial.
type UsuarioCompleto = Required<Usuario>;
// Record: un objeto con claves conocidas y valores del mismo tipo.
type Rol = "admin" | "editor" | "lector";
const permisos: Record<Rol, string[]> = {
admin: ["leer", "escribir", "borrar"],
editor: ["leer", "escribir"],
lector: ["leer"],
};
// Readonly: nadie puede modificarlo después.
const ana: Readonly<Usuario> = {
id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date("2026-10-20T10:00:00Z"),
};
const nueva = actualizar(ana, { telefono: "55 1234 5678" });
const publico: UsuarioPublico = { id: nueva.id, nombre: nueva.nombre };
const alta: NuevoUsuario = { nombre: "Luis", correo: "luis@ejemplo.com" };
console.log(nueva);
console.log(publico, alta);
console.log(permisos.editor);Ábrelo en el editor y cámbialo →
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es usuario.ts
node usuario.ts
salida{
id: 1,
nombre: 'Ana',
correo: 'ana@ejemplo.com',
creado: 2026-10-20T10:00:00.000Z,
telefono: '55 1234 5678'
}
{ id: 1, nombre: 'Ana' } { nombre: 'Luis', correo: 'luis@ejemplo.com' }
[ 'leer', 'escribir' ]| Utilitario | Qué hace | Para qué se usa |
|---|---|---|
Partial<T> | Todas las propiedades, opcionales | Actualizar solo lo que venga |
Required<T> | Todas, obligatorias | Un objeto ya completo, sin huecos |
Readonly<T> | Todas, de solo lectura | Configuración o datos que no deben cambiar |
Pick<T, "a" | "b"> | Solo esas propiedades | Lo que se enseña en público |
Omit<T, "a" | "b"> | Todas menos esas | Formularios de alta sin id ni fechas |
Record<K, V> | Un objeto con claves K y valores V | Tablas: permisos por rol, precios por talla |
Pick elige campos; Omit los quita. Esquema: decodigo.com.Lo que impiden
Los tipos derivados son tan estrictos como los escritos a mano. Este archivo comete cuatro errores, uno por utilitario:
interface Usuario {
id: number;
nombre: string;
correo: string;
telefono?: string;
creado: Date;
}
type Rol = "admin" | "editor" | "lector";
const ana: Readonly<Usuario> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date() };
ana.nombre = "Ana María";
const publico: Pick<Usuario, "id" | "nombre"> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com" };
const permisos: Record<Rol, string[]> = {
admin: ["leer", "escribir", "borrar"],
editor: ["leer", "escribir"],
};
const completo: Required<Usuario> = { id: 1, nombre: "Ana", correo: "ana@ejemplo.com", creado: new Date() };Ábrelo en el editor y cámbialo →
npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es errores.ts
tscerrores.ts(11,5): error TS2540: No se puede asignar a "nombre" porque es una propiedad de solo lectura.
errores.ts(13,73): error TS2353: El literal de objeto solo puede especificar propiedades conocidas y 'correo' no existe en el tipo 'Pick<Usuario, "id" | "nombre">'.
errores.ts(15,7): error TS2741: La propiedad "lector" falta en el tipo "{ admin: string[]; editor: string[]; }", pero es obligatoria en el tipo "Record<Rol, string[]>".
errores.ts(20,7): error TS2741: La propiedad "telefono" falta en el tipo "{ id: number; nombre: string; correo: string; creado: Date; }", pero es obligatoria en el tipo "Required<Usuario>".El tercero es el más útil en la práctica: si mañana añades el rol "auditor", cada Record<Rol, …> del programa dejará de compilar hasta que le des su valor. Nada se queda sin configurar por olvido.
Sacar tipos de una función
A veces el tipo ya está escrito, implícito, en una función. Estos utilitarios lo extraen en lugar de repetirlo:
// Utilitarios que sacan tipos de una función que ya existe.
async function buscarPedido(id: number, conDetalle: boolean) {
return { id, total: 1299.99, moneda: "USD" as const, detalle: conDetalle ? ["Audífonos"] : [] };
}
type Args = Parameters<typeof buscarPedido>; // [id: number, conDetalle: boolean]
type Devuelve = ReturnType<typeof buscarPedido>; // Promise<{ ... }>
type Pedido = Awaited<ReturnType<typeof buscarPedido>>; // { id: number; total: number; ... }
const args: Args = [42, true];
const p: Pedido = await buscarPedido(...args);
console.log(p);
// NonNullable quita null y undefined de una unión.
type QuizaTexto = string | null | undefined;
const t: NonNullable<QuizaTexto> = "seguro que hay texto";
console.log(t.length);npx tsc --noEmit --strict --target esnext --module nodenext --types node --locale es funciones.ts
node funciones.ts
salida{ id: 42, total: 1299.99, moneda: 'USD', detalle: [ 'Audífonos' ] }
20| Utilitario | Da |
|---|---|
Parameters<typeof f> | Los parámetros de f, como tupla |
ReturnType<typeof f> | Lo que devuelve f; con una función async, la promesa |
Awaited<T> | Lo que hay dentro de la promesa |
NonNullable<T> | T sin null ni undefined |
Readonly<Usuario> impide cambiar ana.nombre, pero si una propiedad es un objeto o un array, su contenido sí se puede modificar. Lo contamos en las rarezas de TypeScript.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: Utility Types en el manual de TypeScript.