Copias un tutorial de JavaScript y ves let, const y var mezclados sin ningún criterio aparente. Unos usan uno, otros usan otro, y nadie te explica por qué. Al final acabas usando lo primero que te sale y cruzando los dedos.
Aquí es donde la mayoría se atasca: no es que let, const y var hagan cosas muy distintas por fuera, es que las diferencias están en cómo se comportan por dentro — y eso es precisamente lo que causa bugs raros más adelante, cuando ya no te acuerdas de qué variable declaraste con qué.
¿Qué son las variables?
Una variable es como una caja donde guardas información que puedes usar después.
🏪 Analogía del almacén:
Imagina un almacén con cajas etiquetadas:
- Caja "nombre" → Contiene "Juan"
- Caja "edad" → Contiene 25
- Caja "email" → Contiene "juan@email.com"
Las variables funcionan igual: tienen un nombre y contienen un valor.
Ejemplo básico de variables:
// Crear variables (declarar)
let nombre = "Ana";
let edad = 22;
let esEstudiante = true;
// Usar las variables
console.log("Hola, soy " + nombre);
console.log("Tengo " + edad + " años");
console.log("¿Soy estudiante? " + esEstudiante);
Fíjate en algo del ejemplo de arriba: en ningún sitio dijiste que nombre era texto, que edad era un número o que esEstudiante era verdadero/falso. En otros lenguajes tienes que declarar el tipo a mano (algo como string nombre = "Ana"). En JavaScript no — la variable simplemente coge el tipo del valor que le metas, y ese tipo puede cambiar según lo que le asignes después. Esto se llama tipado dinámico.
El operador typeof
Si en algún momento no tienes claro qué tipo de dato tiene una variable (te pasará más de lo que crees, sobre todo cuando el dato viene de un formulario o de una API), usa typeof:
let dato = "Hola";
console.log(typeof dato); // "string"
dato = 42;
console.log(typeof dato); // "number"
dato = true;
console.log(typeof dato); // "boolean"
let sinValor;
console.log(typeof sinValor); // "undefined" - declarada pero sin valor todavía
⚠️ El tipado dinámico también es una trampa:
Que dato pueda ser un string y dos líneas más abajo un number es cómodo, pero es justo lo que hace que se cuelen bugs raros. Si una función espera recibir un número y por lo que sea le llega un string, JavaScript no te va a avisar — simplemente hará algo que probablemente no es lo que esperabas (lo verás en la siguiente sección). Cuando algo se comporte de forma rara en tu código, poner un console.log(typeof variable) antes de la línea sospechosa es de las formas más rápidas de encontrar el problema.
let vs const vs var: las diferencias importantes
1. var (la forma antigua)
var nombre = "Pedro";
var edad = 30;
// Puedes cambiar el valor
nombre = "Luis";
edad = 31;
console.log(nombre); // "Luis"
console.log(edad); // 31
Características de var:
- ✅ Se puede cambiar el valor
- ✅ Se puede declarar otra vez
- ⚠️ Tiene scope de función (explicado abajo)
- ❌ Puede causar bugs extraños
2. let (recomendado para valores que cambian)
let puntos = 0;
let nombreJugador = "María";
// Puedes cambiar el valor
puntos = 100;
nombreJugador = "Carmen";
console.log(puntos); // 100
console.log(nombreJugador); // "Carmen"
Características de let:
- ✅ Se puede cambiar el valor
- ❌ NO se puede declarar otra vez en el mismo scope
- ✅ Tiene block scope (más seguro)
- ✅ Es la forma moderna y recomendada
3. const (para valores constantes)
const PI = 3.14159;
const nombre = "StudyCode Pro";
const añoActual = 2025;
// ❌ Esto daría error - no puedes cambiar const
// PI = 3.14; // Error!
// nombre = "Otro nombre"; // Error!
console.log(PI); // 3.14159
console.log(nombre); // "StudyCode Pro"
Características de const:
- ❌ NO se puede cambiar el valor
- ❌ NO se puede declarar otra vez
- ✅ Tiene block scope
- ✅ Ideal para valores que no cambian
Comparación práctica
// Ejemplo con los tres tipos
var apellido = "García"; // Forma antigua
let edad = 25; // Para valores que pueden cambiar
const email = "ana@test.com"; // Para valores constantes
// Casos de uso reales:
let contador = 0; // ✅ Bueno - contador puede cambiar
const maxIntentos = 3; // ✅ Bueno - máximo no cambia
var usuario = "Ana"; // ⚠️ Mejor usar let
// Cambiar valores
contador = contador + 1; // ✅ Funciona
edad = edad + 1; // ✅ Funciona
// email = "nuevo@test.com"; // ❌ Error - const no se puede cambiar
Scope: dónde existen las variables
Scope significa "en qué parte del código puedes usar una variable".
Block Scope (let y const):
function ejemplo() {
let nombre = "Global";
if (true) {
let nombre = "Dentro del bloque";
const apellido = "López";
console.log(nombre); // "Dentro del bloque"
console.log(apellido); // "López"
}
console.log(nombre); // "Global"
// console.log(apellido); // ❌ Error - apellido no existe aquí
}
ejemplo();
Function Scope (var):
function problemaConVar() {
var mensaje = "Inicio";
if (true) {
var mensaje = "Dentro del if"; // ¡Cambia la variable original!
}
console.log(mensaje); // "Dentro del if" - ¡No es lo que esperabas!
}
problemaConVar();
⚠️ Por qué var puede causar problemas:
Con var, las variables "escapan" de los bloques if, for, etc. Esto puede crear bugs difíciles de encontrar.
Recomendación: Usa let y const siempre. Olvídate de var.
Este ejemplo del if pisando la variable de fuera parece una tontería aquí, con cuatro líneas de código. El problema de verdad aparece cuando tienes una función de cien líneas con varios if y bucles anidados usando var, y de repente una variable cambia de valor sin que tú la hayas tocado en ese punto del código. Buscar ese bug a las tantas de la noche es justo por lo que se dejó de usar var.
Bucles y condicionales con variables
Condicionales (if/else)
const edad = 20;
let mensaje;
if (edad >= 18) {
mensaje = "Eres mayor de edad";
} else if (edad >= 13) {
mensaje = "Eres adolescente";
} else {
mensaje = "Eres menor de edad";
}
console.log(mensaje); // "Eres mayor de edad"
// Forma más corta con operador ternario
const estado = edad >= 18 ? "adulto" : "menor";
console.log(estado); // "adulto"
== vs ===: el error que comete todo el mundo al principio
Antes hablábamos del tipado dinámico y de que una variable puede cambiar de tipo. Pues aquí es donde eso te muerde de verdad: JavaScript tiene dos formas de comparar si dos valores son iguales, y se comportan distinto.
// == compara el VALOR, convirtiendo tipos si hace falta
console.log(5 == "5"); // true - convierte "5" a número antes de comparar
console.log(0 == false); // true - convierte false a 0 antes de comparar
// === compara VALOR Y TIPO, sin convertir nada
console.log(5 === "5"); // false - un number nunca es igual a un string
console.log(0 === false); // false - un number nunca es igual a un boolean
❌ Por qué esto la lía tanto:
Imagina que lees un número de un formulario HTML — todo lo que viene de un <input> llega como string, aunque sea type="number". Si comparas edad == 18 te puede colar cosas que no esperas, porque "18" == 18 da true. Con === ese mismo caso sería false, porque uno es string y el otro number — y probablemente eso es justo lo que quieres saber.
Regla que te ahorra dolores de cabeza: usa siempre === y !== (su versión de "distinto"). Casi nunca necesitas ==, y cuando lo usas sin darte cuenta de la conversión automática, es cuando aparecen los bugs más difíciles de encontrar.
switch: cuando tienes muchos else if seguidos
Si te fijas en el ejemplo de arriba, comparábamos una única variable (edad) contra varios valores distintos con else if. Cuando esa cadena de comparaciones crece mucho — cinco, seis, diez casos posibles — se vuelve difícil de leer. Para ese caso concreto existe switch:
const diaSemana = 3;
let nombreDia;
switch (diaSemana) {
case 1:
nombreDia = "Lunes";
break;
case 2:
nombreDia = "Martes";
break;
case 3:
nombreDia = "Miércoles";
break;
default:
nombreDia = "Día no válido";
}
console.log(nombreDia); // "Miércoles"
switch compara diaSemana contra cada case usando comparación estricta (como ===, no como ==), y ejecuta el bloque que coincide. default es el equivalente al else final: se ejecuta si ningún case encajó.
❌ El error que te va a pasar seguro con switch:
Olvidarte del break. Sin él, JavaScript no se detiene al encontrar el case que coincide — sigue ejecutando también el código de los siguientes case, aunque no coincidan. A eso se le llama fall-through, y es de las cosas que más rabia da la primera vez que te pasa, porque el código "parece" correcto a simple vista.
Bucle for clásico
// Contar del 1 al 5
for (let i = 1; i <= 5; i++) {
console.log("Número: " + i);
}
// i no existe aquí - está limitada al scope del for
// console.log(i); // ❌ Error
// Ejemplo práctico: tabla de multiplicar
const numero = 7;
for (let i = 1; i <= 10; i++) {
const resultado = numero * i;
console.log(numero + " x " + i + " = " + resultado);
}
break y continue: controlar el bucle desde dentro
Un for normal recorre todas las vueltas que le dijiste, sin excepción. Pero a veces quieres cortar antes de tiempo, o saltarte una vuelta concreta sin parar el bucle entero. Para eso están break y continue:
// break: para el bucle por completo
for (let i = 1; i <= 10; i++) {
if (i === 5) {
break; // Sale del bucle en cuanto i vale 5
}
console.log(i);
}
// Imprime: 1, 2, 3, 4 (nunca llega al 5)
// continue: se salta SOLO esta vuelta, el bucle sigue
for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue; // Se salta el 3, pero el bucle sigue con el 4
}
console.log(i);
}
// Imprime: 1, 2, 4, 5 (el 3 no sale, pero el bucle no se detiene)
💡 La diferencia que la mayoría confunde al principio:
break es un "hasta aquí hemos llegado, me voy" — corta el bucle entero y sigue con el código que venga después. continue es un "esta vuelta no me interesa, pero sigo con las siguientes" — se salta lo que queda de esa iteración y pasa a la próxima. Confundirlos es fácil porque los dos "cortan" algo, solo que uno corta todo el bucle y el otro corta solo la vuelta actual.
Si vienes de algún curso o libro que insiste en que un bucle debería tener un único punto de salida, es normal que break te chirríe un poco. Pero no es un truco ni un antipatrón: es una sentencia estándar del propio lenguaje, tan oficial como if o for (documentación de MDN). El caso de uso típico es buscar algo en un array y parar en cuanto lo encuentras — sin break seguirías recorriendo el resto de elementos sin necesidad, que sí sería el problema real.
Bucle while
let intentos = 0;
const maxIntentos = 3;
while (intentos < maxIntentos) {
console.log("Intento número: " + (intentos + 1));
intentos++; // Equivale a: intentos = intentos + 1
}
console.log("Fin del bucle. Total intentos: " + intentos);
Bucle for...of (para arrays)
const frutas = ["manzana", "banana", "naranja"];
for (const fruta of frutas) {
console.log("Me gusta la " + fruta);
}
// Con índice también
for (let i = 0; i < frutas.length; i++) {
console.log(i + ": " + frutas[i]);
}
Ejemplo práctico: contador avanzado
'use strict';
// Variables para el contador
let contador = 0;
let pasos = 1;
const limiteMaximo = 100;
const limiteMinimo = -100;
// Simulamos diferentes operaciones
console.log("=== CONTADOR AVANZADO ===");
console.log("Contador inicial: " + contador);
// Incrementar varias veces
for (let i = 0; i < 5; i++) {
if (contador + pasos <= limiteMaximo) {
contador = contador + pasos;
console.log("Incrementado: " + contador);
} else {
console.log("No se puede incrementar más. Límite: " + limiteMaximo);
break;
}
}
// Cambiar el paso a 10
pasos = 10;
console.log("Cambiando pasos a: " + pasos);
// Incrementar con pasos más grandes
for (let i = 0; i < 3; i++) {
if (contador + pasos <= limiteMaximo) {
contador += pasos; // Forma corta de: contador = contador + pasos
console.log("Incrementado (+10): " + contador);
} else {
console.log("Límite alcanzado: " + limiteMaximo);
break;
}
}
// Decrementar hasta un límite
pasos = 15;
console.log("Decrementando con pasos de: " + pasos);
while (contador > limiteMinimo) {
contador -= pasos; // Forma corta de: contador = contador - pasos
if (contador < limiteMinimo) {
contador = limiteMinimo; // No permitir que baje del límite
}
console.log("Decrementado: " + contador);
// Salir si llegamos al límite
if (contador === limiteMinimo) {
console.log("Límite mínimo alcanzado!");
break;
}
}
// Resumen final
console.log("=== RESUMEN ===");
console.log("Contador final: " + contador);
console.log("Límite máximo: " + limiteMaximo);
console.log("Límite mínimo: " + limiteMinimo);
Buenas prácticas para variables
✅ Hazlo SIEMPRE:
const por defecto, let solo si necesitas cambiar el valoredadUsuario no enombreCompletoMAX_INTENTOS❌ NUNCA hagas esto:
var en código nuevoa, x, tempconst después de declararlaEjemplos de nombres buenos vs malos
// ❌ Nombres MALOS
let a = 25;
let x = "Pedro";
let f = true;
let data = [];
// ✅ Nombres BUENOS
let edadUsuario = 25;
let nombreEmpleado = "Pedro";
let esClientePremium = true;
let listaPedidos = [];
// ✅ Constantes globales
const MAX_PRODUCTOS = 100;
const URL_API = "https://api.ejemplo.com";
const COLORES = {
ROJO: "#ff0000",
VERDE: "#00ff00",
AZUL: "#0000ff"
};
'use strict': tu amigo para código más seguro
'use strict' es un modo especial de JavaScript que te ayuda a evitar errores comunes.
Sin 'use strict' (modo sloppy):
// Sin 'use strict' - permite errores silenciosos
function sinModoEstricto() {
// Variable sin declarar - crea variable global automáticamente
usuario = "Ana"; // ¡Esto es un error pero no se queja!
// Parámetros duplicados - permitido pero confuso
function problema(a, a) {
return a; // ¿Cuál de los dos 'a'?
}
// Eliminar variables - falla silenciosamente
var edad = 25;
delete edad; // No hace nada pero no da error
}
sinModoEstricto();
Con 'use strict' (recomendado):
'use strict';
function conModoEstricto() {
// Variable sin declarar - ERROR inmediato
// usuario = "Ana"; // ❌ ReferenceError: usuario is not defined
// Forma correcta
let usuario = "Ana"; // ✅ Correcto
// Parámetros duplicados - ERROR de sintaxis
// function problema(a, a) { // ❌ SyntaxError
// return a;
// }
// Forma correcta
function sinProblema(a, b) { // ✅ Correcto
return a + b;
}
return usuario;
}
console.log(conModoEstricto());
Beneficios de 'use strict'
- 🚫 Previene variables globales accidentales
- 🚫 Elimina sintaxis confusa (parámetros duplicados)
- 🚫 Prohíbe nombres reservados (let, const, class, etc.)
- ⚡ Hace el código más rápido (optimizaciones del motor)
- 🐛 Detecta errores antes (en lugar de fallar silenciosamente)
Cómo usar 'use strict'
// Opción 1: Al inicio del archivo (recomendado)
'use strict';
let nombre = "Ana";
const edad = 25;
// Todo el archivo estará en modo estricto
// Opción 2: Solo dentro de una función
function miFuncion() {
'use strict';
// Solo esta función está en modo estricto
let valor = 100;
return valor;
}
💡 Un caso en el que no hace falta escribirlo:
Todo lo de arriba vale para un script normal, el que enlazas en tu HTML así: <script src="app.js"></script>. Es la forma que has usado en todos los ejemplos de este tutorial.
Pero existe otra forma de cargar JavaScript: dividir tu código en varios archivos que se importan unos a otros con import y export, en vez de tenerlo todo suelto en uno solo. A eso se le llama trabajar con módulos (ES Modules), y para usarlos cambias la etiqueta por <script type="module" src="app.js"></script>.
Lo que te interesa aquí: si trabajas con módulos, el modo estricto se activa solo, automáticamente, sin que tengas que escribir 'use strict' en ningún sitio. Si lo pones igualmente no rompe nada, simplemente no hace falta. Cuando más adelante uses herramientas como Vite o Webpack para organizar tu proyecto en varios archivos, es muy probable que ya estés trabajando con módulos sin haberte dado cuenta.
🚨 Errores comunes y cómo evitarlos
❌ Error #1: Confundir let y const
// ❌ MAL
const puntos = 0;
puntos = 100; // Error: Assignment to constant variable
// ✅ BIEN
let puntos = 0;
puntos = 100; // Funciona perfecto
❌ Error #2: Variables sin declarar en modo estricto
'use strict';
// ❌ MAL
function sumar(a, b) {
resultado = a + b; // Error: resultado is not defined
return resultado;
}
// ✅ BIEN
function sumar(a, b) {
let resultado = a + b;
return resultado;
}
❌ Error #3: Usar const con objetos/arrays incorrectamente
// ⚠️ CONFUSO - const no impide modificar el contenido
const persona = { nombre: "Ana", edad: 25 };
persona.edad = 26; // ✅ Esto SÍ funciona
console.log(persona); // { nombre: "Ana", edad: 26 }
// ❌ Esto sí daría error
// persona = { nombre: "Luis" }; // Error: Assignment to constant
// Para objetos inmutables usa Object.freeze()
const personaInmutable = Object.freeze({ nombre: "Ana", edad: 25 });
// personaInmutable.edad = 26; // No funciona (modo estricto da error)
Ejercicio práctico
Tu misión: Crear un sistema de puntuación para un juego usando las buenas prácticas aprendidas.
🎯 Requisitos:
Solución del ejercicio
Click para ver la solución (¡intenta hacerlo solo primero!)
'use strict';
// Constantes del juego
const PUNTOS_POR_NIVEL = 100;
const BONUS_PERFECTO = 50;
const MAX_VIDAS = 3;
const PUNTOS_VIDA_EXTRA = 500;
// Estado del jugador
let puntuacionTotal = 0;
let vidasRestantes = MAX_VIDAS;
let nivelActual = 1;
let partidasJugadas = 0;
// Simular varias rondas de juego
console.log("=== JUEGO DE PUNTUACIÓN ===");
console.log("Vidas iniciales: " + vidasRestantes);
console.log("Puntuación inicial: " + puntuacionTotal);
// Jugar 10 rondas
for (let ronda = 1; ronda <= 10; ronda++) {
console.log("\n--- Ronda " + ronda + " ---");
// Simular resultado aleatorio (true = éxito, false = fallo)
const exito = Math.random() > 0.3; // 70% de probabilidad de éxito
if (exito) {
// Calcular puntos base
let puntosRonda = PUNTOS_POR_NIVEL * nivelActual;
// Bonus si es perfecto (20% de probabilidad)
const esPerfecto = Math.random() > 0.8;
if (esPerfecto) {
puntosRonda += BONUS_PERFECTO;
console.log("¡PERFECTO! Bonus: +" + BONUS_PERFECTO);
}
puntuacionTotal += puntosRonda;
console.log("¡Éxito! Puntos ganados: " + puntosRonda);
// Subir de nivel cada 3 rondas exitosas
if (ronda % 3 === 0) {
nivelActual++;
console.log("¡NIVEL UP! Nuevo nivel: " + nivelActual);
}
// Vida extra cada 500 puntos
if (puntuacionTotal >= PUNTOS_VIDA_EXTRA && vidasRestantes < MAX_VIDAS) {
vidasRestantes++;
console.log("¡VIDA EXTRA! Vidas: " + vidasRestantes);
}
} else {
// Perder vida
vidasRestantes--;
console.log("¡Fallo! Vidas restantes: " + vidasRestantes);
// Game Over
if (vidasRestantes <= 0) {
console.log("💀 GAME OVER 💀");
break;
}
}
partidasJugadas++;
console.log("Puntuación actual: " + puntuacionTotal);
}
// Estadísticas finales
console.log("\n=== ESTADÍSTICAS FINALES ===");
console.log("Partidas jugadas: " + partidasJugadas);
console.log("Puntuación final: " + puntuacionTotal);
console.log("Nivel alcanzado: " + nivelActual);
console.log("Vidas restantes: " + vidasRestantes);
// Calcular ranking
let ranking;
if (puntuacionTotal >= 1000) {
ranking = "MAESTRO";
} else if (puntuacionTotal >= 500) {
ranking = "EXPERTO";
} else if (puntuacionTotal >= 200) {
ranking = "INTERMEDIO";
} else {
ranking = "PRINCIPIANTE";
}
console.log("Ranking: " + ranking);
¿Qué sigue después?
Con esto ya tienes lo que necesitas para declarar variables sin liarla y controlar el flujo de tu código con bucles y condicionales. El siguiente paso natural es meterte con funciones en JavaScript — es donde todo esto empieza a organizarse en bloques reutilizables en vez de código suelto.
Cuando quieras seguir avanzando, tienes objetos en JavaScript para estructurar datos más complejos, manipulación del DOM para que tu código toque de verdad la página web, y async/await cuando te toque trabajar con peticiones y datos que tardan en llegar.
💪 Recuerda:
- Usa const por defecto, let solo si necesitas cambiar el valor
- Siempre pon 'use strict' al inicio de tus archivos
- Nombres de variables descriptivos y claros
- Practica creando proyectos reales, no solo ejercicios
🚀 ¿Te gustó este tutorial?
Únete a StudyCode Pro para más tutoriales como este, roadmaps completos y acceso a nuestra comunidad de developers que te ayudará a conseguir tu primer trabajo en tech.
📬 Únete Gratis