Tipos primitivos y especiales

Intermedio
TypeScript
TypeScript
Actualizado: 27/08/2025

Tipos primitivos básicos

TypeScript hereda todos los tipos primitivos de JavaScript y añade un sistema de tipado estático que permite detectar errores en tiempo de compilación. Los tipos primitivos son los bloques fundamentales para construir aplicaciones más complejas y representan los valores más básicos que podemos manejar.

El tipo number

El tipo number en TypeScript representa tanto números enteros como decimales, siguiendo el estándar IEEE 754 de punto flotante de doble precisión. A diferencia de otros lenguajes, TypeScript no distingue entre enteros y decimales.

let edad: number = 25;
let precio: number = 19.99;
let temperatura: number = -15;
let cientifico: number = 1.23e-4;

TypeScript también soporta literales numéricos en diferentes bases:

let decimal: number = 42;
let hexadecimal: number = 0x2A;
let binario: number = 0b101010;
let octal: number = 0o52;

El tipo string

Las cadenas de texto se definen con el tipo string y pueden crearse usando comillas simples, dobles o template literals:

let nombre: string = "María";
let apellido: string = 'González';
let saludo: string = `Hola, ${nombre} ${apellido}`;

Los template literals son especialmente útiles para crear cadenas dinámicas:

let usuario: string = "admin";
let intentos: number = 3;
let mensaje: string = `Usuario ${usuario} tiene ${intentos} intentos restantes`;

El tipo boolean

El tipo boolean solo puede tener dos valores: true o false. Es fundamental para la lógica condicional:

let estaActivo: boolean = true;
let esValido: boolean = false;
let resultado: boolean = edad >= 18;

El tipo bigint

Para números enteros muy grandes que exceden el límite seguro de number (2^53 - 1), TypeScript proporciona el tipo bigint:

let numeroGrande: bigint = 9007199254740991n;
let otroGrande: bigint = BigInt("9007199254740992");

Es importante recordar que no se pueden mezclar operaciones entre number y bigint:

let normal: number = 100;
let grande: bigint = 200n;

// Error: no se pueden sumar directamente
// let suma = normal + grande;

// Correcto: convertir tipos
let suma: bigint = BigInt(normal) + grande;

El tipo symbol

Los símbolos son primitivos únicos e inmutables, útiles como identificadores de propiedades de objetos:

let id1: symbol = Symbol("id");
let id2: symbol = Symbol("id");

// Aunque tengan la misma descripción, son diferentes
console.log(id1 === id2); // false

Los símbolos garantizan unicidad absoluta, incluso cuando se crean con la misma descripción:

let claveUnica: symbol = Symbol("configuracion");
let otraClaveUnica: symbol = Symbol("configuracion");

// Son completamente diferentes
console.log(claveUnica === otraClaveUnica); // false

Inferencia automática de tipos

TypeScript puede inferir automáticamente el tipo de una variable basándose en su valor inicial:

// TypeScript infiere que es number
let contador = 0;

// TypeScript infiere que es string  
let mensaje = "Bienvenido";

// TypeScript infiere que es boolean
let activo = true;

Sin embargo, es recomendable especificar explícitamente los tipos en casos donde la inferencia podría ser ambigua o cuando queremos ser más claros sobre nuestras intenciones:

// Explícito: más claro para otros desarrolladores
let porcentaje: number = 0;
let estado: boolean = false;

Conversión entre tipos primitivos

TypeScript permite conversiones explícitas entre tipos primitivos usando las funciones constructoras correspondientes:

let texto: string = "42";
let numero: number = Number(texto);
let booleano: boolean = Boolean(numero);

// También podemos usar parseInt y parseFloat
let entero: number = parseInt("123.45");
let decimal: number = parseFloat("123.45");

La conversión a string es especialmente común:

let edad: number = 25;
let edadTexto: string = String(edad);
let mensaje: string = `Tienes ${edad} años`; // Conversión automática

Buenas prácticas con tipos primitivos

Siempre utiliza anotaciones de tipo explícitas en parámetros de funciones y propiedades de objetos, incluso si TypeScript puede inferirlas:

function calcularDescuento(precio: number, porcentaje: number): number {
    return precio * (porcentaje / 100);
}

function saludar(nombre: string): string {
    return `Hola, ${nombre}`;
}

Evita el uso de constructores de objetos para tipos primitivos:

// Incorrecto: crea objetos, no primitivos
let texto = new String("hola");
let numero = new Number(42);

// Correcto: valores primitivos
let texto: string = "hola";
let numero: number = 42;

Tipos especiales

TypeScript incluye varios tipos especiales que van más allá de los primitivos básicos. Estos tipos proporcionan mayor flexibilidad y control sobre el sistema de tipos, permitiendo manejar situaciones específicas donde los tipos primitivos no son suficientes.

El tipo any

