Módulo 02: Variables y tipos de datos

Objetivos del módulo

  • Entender qué es una variable y cómo se declara, se inicializa y se reasigna
  • Usar let y const correctamente, y saber por qué const no significa inmutable
  • Conocer var y sus problemas para poder leer código antiguo
  • Aplicar las reglas y convenciones de nombres de JavaScript
  • Conocer los siete tipos primitivos y el tipo object, y usar typeof (con sus rarezas)
  • Dominar el tipo number: decimales imprecisos, NaN, Infinity, enteros seguros, separadores numéricos y toFixed
  • Usar Math.sumPrecise (ES2026) y BigInt cuando los números normales no bastan
  • Distinguir valores truthy y falsy, y null frente a undefined
  • Convertir tipos de forma explícita y reconocer las trampas de la coerción implícita
  • Comprender la diferencia entre valor y referencia, y la zona muerta temporal (TDZ)

1. ¿Qué es una variable?

📘 Concepto: Una variable es un nombre que apunta a un valor guardado en la memoria del ordenador. Piensa en una caja con una etiqueta: la etiqueta es el nombre (edad) y dentro de la caja está el dato (28). Cuando el programa necesita el dato, lo busca por su etiqueta.

let edad = 28;            // declaramos la variable "edad" y le damos el valor 28
console.log(edad);        // 28

edad = 29;                // cambiamos su valor (reasignación)
console.log(edad);        // 29

console.log(edad + 1);    // 30 (usamos su valor sin cambiarlo)

Tres conceptos que conviene distinguir desde el principio:

Término Qué es Ejemplo
Declarar Crear la variable (reservar el nombre) let precio;
Inicializar Darle su primer valor let precio = 10;
Asignar / reasignar Darle un valor nuevo con = precio = 12;
let mensaje;              // declarada pero sin valor
console.log(mensaje);     // undefined (el valor "sin valor" de JavaScript)

mensaje = 'Hola';         // ahora tiene valor
console.log(mensaje);     // Hola

⚠️ Importante: El signo = en programación no significa “es igual a” como en matemáticas, sino “guarda en la variable de la izquierda el valor de la derecha”. Por eso contador = contador + 1 tiene sentido: calcula contador + 1 y guarda el resultado en contador.


2. let y const

Desde ES2015, JavaScript tiene dos formas modernas de declarar variables.

2.1 let: variables que pueden cambiar

let puntos = 0;
puntos = puntos + 10;
puntos = puntos + 5;
console.log(puntos); // 15

2.2 const: variables que no se pueden reasignar

const IVA = 0.21;
const nombre = 'Ana';

nombre = 'Luis'; // ❌ TypeError: Assignment to constant variable.

Una const debe inicializarse al declararla:

const pi; // ❌ SyntaxError: Missing initializer in const declaration

2.3 const no significa inmutable

Esta es una de las confusiones más habituales. const impide reasignar la variable (cambiar lo que hay en la caja por otra cosa), pero no impide modificar el contenido de un objeto o un array que esté dentro:

const usuario = { nombre: 'Ana', edad: 28 };

usuario.edad = 29;           // ✅ permitido: modificamos el objeto
console.log(usuario);        // { nombre: 'Ana', edad: 29 }

usuario = { nombre: 'Luis' }; // ❌ TypeError: Assignment to constant variable.

const colores = ['rojo', 'verde'];
colores.push('azul');        // ✅ permitido: modificamos el array
console.log(colores);        // [ 'rojo', 'verde', 'azul' ]

📘 Concepto: Piensa en const como una caja pegada con cola a su etiqueta: la etiqueta usuario siempre apuntará al mismo objeto, pero ese objeto puede cambiar por dentro. Si quieres un objeto que no se pueda modificar, existe Object.freeze() (lo verás en el módulo 07).

2.4 ¿Cuál uso?

💡 Consejo: La regla profesional es sencilla: usa const por defecto y cambia a let solo cuando necesites reasignar la variable (contadores, acumuladores, valores que cambian en un bucle). Así, quien lea tu código sabe que lo que es const no va a cambiar de valor más adelante, y te ahorras muchos errores.

const precioBase = 100;   // no cambia
const descuento = 0.15;   // no cambia
let total = precioBase;   // va a cambiar

total = total - total * descuento;
console.log(total); // 85

2.5 Ámbito de bloque

let y const tienen ámbito de bloque: solo existen dentro de las llaves { } donde se declaran.

const edad = 20;

if (edad >= 18) {
  const mensaje = 'Eres mayor de edad';
  console.log(mensaje); // Eres mayor de edad
}

console.log(mensaje); // ❌ ReferenceError: mensaje is not defined

Profundizarás en el ámbito en el módulo 05.


3. var y sus problemas

