JavaScript básico: Variables, bucles, condicionales y buenas prácticas

Tutorial completo desde cero: let/const/var, tipado dinámico, == vs ===, switch, bucles con break/continue, scope y 'use strict'

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:

Declaración y uso 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)

Variables con var - 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:

2. let (recomendado para valores que cambian)

Variables con let - forma moderna
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:

3. const (para valores constantes)

Variables con const - 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:

Comparación práctica

Comparación práctica entre var, let y const
// 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):

Block scope con 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):

Problemas con function scope de 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)

Condicionales if/else y operador ternario
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

Bucle for clásico con ejemplos prácticos
// 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

Bucle while con contador de intentos
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)

Bucle for...of para recorrer 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

Contador avanzado con bucles y condicionales
'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:

Usa const por defecto, let solo si necesitas cambiar el valor
Nombres descriptivos: edadUsuario no e
camelCase para variables: nombreCompleto
MAYÚSCULAS para constantes globales: MAX_INTENTOS
Declara variables al principio de su scope

❌ NUNCA hagas esto:

Usar var en código nuevo
Variables globales innecesarias
Nombres confusos: a, x, temp
Cambiar const después de declararla
Variables sin declarar (modo no estricto)

Ejemplos de nombres buenos vs malos

Buenos vs malos nombres de variables
// ❌ 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):

Código sin 'use strict' - modo permisivo
// 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):

Código con 'use strict' - modo estricto
'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'

Cómo usar 'use strict'

Cómo usar 'use strict' correctamente
// 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

Error común: 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

Error: 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

Error: usar const con objetos 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:

Usar 'use strict'
Variables con nombres descriptivos
const para valores que no cambian
let para valores que sí cambian
Bucles y condicionales

Solución del ejercicio

Click para ver la solución (¡intenta hacerlo solo primero!)
Solución completa del ejercicio: sistema de puntuación
'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