El tipo any desactiva completamente la verificación de tipos para una variable, permitiendo asignar cualquier valor y acceder a cualquier propiedad:

let valor: any = 42;
valor = "texto";
valor = true;
valor = { nombre: "Juan" };

// No hay verificación de tipos
valor.propiedad.inexistente.anidada; // No genera error

Aunque any proporciona máxima flexibilidad, debe usarse con precaución ya que elimina los beneficios del tipado estático:

function procesarDatos(datos: any): any {
    // Perdemos toda la información de tipos
    return datos.toUpperCase(); // ¿Qué pasa si datos no es string?
}

Casos de uso legítimos para any:

  • Migración gradual de código JavaScript existente
  • Integración con bibliotecas de terceros sin tipado
  • Prototipado rápido donde el tipado se refinará después

El tipo unknown

El tipo unknown es la alternativa segura a any. Representa un valor de tipo desconocido, pero requiere verificaciones antes de su uso:

let valor: unknown = obtenerDatosExternos();

// Error: no podemos usar directamente unknown
// console.log(valor.length);

// Correcto: verificamos el tipo primero
if (typeof valor === "string") {
    console.log(valor.length); // TypeScript sabe que es string
}

Unknown es especialmente útil cuando recibimos datos externos y queremos validarlos antes de procesarlos:

function procesarRespuestaAPI(respuesta: unknown): string {
    if (typeof respuesta === "string") {
        return respuesta.toUpperCase();
    }
    
    if (typeof respuesta === "object" && respuesta !== null) {
        return JSON.stringify(respuesta);
    }
    
    return "Datos no válidos";
}

El tipo void

El tipo void indica la ausencia de un valor de retorno. Se usa principalmente en funciones que no devuelven nada:

function mostrarMensaje(texto: string): void {
    console.log(texto);
    // No hay return explícito
}

function configurarEventos(): void {
    document.addEventListener("click", () => {
        console.log("Click detectado");
    });
}

Una variable de tipo void solo puede ser undefined o null:

let resultado: void = undefined; // Válido
// let otro: void = "texto"; // Error

El tipo never

El tipo never representa valores que nunca ocurren. Se usa en funciones que nunca terminan o siempre lanzan excepciones:

function lanzarError(mensaje: string): never {
    throw new Error(mensaje);
    // Esta línea nunca se ejecuta
}

function bucleInfinito(): never {
    while (true) {
        console.log("Ejecutándose...");
    }
    // Esta función nunca termina
}

Never también aparece en análisis de flujo exhaustivo:

type Estado = "cargando" | "completado" | "error";

function manejarEstado(estado: Estado): string {
    switch (estado) {
        case "cargando":
            return "Procesando...";
        case "completado":
            return "Finalizado";
        case "error":
            return "Ha ocurrido un error";
        default:
            // estado es de tipo never aquí
            const _exhaustivo: never = estado;
            throw new Error(`Estado no manejado: ${estado}`);
    }
}

Los tipos null y undefined

null y undefined son tipos y valores distintos en TypeScript:

let valorNulo: null = null;
let valorIndefinido: undefined = undefined;

// Son tipos diferentes
let opcional: string | null = null;
let sinAsignar: string | undefined = undefined;

Por defecto, null y undefined son subtipos de todos los demás tipos:

let texto: string = null; // Válido por defecto
let numero: number = undefined; // Válido por defecto

Con strictNullChecks habilitado (recomendado), debemos ser explícitos:

// Con strictNullChecks: true
let nombre: string = "Juan";
// nombre = null; // Error

let nombreOpcional: string | null = "María";
nombreOpcional = null; // Válido

El tipo object

El tipo object representa cualquier valor que no sea un primitivo:

let persona: object = { nombre: "Ana", edad: 30 };
let fechas: object = new Date();
let numeros: object = [1, 2, 3];

// Error: los primitivos no son object
// let texto: object = "hola";
// let numero: object = 42;

Sin embargo, object es muy genérico y limitado. Generalmente preferimos interfaces o tipos más específicos:

// Mejor que object genérico
interface Usuario {
    nombre: string;
    edad: number;
}

let usuario: Usuario = { nombre: "Carlos", edad: 25 };

Verificación de tipos especiales

Para trabajar con tipos especiales de forma segura, usamos verificaciones de tipo:

function procesarValor(valor: unknown): string {
    // Verificación de null/undefined
    if (valor == null) {
        return "Valor nulo o indefinido";
    }
    
    // Verificación de tipo primitivo
    if (typeof valor === "string") {
        return valor.toUpperCase();
    }
    
    // Verificación de objeto
    if (typeof valor === "object") {
        return JSON.stringify(valor);
    }
    
    return String(valor);
}

Buenas prácticas con tipos especiales

Evita any siempre que sea posible. Si necesitas flexibilidad, considera unknown:

// Evitar
function procesar(datos: any): any {
    return datos.resultado;
}

// Preferir
function procesarSeguro(datos: unknown): string {
    if (typeof datos === "object" && datos !== null && "resultado" in datos) {
        return String((datos as any).resultado);
    }
    throw new Error("Datos inválidos");
}

Usa void consistentemente en funciones sin retorno:

// Correcto
function actualizar(): void {
    // lógica de actualización
}

// Evitar retorno implícito ambiguo
function configurar() {
    // sin anotación de tipo
}

Aprovecha never para crear código más robusto:

function validarTipo(valor: string | number): string {
    if (typeof valor === "string") {
        return valor.trim();
    }
    if (typeof valor === "number") {
        return valor.toString();
    }
    
    // Esta línea garantiza que hemos cubierto todos los casos
    const _never: never = valor;
    throw new Error("Tipo no esperado");
}

Tipos literales y const assertions

Los tipos literales en TypeScript permiten especificar valores exactos como tipos, proporcionando un control más granular sobre qué valores son aceptables. Esta característica, combinada con las const assertions, ofrece herramientas avanzadas para crear código más preciso y seguro.

Tipos literales de cadena

Un tipo literal de cadena especifica que una variable solo puede contener un valor de cadena específico:

let direccion: "norte" = "norte";
// direccion = "sur"; // Error: solo acepta "norte"

let estado: "activo" | "inactivo" | "pendiente" = "activo";
estado = "inactivo"; // Válido
// estado = "otro"; // Error: no está en la unión

Los tipos literales son especialmente útiles para crear enumeraciones de cadenas sin usar la palabra clave enum:

type TipoNotificacion = "info" | "warning" | "error" | "success";

function mostrarNotificacion(tipo: TipoNotificacion, mensaje: string): void {
    console.log(`[${tipo.toUpperCase()}] ${mensaje}`);
}

mostrarNotificacion("info", "Proceso completado"); // Válido
// mostrarNotificacion("debug", "Error"); // Error: "debug" no es válido

Tipos literales numéricos

Los números específicos también pueden usarse como tipos literales:

let puerto: 3000 | 8080 | 9000 = 3000;
puerto = 8080; // Válido
// puerto = 5000; // Error: no está permitido

type CodigoHTTP = 200 | 404 | 500 | 503;

function manejarRespuesta(codigo: CodigoHTTP): string {
    switch (codigo) {
        case 200:
            return "Éxito";
        case 404:
            return "No encontrado";
        case 500:
            return "Error interno";
        case 503:
            return "Servicio no disponible";
    }
}

Tipos literales booleanos

Aunque menos comunes, los valores booleanos específicos también pueden ser tipos literales:

let configuracionDebug: true = true;
// configuracionDebug = false; // Error: solo acepta true

type ConfiguracionFeature = {
    habilitado: true; // Solo acepta true
    experimental: false; // Solo acepta false
};

Const assertions básicas

Las const assertions utilizan la sintaxis as const para indicar a TypeScript que trate un valor como inmutable y con tipos más específicos:

// Sin const assertion - tipo inferido: string
let mensaje1 = "bienvenido";

// Con const assertion - tipo inferido: "bienvenido"
let mensaje2 = "bienvenido" as const;

Esta diferencia es crucial cuando trabajamos con valores que no deben cambiar:

// Tipo inferido: string
const saludo = "hola";

// Tipo inferido: "hola" (literal)
const saludoLiteral = "hola" as const;

function procesar(texto: "hola" | "adios"): void {
    console.log(texto);
}

// procesar(saludo); // Error: string no es asignable a "hola" | "adios"
procesar(saludoLiteral); // Válido: "hola" es exactamente lo esperado

Const assertions con objetos

Las const assertions en objetos convierten todas las propiedades en readonly y sus valores en tipos literales:

// Sin const assertion
const configuracion1 = {
    servidor: "localhost",
    puerto: 3000,
    ssl: false
};
// Tipo: { servidor: string; puerto: number; ssl: boolean; }

// Con const assertion
const configuracion2 = {
    servidor: "localhost",
    puerto: 3000,
    ssl: false
} as const;
// Tipo: { readonly servidor: "localhost"; readonly puerto: 3000; readonly ssl: false; }

Esto es especialmente útil para configuraciones que no deben modificarse:

const CONFIGURACION_API = {
    baseUrl: "https://api.ejemplo.com",
    version: "v1",
    timeout: 5000
} as const;

// Error: no se puede modificar
// CONFIGURACION_API.baseUrl = "otra-url";

// Podemos extraer tipos de la configuración
type ConfiguracionAPI = typeof CONFIGURACION_API;

Narrowing con tipos literales

Los tipos literales permiten que TypeScript realice narrowing automático en estructuras condicionales:

type EstadoProceso = "iniciando" | "procesando" | "completado" | "error";

function manejarProceso(estado: EstadoProceso): string {
    if (estado === "iniciando") {
        // TypeScript sabe que estado es exactamente "iniciando"
        return "Preparando proceso...";
    }
    
    if (estado === "procesando") {
        // TypeScript sabe que estado es exactamente "procesando"
        return "Proceso en ejecución...";
    }
    
    // TypeScript sabe que aquí estado es "completado" | "error"
    return estado === "completado" ? "Finalizado" : "Error detectado";
}

Const assertions con estructuras anidadas

Las const assertions se aplican recursivamente a estructuras anidadas:

const menuNavegacion = {
    principal: {
        inicio: "/",
        productos: "/productos",
        contacto: "/contacto"
    },
    admin: {
        dashboard: "/admin",
        usuarios: "/admin/usuarios"
    }
} as const;

// Todos los valores son literales y readonly
type RutaInicio = typeof menuNavegacion.principal.inicio; // "/"
type RutasAdmin = typeof menuNavegacion.admin[keyof typeof menuNavegacion.admin]; // "/admin" | "/admin/usuarios"

Template literal types

TypeScript permite crear tipos literales con plantillas que combinan literales de cadena:

type Prefijo = "get" | "set" | "delete";
type Recurso = "User" | "Product" | "Order";

// Combinación de literales usando template literals
type MetodoAPI = `${Prefijo}${Recurso}`;
// Resultado: "getUser" | "setUser" | "deleteUser" | "getProduct" | "setProduct" | "deleteProduct" | "getOrder" | "setOrder" | "deleteOrder"

function ejecutarMetodo(metodo: MetodoAPI): void {
    console.log(`Ejecutando: ${metodo}`);
}

ejecutarMetodo("getUser"); // Válido
ejecutarMetodo("setProduct"); // Válido
// ejecutarMetodo("createUser"); // Error: no está en el tipo

Casos prácticos con tipos literales

Los tipos literales son ideales para APIs con valores específicos:

type MetodoHTTP = "GET" | "POST" | "PUT" | "DELETE";
type ContentType = "application/json" | "application/xml" | "text/plain";

interface ConfiguracionRequest {
    metodo: MetodoHTTP;
    contentType: ContentType;
    url: string;
}

function hacerRequest(config: ConfiguracionRequest): void {
    // TypeScript garantiza que método y contentType son valores válidos
    console.log(`${config.metodo} ${config.url} (${config.contentType})`);
}

También son útiles para sistemas de eventos tipados:

type EventoUsuario = "login" | "logout" | "registro" | "actualizacion";

const manejadoresEventos = {
    login: () => console.log("Usuario conectado"),
    logout: () => console.log("Usuario desconectado"),
    registro: () => console.log("Nuevo usuario registrado"),
    actualizacion: () => console.log("Perfil actualizado")
} as const;

function procesarEvento(evento: EventoUsuario): void {
    const manejador = manejadoresEventos[evento];
    manejador();
}

Buenas prácticas con tipos literales

Prefiere tipos literales sobre strings genéricos cuando tengas un conjunto conocido de valores:

// Evitar
function cambiarTema(tema: string): void {
    // No sabemos qué valores son válidos
}

// Preferir
type Tema = "claro" | "oscuro" | "automatico";
function cambiarTema(tema: Tema): void {
    // TypeScript valida que el tema sea correcto
}

Usa const assertions para configuraciones inmutables:

const CONFIGURACION_PRODUCCION = {
    apiUrl: "https://api.produccion.com",
    cacheTTL: 3600,
    debugMode: false
} as const;

// Exportar tipo derivado para reutilización
export type ConfiguracionProduccion = typeof CONFIGURACION_PRODUCCION;

Combina tipos literales con validación en tiempo de ejecución:

type NivelLog = "debug" | "info" | "warn" | "error";

function esNivelLogValido(valor: string): valor is NivelLog {
    return ["debug", "info", "warn", "error"].includes(valor);
}

function configurarLogger(nivel: string): void {
    if (esNivelLogValido(nivel)) {
        // nivel es ahora de tipo NivelLog
        console.log(`Logger configurado en nivel: ${nivel}`);
    } else {
        throw new Error(`Nivel de log inválido: ${nivel}`);
    }
}

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en TypeScript

Documentación oficial de TypeScript
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 los tipos primitivos básicos en TypeScript: number, string, boolean, bigint y symbol.
  • Conocer los tipos especiales como any, unknown, void, never, null, undefined y object.
  • Aprender el uso y ventajas de los tipos literales y las const assertions para mayor precisión en el tipado.
  • Aplicar buenas prácticas en la anotación y manejo de tipos para mejorar la seguridad y claridad del código.
  • Entender cómo realizar conversiones y verificaciones de tipos de forma segura y eficiente.