Antes de 2015 la única forma de declarar variables era var. Hoy no debes usarla, pero la encontrarás en tutoriales antiguos y en código heredado, así que tienes que conocer sus trampas.

3.1 Problema 1: ámbito de función, no de bloque

if (true) {
  var secreto = 'visible fuera';
}
console.log(secreto); // visible fuera  ← ¡se "escapa" del bloque!

Las variables var solo respetan los límites de una función, no los de un if o un for. Un ejemplo clásico de error:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 0);
}
// var: 3
// var: 3
// var: 3   ← las tres comparten la misma i, que al final vale 3

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 0);
}
// let: 0
// let: 1
// let: 2   ← cada vuelta tiene su propia j

3.2 Problema 2: hoisting con valor undefined

📘 Concepto: Hoisting (elevación) es el comportamiento por el que JavaScript procesa las declaraciones antes de ejecutar el código, como si las “subiera” al principio de su ámbito.

Con var, la declaración sube pero el valor no, así que puedes usar la variable antes de declararla y obtienes undefined en vez de un error:

console.log(ciudad); // undefined  (¡sin error!)
var ciudad = 'Madrid';
console.log(ciudad); // Madrid

Es como si JavaScript hubiera reescrito tu código así:

var ciudad;          // la declaración "sube"
console.log(ciudad); // undefined
ciudad = 'Madrid';   // la asignación se queda en su sitio

Esto oculta errores: usar una variable antes de darle valor casi siempre es un fallo, y var no te avisa.

3.3 Problema 3: redeclaración

var total = 100;
// ... 200 líneas después, otra persona escribe:
var total = 0; // ✅ sin error: ¡acaba de machacar la variable anterior!

let precio = 100;
let precio = 0; // ❌ SyntaxError: Identifier 'precio' has already been declared

3.4 Problema 4: variables globales en el objeto window

En un script clásico del navegador (no módulo), un var en el nivel superior se convierte en propiedad de window, y puede chocar con propiedades del propio navegador:

var name = 'Ana';
console.log(window.name); // Ana  ← hemos sobrescrito una propiedad del navegador

3.5 Tabla comparativa

Característica var let const
Ámbito Función Bloque Bloque
Hoisting Sí, inicializada a undefined Sí, pero en TDZ (error si se usa antes) Sí, pero en TDZ
Redeclarar en el mismo ámbito Permitido Error Error
Reasignar Sí Sí No
Obligatorio inicializar No No Sí
Crea propiedad en window (script clásico) Sí No No
Uso recomendado Ninguno (solo código antiguo) Valores que cambian Por defecto

⚠️ Importante: En este curso nunca usaremos var salvo para explicarlo. ESLint tiene una regla (no-var) que te avisa si lo usas.


4. Nombres de variables

4.1 Reglas obligatorias

Si no las cumples, el código da error de sintaxis:

  • Pueden contener letras, dígitos, _ (guion bajo) y $ (dólar).
  • No pueden empezar por un dígito: 1usuario ❌, usuario1 ✅.
  • No pueden contener espacios ni guiones: mi nombre ❌, mi-nombre ❌ (el guion es la resta).
  • No pueden ser palabras reservadas: let, const, if, for, function, class, return, true, null, new, this, typeof…
  • Distinguen mayúsculas y minúsculas: edad, Edad y EDAD son tres variables distintas.
const nombre1 = 'válido';
const _privado = 'válido';
const $elemento = 'válido';
const precioConIVA = 'válido';
// const 2precio = 'error';     ❌ empieza por dígito
// const precio-final = 'error'; ❌ contiene guion
// const class = 'error';       ❌ palabra reservada

📘 Concepto: JavaScript admite letras Unicode en los nombres, así que const año = 2026; es válido. Aun así, en la práctica profesional se evitan las tildes y la eñe en los identificadores (por ejemplo, anio o year) para no tener problemas con teclados, herramientas o equipos internacionales.

4.2 Convenciones (no obligatorias, pero universales)

Convención Cuándo se usa Ejemplo
camelCase Variables y funciones (lo normal) precioTotal, nombreUsuario, calcularMedia
PascalCase Clases y constructores Usuario, CarritoCompra
UPPER_SNAKE_CASE Constantes “de configuración” conocidas antes de ejecutar MAX_INTENTOS, URL_API, IVA_GENERAL
Prefijo es, tiene, puede Booleanos esAdmin, tieneStock, puedeEditar
Plural Colecciones usuarios, productos
const MAX_INTENTOS = 3;             // configuración fija
const nombreUsuario = 'ana_garcia'; // camelCase
let intentosRestantes = MAX_INTENTOS;
const esAdmin = false;              // booleano con prefijo "es"
const productos = ['pan', 'leche']; // plural para colecciones

4.3 Nombres que explican

// ❌ Nombres crípticos
const d = 7;
const x = d * 24;

// ✅ Nombres que cuentan qué guardan
const diasDeLaSemana = 7;
const horasPorSemana = diasDeLaSemana * 24;

💡 Consejo: Un buen nombre ahorra un comentario. Si dudas entre un nombre corto y uno largo pero claro, elige el claro. La única excepción aceptada son contadores de bucles muy cortos como i o j.


5. Tipos de datos

📘 Concepto: Cada valor en JavaScript tiene un tipo, que indica qué clase de dato es y qué operaciones puedes hacer con él. No es lo mismo sumar dos números que “sumar” dos textos.

JavaScript tiene siete tipos primitivos y un tipo no primitivo (object):

Tipo Qué representa Ejemplos
number Números enteros y decimales 42, 3.14, -7, NaN, Infinity
string Texto 'Hola', "Adiós", `Hola ${nombre}`
boolean Verdadero o falso true, false
undefined Variable sin valor asignado undefined
null Ausencia intencionada de valor null
bigint Enteros de tamaño arbitrario 9007199254740993n
symbol Identificador único Symbol('id')
object Colecciones de datos: objetos, arrays, funciones, fechas… { nombre: 'Ana' }, [1, 2, 3]

📘 Concepto: Un primitivo es un valor simple e indivisible (un número, un texto, un booleano). Un objeto es una estructura que agrupa varios valores. La diferencia es importante para entender cómo se copian (sección 14).

5.1 El operador typeof

typeof devuelve, como texto, el tipo de un valor:

console.log(typeof 42);          // number
console.log(typeof 'hola');      // string
console.log(typeof true);        // boolean
console.log(typeof undefined);   // undefined
console.log(typeof 10n);         // bigint
console.log(typeof Symbol('id')); // symbol
console.log(typeof { a: 1 });    // object

5.2 Las rarezas de typeof

console.log(typeof null);           // object    ← ¡un error histórico!
console.log(typeof []);             // object    (los arrays son objetos)
console.log(typeof function () {}); // function  (las funciones son objetos, pero typeof las distingue)
console.log(typeof NaN);            // number    ("no es un número"... es de tipo number)
console.log(typeof noDeclarada);    // undefined (no da error con variables no declaradas)
console.log(typeof typeof 1);       // string    (typeof siempre devuelve un texto)

⚠️ Importante: typeof null === 'object' es un error de la primera versión de JavaScript (1995) que nunca se corrigió porque arreglarlo rompería millones de webs. Para comprobar si algo es null, compáralo directamente: valor === null. Para saber si algo es un array, usa Array.isArray(valor).

Comprobación Forma correcta
¿Es un número? typeof x === 'number' (y !Number.isNaN(x) si quieres excluir NaN)
¿Es un texto? typeof x === 'string'
¿Es null? x === null
¿Es undefined? x === undefined
¿Es un array? Array.isArray(x)
¿Es una función? typeof x === 'function'
¿Es un objeto “de verdad”? typeof x === 'object' && x !== null

6. El tipo number en detalle

6.1 Un solo tipo para enteros y decimales

A diferencia de otros lenguajes, JavaScript no distingue entre enteros y decimales: todos son number. Internamente se guardan en formato IEEE 754 de doble precisión (64 bits), el mismo estándar que usan casi todos los procesadores.

const entero = 42;
const decimal = 3.14;   // el separador decimal es el PUNTO, nunca la coma
const negativo = -7;
const cientifico = 2.5e-3; // 0.0025 (notación científica)
const hexadecimal = 0xff;  // 255
const binario = 0b1010;    // 10
const octal = 0o17;        // 15

6.2 Separadores numéricos (ES2021)

Para leer mejor números largos puedes usar _ como separador visual. No cambia el valor:

const poblacion = 48_000_000;
const precioMillones = 1_250_000.50;
const microsegundo = 0.000_001;
console.log(poblacion); // 48000000

6.3 La famosa suma 0.1 + 0.2

console.log(0.1 + 0.2);         // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

📘 Concepto: No es un error de JavaScript: le pasa a todos los lenguajes que usan IEEE 754 (Python, Java, C#…). El ordenador guarda los números en binario, y igual que 1/3 no se puede escribir exactamente en decimal (0,3333…), 0,1 no se puede escribir exactamente en binario. Se guarda el número binario más cercano, que es 0.1000000000000000055511..., y al sumar, esos pequeños errores se notan.

console.log((0.1).toFixed(20)); // 0.10000000000000000555 (el valor real guardado)

Cómo comparar decimales de forma segura:

const resultado = 0.1 + 0.2;

// Comparar con una tolerancia muy pequeña
console.log(Math.abs(resultado - 0.3) < Number.EPSILON); // true

6.4 Redondear para mostrar: toFixed

toFixed(n) devuelve un texto con n decimales, redondeando:

const total = 0.1 + 0.2;
console.log(total.toFixed(2));        // 0.30
console.log(typeof total.toFixed(2)); // string  ← ¡es un texto, no un número!
console.log((1234.5678).toFixed(1));  // 1234.6
console.log((1.005).toFixed(2));      // 1.00  ← 1.005 se guarda como 1.00499999...

⚠️ Importante: toFixed sirve para mostrar números, no para calcular con ellos. Y como ves con 1.005, incluso el redondeo sufre la imprecisión binaria. Para mostrar cantidades con formato local (por ejemplo 1.234,57 €) usarás Intl.NumberFormat en el módulo 19.

6.5 Los valores especiales: NaN e Infinity

console.log(1 / 0);         // Infinity
console.log(-1 / 0);        // -Infinity
console.log(0 / 0);         // NaN
console.log('abc' * 2);     // NaN
console.log(Math.sqrt(-1)); // NaN

NaN (Not a Number) es el resultado de una operación matemática que no tiene sentido. Tiene una propiedad única: no es igual a nada, ni siquiera a sí mismo.

console.log(NaN === NaN);         // false  ← ¡nunca compares con NaN así!
console.log(Number.isNaN(NaN));   // true   ✅ la forma correcta

// La función global antigua isNaN() convierte antes a número: engaña
console.log(isNaN('hola'));        // true  (convierte 'hola' en NaN)
console.log(Number.isNaN('hola')); // false ('hola' es un string, no NaN)

💡 Consejo: Usa siempre Number.isNaN() y Number.isFinite() en lugar de las funciones globales isNaN() e isFinite(), que hacen conversiones automáticas y dan resultados confusos.

6.6 Enteros: Number.isInteger y los límites seguros

console.log(Number.isInteger(5));   // true
console.log(Number.isInteger(5.0)); // true  (5.0 es lo mismo que 5)
console.log(Number.isInteger(5.5)); // false
console.log(Number.isInteger('5')); // false (es un texto)

Como los números usan 64 bits, solo se pueden representar con exactitud los enteros entre -(2^53 - 1) y 2^53 - 1:

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992  ← ¡el mismo! Se pierde precisión
console.log(9007199254740993);            // 9007199254740992

console.log(Number.isSafeInteger(2 ** 53)); // false
Constante Valor Significado
Number.MAX_SAFE_INTEGER 9007199254740991 Mayor entero exacto
Number.MIN_SAFE_INTEGER -9007199254740991 Menor entero exacto
Number.MAX_VALUE 1.7976931348623157e+308 Mayor número representable
Number.MIN_VALUE 5e-324 Menor positivo representable
Number.EPSILON 2.220446049250313e-16 Diferencia mínima entre 1 y el siguiente número

⚠️ Importante: Los identificadores enormes que vienen de bases de datos o APIs (por ejemplo, IDs de 18 dígitos) no caben en un number sin perder precisión. Guárdalos como texto o como BigInt (sección 8).


7. Sumas imprecisas y Math.sumPrecise (ES2026)

7.1 El problema de acumular errores

Cuando sumas muchos decimales uno a uno, cada suma intermedia se redondea y los errores se acumulan:

const importes = [0.1, 0.2, 0.3];

let total = 0;
for (const importe of importes) {
  total += importe;
}
console.log(total); // 0.6000000000000001

En casos extremos, el error se come un número entero:

const valores = [1e20, 0.1, -1e20];
let suma = 0;
for (const v of valores) {
  suma += v;
}
console.log(suma); // 0  ← ¡el 0.1 ha desaparecido!

7.2 La solución de ES2026: Math.sumPrecise

Math.sumPrecise(iterable) recibe un array (o cualquier iterable) de números y calcula la suma como si tuviera precisión infinita, redondeando una sola vez al final:

console.log(Math.sumPrecise([0.1, 0.2, 0.3]));    // 0.6
console.log(Math.sumPrecise([1e20, 0.1, -1e20])); // 0.1
console.log(Math.sumPrecise([]));                 // -0 (suma vacía)

⚠️ Importante: Math.sumPrecise evita los errores de la suma, pero no puede arreglar que 0.1 y 0.2 ya estuvieran guardados de forma aproximada. Por eso Math.sumPrecise([0.1, 0.2]) sigue dando 0.30000000000000004: es el número más cercano a la suma exacta de esos dos valores binarios. Donde más ayuda es al sumar muchos valores o valores de magnitudes muy distintas.

Otras características:

  • Solo acepta números: si algún elemento no es number lanza un TypeError (no convierte textos como '5').
  • Recibe un iterable, no varios argumentos: Math.sumPrecise([1, 2]), no Math.sumPrecise(1, 2).

7.3 Compatibilidad

Math.sumPrecise funciona en las versiones actuales de Chrome, Edge, Firefox y Safari, pero Node.js 24 todavía no la incluye. Comprueba siempre su existencia antes de usarla:

const importes = [0.1, 0.2, 0.3];

const total = typeof Math.sumPrecise === 'function'
  ? Math.sumPrecise(importes)
  : importes.reduce((acumulado, n) => acumulado + n, 0); // alternativa clásica

console.log(total); // 0.6 en navegadores actuales; 0.6000000000000001 en Node.js 24

💡 Consejo: Para dinero, la técnica profesional clásica sigue siendo trabajar en céntimos enteros (1999 en lugar de 19.99) y dividir entre 100 solo para mostrar. Los enteros hasta MAX_SAFE_INTEGER se suman sin ningún error. Si necesitas Math.sumPrecise en entornos que no la tienen, existen polyfills en npm (por ejemplo, el paquete math.sumprecise).

// Céntimos enteros: sin errores de redondeo
const preciosEnCentimos = [1999, 501, 10, 20];
const totalCentimos = preciosEnCentimos.reduce((a, b) => a + b, 0);
console.log(totalCentimos);                 // 2530
console.log((totalCentimos / 100).toFixed(2)); // 25.30

8. BigInt: enteros sin límite

Cuando necesitas enteros más grandes que MAX_SAFE_INTEGER (criptografía, IDs enormes, cálculos científicos), usa BigInt (ES2020). Se escribe añadiendo una n al final del número o con la función BigInt():

const grande = 9007199254740993n;
console.log(grande);              // 9007199254740993n  ✅ exacto
console.log(2n ** 64n);           // 18446744073709551616n
console.log(10n ** 20n);          // 100000000000000000000n
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n); // 9007199254740993n
console.log(typeof 5n);           // bigint

Reglas importantes:

// 1. No se pueden mezclar BigInt y number en operaciones
console.log(5n + 1); // ❌ TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(5n + BigInt(1)); // 6n ✅

// 2. La división es entera (descarta decimales)
console.log(7n / 2n); // 3n

// 3. Comparar sí se puede
console.log(5n == 5);  // true  (mismo valor matemático)
console.log(5n === 5); // false (tipos distintos)
console.log(10n > 5);  // true

💡 Consejo: BigInt es más lento que number y no funciona con Math (Math.sqrt(4n) da error). Úsalo solo cuando realmente necesites enteros enormes.


9. Texto (string) y symbol

9.1 Cadenas de texto

Un string es una secuencia de caracteres. Hay tres formas de escribirlo:

const simples = 'Hola';            // comillas simples (las que usamos en el curso)
const dobles = "Hola";             // comillas dobles (equivalentes)
const plantilla = `Hola, ${simples}`; // comillas invertidas: template literal

Las plantillas (template literals) permiten insertar expresiones con ${...} y escribir varias líneas:

const producto = 'Teclado';
const precio = 29.99;
const unidades = 2;

console.log(`Has comprado ${unidades} x ${producto}: ${precio * unidades} €`);
// Has comprado 2 x Teclado: 59.98 €

Operaciones básicas:

const saludo = 'Hola';
console.log(saludo.length);        // 4  (número de caracteres)
console.log(saludo[0]);            // H  (primer carácter; los índices empiezan en 0)
console.log(saludo.at(-1));        // a  (último carácter)
console.log(saludo.toUpperCase()); // HOLA
console.log(saludo + ' mundo');    // Hola mundo (concatenación con +)
console.log('It\'s');              // It's (\' escapa la comilla)
console.log('Línea 1\nLínea 2');   // \n = salto de línea

En el módulo 08 verás decenas de métodos de cadenas.

9.2 symbol: identificadores únicos

Un Symbol es un valor único e irrepetible, incluso si tiene la misma descripción:

const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2);     // false: cada Symbol es único
console.log(id1.description); // id

Se usan sobre todo como claves “ocultas” de objetos y para personalizar el comportamiento del lenguaje (Symbol.iterator, que verás en el módulo 11). Como principiante, rara vez crearás los tuyos.


10. boolean y valores truthy / falsy

Un boolean solo puede valer true (verdadero) o false (falso). Es el resultado de las comparaciones y la base de las decisiones:

const esMayorDeEdad = 20 >= 18;
console.log(esMayorDeEdad); // true

10.1 Truthy y falsy

Cuando JavaScript necesita un booleano (por ejemplo, en un if) y le das otro tipo de valor, lo convierte automáticamente. Los valores que se convierten en false se llaman falsy; todos los demás son truthy.

⚠️ Importante: Solo hay ocho valores falsy. Apréndetelos: todo lo que no esté en esta lista es truthy.

Valor falsy Tipo
false boolean
0 number
-0 number
0n bigint
'' (cadena vacía) string
null null
undefined undefined
NaN number

Valores truthy que sorprenden:

Valor ¿Truthy? Por qué sorprende
'0' Sí Es un texto con un carácter, no el número 0
'false' Sí Es un texto no vacío
' ' Sí Un espacio no es una cadena vacía
[] Sí Un array vacío es un objeto, y todos los objetos son truthy
{} Sí Ídem
-1 Sí Cualquier número distinto de 0 y NaN
const nombre = '';

if (nombre) {
  console.log(`Hola, ${nombre}`);
} else {
  console.log('No has escrito tu nombre'); // ← se ejecuta esto ('' es falsy)
}

const carrito = [];
if (carrito) {
  console.log('Esto se ejecuta aunque el carrito esté vacío'); // [] es truthy
}
if (carrito.length > 0) {
  console.log('Esto NO se ejecuta'); // ✅ la comprobación correcta
}

11. null frente a undefined

Los dos representan “ausencia de valor”, pero con intención distinta:

  undefined null
Significado “Todavía no tiene valor” “No tiene valor, a propósito”
Quién lo pone Normalmente JavaScript Normalmente el programador
typeof 'undefined' 'object' (error histórico)
Convertido a número NaN 0

Dónde aparece undefined automáticamente:

let sinValor;
console.log(sinValor);           // undefined: variable sin inicializar

const persona = { nombre: 'Ana' };
console.log(persona.edad);       // undefined: propiedad que no existe

function saludar(nombre) {
  console.log(nombre);
}
saludar();                       // undefined: parámetro que no se pasó

function nada() {}
console.log(nada());             // undefined: función sin return

Cuándo usar null:

let usuarioConectado = null; // aún no hay nadie conectado, y lo decimos explícitamente

// ...más tarde
usuarioConectado = { nombre: 'Ana' };

// ...al cerrar sesión
usuarioConectado = null;

💡 Consejo: Una convención común es: deja que undefined lo ponga JavaScript y usa null cuando tú quieras indicar “vacío a propósito”. Para comprobar “ni null ni undefined” verás en el módulo 03 el operador ??, pensado justo para esto.


12. Conversiones explícitas

Muchas veces recibes datos del tipo equivocado: lo que escribe el usuario en un formulario o en prompt siempre es texto, aunque escriba un número. Convertir de forma explícita (diciéndolo tú) es la forma segura de trabajar.

12.1 A número: Number(), parseInt(), parseFloat()

console.log(Number('42'));       // 42
console.log(Number('3.5'));      // 3.5
console.log(Number(''));         // 0     ← ¡cuidado! la cadena vacía es 0
console.log(Number('  12  '));   // 12    (ignora espacios alrededor)
console.log(Number('12px'));     // NaN   (todo el texto debe ser un número)
console.log(Number(true));       // 1
console.log(Number(false));      // 0
console.log(Number(null));       // 0
console.log(Number(undefined));  // NaN

parseInt y parseFloat leen desde el principio del texto y se detienen al encontrar algo que no es un número:

console.log(parseInt('12px'));      // 12
console.log(parseInt('3.99'));      // 3     (solo la parte entera)
console.log(parseFloat('3.99€'));   // 3.99
console.log(parseInt('px12'));      // NaN   (no empieza por número)
console.log(parseInt('ff', 16));    // 255   (segundo argumento: la base)
console.log(parseInt('101', 2));    // 5
console.log(parseInt(''));          // NaN   (a diferencia de Number(''))
Entrada Number() parseInt() parseFloat()
'42' 42 42 42
'3.99' 3.99 3 3.99
'12px' NaN 12 12
'' 0 NaN NaN
' 7 ' 7 7 7
null 0 NaN NaN

💡 Consejo: Usa Number() cuando el texto deba ser entero un número (por ejemplo, un campo de formulario) y comprueba después con Number.isNaN(). Usa parseInt/parseFloat cuando quieras extraer el número del principio de un texto, como '12px'. Con parseInt indica siempre la base: parseInt(texto, 10).

12.2 A texto: String() y toString()

console.log(String(123));        // '123'
console.log(String(true));       // 'true'
console.log(String(null));       // 'null'
console.log(String(undefined));  // 'undefined'
console.log(String([1, 2]));     // '1,2'
console.log((255).toString(16)); // 'ff'  (toString admite base en números)
console.log((5).toString(2));    // '101'

⚠️ Importante: null.toString() y undefined.toString() dan error, porque null y undefined no tienen métodos. String(valor) funciona con cualquier valor.

12.3 A booleano: Boolean()

console.log(Boolean(1));        // true
console.log(Boolean(0));        // false
console.log(Boolean('hola'));   // true
console.log(Boolean(''));       // false
console.log(Boolean('false'));  // true ← texto no vacío

12.4 Atajos: + unario y !!

En código profesional verás estos atajos:

console.log(+'42');   // 42    (el + delante convierte a número, como Number())
console.log(+'');     // 0
console.log(+true);   // 1

console.log(!!'hola'); // true  (doble negación: convierte a booleano, como Boolean())
console.log(!!0);      // false

💡 Consejo: Number(x) y Boolean(x) son más legibles para un principiante; +x y !!x son más cortos. Ambos son correctos: lo importante es que la conversión sea intencionada.


13. Coerción implícita: la tabla de sorpresas

📘 Concepto: La coerción implícita es la conversión automática que hace JavaScript cuando mezclas tipos en una operación. La regla que explica casi todo: el operador + con un texto concatena; el resto de operadores aritméticos (-, *, /, %) convierten a número.

Expresión Resultado Explicación
'5' + 1 '51' Hay un texto: + concatena
'5' - 1 4 - solo funciona con números: convierte '5'
'5' * '2' 10 Ambos se convierten a número
'10' / '2' 5 Ídem
1 + 2 + '3' '33' De izquierda a derecha: 1 + 2 = 3, luego 3 + '3'
'1' + 2 + 3 '123' '1' + 2 = '12', luego '12' + 3
true + 1 2 true se convierte en 1
null + 1 1 null se convierte en 0
undefined + 1 NaN undefined se convierte en NaN
[] + [] '' Los arrays se convierten en texto vacío
[] + {} '[object Object]' El objeto se convierte en ese texto
'b' + 'a' + +'a' + 'a' 'baNaNa' +'a' es NaN, que se concatena como texto
Number([]) 0 [] → '' → 0
Number([5]) 5 [5] → '5' → 5
Number({}) NaN {} → '[object Object]' → NaN

Un error real y muy común:

// En el navegador, prompt() SIEMPRE devuelve texto
const a = prompt('Primer número'); // el usuario escribe 2
const b = prompt('Segundo número'); // el usuario escribe 3
console.log(a + b); // '23'  ❌ concatena

// ✅ Convertimos explícitamente
console.log(Number(a) + Number(b)); // 5

⚠️ Importante: No hace falta memorizar la tabla. Hace falta evitar depender de ella: convierte explícitamente con Number(), String() o Boolean() y compara con === (módulo 03). Así la coerción implícita nunca te sorprenderá.


14. Inmutabilidad de primitivos, valor y referencia

14.1 Los primitivos son inmutables

Un valor primitivo no se puede modificar: solo se puede sustituir por otro.

const saludo = 'hola';
saludo[0] = 'H'; // ❌ en modo estricto: TypeError: Cannot assign to read only property '0' of string 'hola'

// Los métodos NO modifican el original: devuelven un valor nuevo
let nombre = 'ana';
nombre.toUpperCase();
console.log(nombre);         // ana  (sin cambios)

nombre = nombre.toUpperCase(); // guardamos el nuevo valor
console.log(nombre);         // ANA

14.2 Copiar por valor (primitivos)

Cuando asignas un primitivo a otra variable, se copia el valor. Son dos cajas independientes:

let x = 10;
let y = x;   // y recibe una COPIA del 10
y = 20;
console.log(x); // 10  (x no cambia)
console.log(y); // 20

14.3 Copiar por referencia (objetos) — adelanto

Con los objetos (y arrays) es distinto: la variable no guarda el objeto, sino una referencia (una “dirección”) al lugar de la memoria donde está. Al asignarla a otra variable, se copia la dirección, no el objeto:

const a = { valor: 1 };
const b = a;      // b apunta al MISMO objeto que a
b.valor = 99;
console.log(a.valor); // 99  ← ¡a también ha cambiado!

📘 Concepto: Es como compartir la dirección de una casa: si le das a alguien tu dirección y pinta la puerta de rojo, cuando tú llegues también verás la puerta roja. Solo hay una casa. En el módulo 07 aprenderás a hacer copias reales con spread y structuredClone().

flowchart LR
    subgraph Primitivos
        X["x: 10"]
        Y["y: 20"]
    end
    subgraph Objetos
        A["a"] --> O["{ valor: 99 }"]
        B["b"] --> O
    end

15. La zona muerta temporal (TDZ)

Vimos que var sube al principio de su ámbito con valor undefined. let y const también suben, pero sin inicializar: desde el comienzo del bloque hasta la línea donde se declaran, la variable está en la zona muerta temporal (Temporal Dead Zone, TDZ). Usarla ahí produce un error:

console.log(producto); // ❌ ReferenceError: Cannot access 'producto' before initialization
const producto = 'Teclado';
console.log(producto); // Teclado
{
  // ── inicio de la TDZ de "total" ──
  // console.log(total); ❌ ReferenceError
  // total = 5;          ❌ ReferenceError
  let total = 0;       // ── fin de la TDZ ──
  total = 5;           // ✅
  console.log(total);  // 5
}

📘 Concepto: Fíjate en la diferencia entre los mensajes: Cannot access 'producto' before initialization (la variable existe pero estás en su TDZ) y producto is not defined (la variable no existe en absoluto). La TDZ es una ventaja: convierte un error silencioso de var en un error claro.

💡 Consejo: La forma de no tropezar nunca con la TDZ es simple: declara las variables al principio del bloque donde las usas y antes de leerlas.


16. Ejercicios

Ejercicio 1: Ficha personal

Crea un fichero ficha.mjs que declare con const o let (decide tú cuál en cada caso y justifícalo en un comentario) tu nombre, tu año de nacimiento, el año actual, tu ciudad y si tienes carné de conducir. Calcula tu edad y muestra con una plantilla de texto: Me llamo Ana, tengo 28 años, vivo en Madrid y tengo carné: true.

Ejercicio 2: Detective de tipos

Sin ejecutar, escribe qué devuelve typeof para cada valor. Después compruébalo en la consola y explica los que hayas fallado: 42, '42', 42n, null, undefined, [], {}, NaN, function () {}, Symbol(), true, typeof 42.

Ejercicio 3: Conversor seguro

Escribe un programa con un array de textos ['42', '3.14', '', '12px', 'abc', ' 7 ', '0x10']. Para cada uno, muestra en una tabla (console.table con un array de objetos) el resultado de Number(), parseInt(texto, 10) y parseFloat(), y si el resultado de Number() es un número válido según Number.isNaN().

Ejercicio 4: Truthy o falsy

Crea un array con estos valores: 0, '0', '', ' ', null, undefined, [], {}, NaN, -1, 0n, 'false'. Recórrelo (puedes usar for (const valor of valores), que verás en el módulo 04) y muestra para cada uno si es truthy o falsy usando Boolean(). Compara el resultado con la tabla de la sección 10.

Ejercicio 5: El ticket de la compra

Tienes estos precios: 19.99, 5.01, 0.10, 0.20, 3.33. Calcula el total de tres formas: (a) sumando los number directamente, (b) convirtiendo cada precio a céntimos enteros con Math.round(precio * 100), sumando y dividiendo entre 100, y (c) con Math.sumPrecise si está disponible (usa detección de características). Muestra los tres resultados y el total formateado con toFixed(2).

Ejercicio 6: Números enormes

Calcula 2 ** 60 con number y con BigInt (2n ** 60n) y compara los resultados. Después calcula el factorial de 25 (1 × 2 × 3 × ... × 25) multiplicando a mano (puedes escribir todas las multiplicaciones) primero con number y luego con BigInt. ¿Cuál es exacto? Comprueba con Number.isSafeInteger el resultado con number.

Ejercicio 7: Caza los errores

Este código tiene cinco problemas (algunos dan error y otros dan resultados incorrectos). Encuéntralos, explícalos y corrígelos:

console.log(descuento);
const descuento = 0.1;
var total = '100';
const final = total + total * descuento;
const IVA;
IVA = 0.21;
if (final == NaN) {
  console.log('Error en el cálculo');
}

Resumen

Concepto Sintaxis / idea clave
Variable reasignable let contador = 0;
Constante (por defecto) const IVA = 0.21; (el objeto interior sí puede cambiar)
var Ámbito de función, hoisting con undefined, redeclarable: no usar
Nombres camelCase; PascalCase para clases; UPPER_SNAKE_CASE para configuración
Primitivos number, string, boolean, undefined, null, bigint, symbol
Tipo de un valor typeof x (ojo: typeof null === 'object')
¿Es array? Array.isArray(x)
¿Es NaN? Number.isNaN(x)
¿Es entero? Number.isInteger(x), Number.isSafeInteger(x)
Separadores numéricos 1_000_000
Mostrar decimales n.toFixed(2) (devuelve texto)
Suma precisa (ES2026) Math.sumPrecise([0.1, 0.2, 0.3]) → 0.6
Enteros enormes 123n, BigInt('123') (no mezclar con number)
Falsy false, 0, -0, 0n, '', null, undefined, NaN
Conversión a número Number(x), parseInt(x, 10), parseFloat(x), +x
Conversión a texto / booleano String(x) / Boolean(x), !!x
Coerción con + Si hay un texto, concatena
Valor frente a referencia Primitivos se copian; objetos se comparten
TDZ let/const no se pueden usar antes de su declaración