JavaScript desde cero: la etiqueta script, tipos de datos, la consola, variables y strings

Por: Artiko
htmlcssjavascriptdomfrontendvariablesstringsconsolatipos-de-datosetiqueta-scripthoisting

JavaScript desde cero: la etiqueta script, tipos de datos, la consola, variables y strings

En el capítulo 7 cerramos la parte del curso dedicada al documento: mapas de imagen, iframes, divisores y el resto de las piezas con las que se arma una página completa. Con eso sabes describir qué hay en una página (HTML) y decidir cómo se ve (CSS). Lo que todavía no puedes hacer es que la página reaccione: que cuente algo, que cambie un texto cuando el usuario hace clic, que valide un formulario antes de enviarlo, que recuerde un dato. Todo eso es comportamiento, y el comportamiento en la web se escribe en un tercer lenguaje: JavaScript.

Este capítulo es el punto de entrada. No vamos a tocar el DOM todavía (eso viene después): primero necesitas el lenguaje en sí. Vamos a ver cómo se conecta un script a una página, qué tipos de datos existen, cómo escribir comentarios y hablar con la consola del navegador, cómo se declaran variables con var, let y const (y por qué esas tres palabras no son intercambiables), y cómo funcionan las cadenas de texto, que son el tipo de dato con el que más vas a trabajar al principio.

El índice completo del curso vive en /tecnologias/superclubnet/00-indice/.

Qué es JavaScript y qué no es

JavaScript es un lenguaje de programación creado en 1995 para darle interactividad a las páginas web. Nació en diez días, con un nombre elegido por razones de marketing (para asociarlo a Java, que en ese momento estaba de moda) y con el que no tiene ninguna relación técnica más allá de un parecido superficial en la sintaxis de las llaves y los puntos y coma.

Ese origen apurado explica varias rarezas del lenguaje que vas a encontrar más adelante. Pero también hay que decir lo otro: JavaScript se estandarizó bajo el nombre ECMAScript, y desde 2015 recibe una revisión anual que lo ha convertido en un lenguaje moderno y perfectamente serio. Las versiones se nombran por año: ES2015 (también llamada ES6), ES2016, ES2017, y así sucesivamente. Cuando alguien dice “esto es sintaxis moderna” normalmente se refiere a ES2015 en adelante.

Hoy JavaScript corre en tres lugares distintos:

EntornoDónde correPara qué se usa
NavegadorChrome, Firefox, Safari, EdgeInteractividad de páginas web: eventos, DOM, animaciones, peticiones a servidores
ServidorNode.js, Deno, BunAPIs, servidores web, herramientas de línea de comandos
OtrosAplicaciones de escritorio (Electron), móviles (React Native), bases de datos, incluso microcontroladoresMuy variado

En este curso nos quedamos exclusivamente en el primero: JavaScript en el navegador, ejecutándose dentro de una página HTML. Es el caso de uso original y el que te permite ver el resultado de inmediato.

Una aclaración importante sobre los tres lenguajes de la web y cómo se reparten el trabajo:

flowchart LR
    subgraph pagina["Una pagina web"]
        H["HTML<br/>estructura y contenido"]
        C["CSS<br/>presentacion visual"]
        J["JavaScript<br/>comportamiento"]
    end
    H -->|"define el arbol de elementos"| DOM["DOM<br/>el documento vivo en memoria"]
    C -->|"aplica reglas de estilo"| DOM
    J -->|"lee y modifica"| DOM
    DOM --> P["Lo que el usuario ve<br/>en la pantalla"]

La clave de ese diagrama es que HTML y CSS son declarativos: describes un estado final y el navegador lo materializa. JavaScript es imperativo: das instrucciones paso a paso, en un orden concreto, y el navegador las ejecuta en ese orden. Esa diferencia de mentalidad es la parte más grande del salto que estás por dar.

La etiqueta script

Para que un navegador ejecute JavaScript hay que decirle dónde está el código. Eso se hace con la etiqueta <script>, y hay dos maneras de usarla.

Código en línea

La primera es escribir el código directamente dentro de la etiqueta, en el mismo archivo HTML:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Mi primer script</title>
</head>
<body>
  <h1>Hola</h1>

  <script>
    console.log("Este mensaje viene desde JavaScript");
  </script>
</body>
</html>

Guarda eso como index.html, ábrelo en el navegador, pulsa F12 y ve a la pestaña Console. Vas a ver el mensaje. Ese es tu primer programa.

Fíjate en que la etiqueta <script> no lleva atributos en este ejemplo. En documentos antiguos vas a encontrar <script type="text/javascript">, que era obligatorio en la época de HTML 4 porque existía la posibilidad teórica de otros lenguajes de scripting (VBScript en Internet Explorer, sobre todo). En HTML5 el valor por defecto ya es JavaScript, así que el atributo type sobra. Escribirlo no rompe nada, pero es ruido innecesario. La única razón moderna para poner type es el valor module, que veremos al final de esta sección.

Archivo externo

La segunda manera es poner el código en un archivo .js aparte y enlazarlo:

<script src="scripts/main.js"></script>

Y en scripts/main.js:

console.log("Este mensaje viene desde un archivo externo");

Esta es la forma que vas a usar en la práctica para casi todo. Las razones son las mismas por las que sacamos el CSS del HTML: separación de responsabilidades, reutilización del mismo archivo en varias páginas, y sobre todo cacheo. Si el navegador ya descargó main.js para una página, no lo vuelve a descargar para la siguiente.

Hay un detalle que confunde a mucha gente: una etiqueta <script> con src ignora por completo lo que haya entre sus etiquetas de apertura y cierre. Esto no funciona:

<!-- MAL: el console.log nunca se ejecuta -->
<script src="scripts/main.js">
  console.log("Esto se ignora");
</script>

Si necesitas ambas cosas, usa dos etiquetas separadas:

<script src="scripts/main.js"></script>
<script>
  console.log("Esto sí se ejecuta");
</script>

Y otro detalle: la etiqueta <script> nunca se autocierra. <script src="main.js" /> no es válido en HTML y el navegador lo interpretará mal, tragándose todo el contenido siguiente. Siempre <script ...></script> con cierre explícito.

Dónde poner la etiqueta y por qué importa

Aquí es donde el asunto se pone interesante. El navegador lee el HTML de arriba hacia abajo, construyendo el árbol del documento a medida que avanza. Cuando se topa con un <script> sin atributos especiales, detiene la construcción del documento, descarga el archivo si es externo, lo ejecuta completo, y solo entonces continúa leyendo el HTML.

Eso tiene dos consecuencias:

  1. Un script pesado al principio del documento retrasa la aparición del contenido. El usuario ve una página en blanco mientras el script se descarga y ejecuta.
  2. Un script que intenta tocar elementos HTML que todavía no se han leído no los encuentra, porque literalmente no existen aún.
sequenceDiagram
    participant N as Navegador
    participant P as Parser HTML
    participant S as Servidor
    N->>P: Empieza a leer index.html
    P->>P: Construye head, h1, p
    P->>P: Encuentra script src="main.js"
    Note over P: PARSER BLOQUEADO
    P->>S: GET main.js
    S-->>P: contenido de main.js
    P->>P: Ejecuta main.js completo
    Note over P: el resto del HTML aun no existe
    P->>P: Continua: footer, imagenes
    P->>N: Documento listo

La solución clásica, y la que recomienda la guía original, es poner los scripts justo antes de </body>:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>Scripts al final</title>
</head>
<body>
  <h1 id="titulo">Hola</h1>
  <p>Todo el contenido de la página va aquí.</p>

  <script src="scripts/main.js"></script>
</body>
</html>

Cuando el parser llega ahí, todo el documento ya está construido, así que el script puede acceder a cualquier elemento sin problemas y el usuario ya vio el contenido.

defer y async

Existe una solución mejor, disponible desde hace más de una década: los atributos defer y async. Ambos solo funcionan en scripts externos (con src); en scripts en línea se ignoran.

<head>
  <script src="scripts/main.js" defer></script>
</head>

Con defer, el navegador descarga el archivo en paralelo con la construcción del documento, sin bloquear nada, y lo ejecuta cuando el documento está completo. Es el mejor de los dos mundos: descarga temprana y ejecución tardía.

Con async el navegador también descarga en paralelo, pero ejecuta el script en cuanto termina la descarga, interrumpiendo el parser en ese momento indeterminado. Sirve para scripts totalmente independientes que no dependen del documento ni de otros scripts (analítica, por ejemplo).

flowchart TD
    A["El parser encuentra un script externo"] --> B{"Que atributos tiene?"}
    B -->|"ninguno"| C["Bloquea el parser<br/>descarga y ejecuta ahora"]
    B -->|"defer"| D["Descarga en paralelo<br/>ejecuta al terminar el documento<br/>respeta el orden de aparicion"]
    B -->|"async"| E["Descarga en paralelo<br/>ejecuta apenas termina la descarga<br/>orden impredecible"]
    C --> F["Documento listo"]
    D --> F
    E --> F

Tabla de referencia rápida:

FormaBloquea el parserCuándo se ejecutaOrden entre scriptsCuándo usarlo
<script> en <head>Inmediatamente, antes de que exista el bodyGarantizadoCasi nunca
<script> antes de </body>Sí, pero al finalCuando ya está todo el HTMLGarantizadoOpción simple y correcta
<script defer>NoAl terminar de construir el documentoGarantizadoRecomendado por defecto
<script async>No la descarga; sí la ejecuciónApenas termina la descargaNo garantizadoScripts independientes
<script type="module">NoComo defer (es el comportamiento por defecto)GarantizadoCódigo organizado en módulos

El último caso, type="module", activa el sistema de módulos de JavaScript, que permite dividir el código en archivos que se importan entre sí. Es lo que se usa en proyectos reales hoy, tiene defer implícito y algunas diferencias de comportamiento (por ejemplo, siempre corre en modo estricto). No lo vamos a usar en este curso, pero conviene que reconozcas el atributo cuando lo veas.

Para todo lo que hagamos de aquí en adelante, esta es la plantilla de trabajo:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Laboratorio de JavaScript</title>
  <script src="scripts/main.js" defer></script>
</head>
<body>
  <h1>Laboratorio</h1>
  <p>Abre la consola con F12 para ver la salida.</p>
</body>
</html>

Comentarios

Antes de escribir código de verdad, dos formas de escribir código que no se ejecuta.

// Esto es un comentario de una línea.
// Todo lo que va después de las dos barras se ignora hasta el final de la línea.

/*
   Esto es un comentario de varias líneas.
   Empieza con barra-asterisco y termina con asterisco-barra.
   Puede ocupar todas las líneas que quieras.
*/

const precio = 1000; // los comentarios también pueden ir al final de una línea de código

Los comentarios de varias líneas no se pueden anidar: el primer */ cierra el bloque, aunque hayas abierto dos. Este código es un error de sintaxis:

/*
   /* comentario interior */
   esta línea ya no está comentada y rompe el programa
*/

Sobre cuándo comentar: un comentario que repite lo que el código ya dice no aporta nada.

// MAL: el comentario no dice nada que el código no diga
// suma uno a contador
contador = contador + 1;

// BIEN: el comentario explica el porqué, que el código no puede expresar
// El servidor cuenta desde 1, no desde 0, así que ajustamos antes de enviar.
contador = contador + 1;

Los comentarios también sirven como interruptor mientras depuras: comentar una línea es más rápido y más reversible que borrarla.

La consola

La consola del navegador es tu herramienta principal para ver qué está haciendo tu programa. Se abre con F12 (o Ctrl+Shift+I en Windows y Linux, Cmd+Option+I en macOS) y luego eligiendo la pestaña Console.

Tiene dos usos distintos:

  1. Como salida: tu código escribe mensajes ahí con console.log() y tú los lees.
  2. Como entrada: puedes escribir JavaScript directamente en la consola y ver el resultado al instante. Es el mejor lugar para experimentar mientras aprendes.

Prueba lo segundo ahora mismo: abre la consola, escribe 2 + 2 y pulsa Enter. El resultado aparece de inmediato. Escribe "hola".toUpperCase() y pulsa Enter. Todo lo que veas en este capítulo lo puedes probar así, sin crear archivos.

console.log y sus parientes

console.log() acepta cualquier cantidad de argumentos separados por comas y los imprime uno tras otro separados por un espacio:

console.log("Hola");
console.log("Hola", "mundo", 42, true);

const nombre = "Luigi";
const edad = 33;
console.log("Nombre:", nombre, "| Edad:", edad);

Esa costumbre de separar por comas en vez de concatenar con + tiene una ventaja concreta: la consola muestra cada valor con su tipo real y su formato propio. Un objeto se muestra como objeto explorable, no como el texto [object Object].

Hay varios métodos más que valen la pena:

console.log("Mensaje normal");
console.info("Mensaje informativo");
console.warn("Advertencia: algo puede salir mal");
console.error("Error: algo salió mal");

// Agrupa mensajes bajo un encabezado plegable
console.group("Datos del usuario");
console.log("Nombre: Luigi");
console.log("Edad: 33");
console.groupEnd();

// Muestra una lista o un objeto como tabla
console.table([
  { producto: "Teclado", precio: 25000 },
  { producto: "Mouse", precio: 12000 },
  { producto: "Monitor", precio: 180000 }
]);

// Mide cuánto demora un bloque de código
console.time("espera");
console.timeEnd("espera");

// Cuenta cuántas veces pasa la ejecución por una línea
console.count("iteración");
console.count("iteración");
MétodoQué haceCuándo usarlo
console.log()Imprime un mensajeUso general
console.info()Igual que log, marcado como informativoMensajes de estado
console.warn()Imprime con ícono y fondo de advertenciaSituaciones sospechosas pero no fatales
console.error()Imprime con ícono de error y traza de llamadasErrores reales
console.table()Muestra listas y objetos en formato tabularInspeccionar colecciones de datos
console.group() / groupEnd()Agrupa mensajes de forma plegableSalidas largas y repetitivas
console.time() / timeEnd()Mide tiempo transcurridoComparar rendimiento
console.count()Cuenta llamadas con la misma etiquetaVerificar cuántas veces se ejecuta algo
console.clear()Limpia la consolaEmpezar de cero

Un aviso práctico: los console.log() no deben quedar en el código que publicas. No rompen nada, pero ensucian la consola de quien visita tu sitio y a veces filtran información que no querías mostrar. Bórralos cuando termines de depurar.

alert, prompt y confirm

Existen tres funciones que muestran ventanas emergentes del navegador:

alert("Hola, esto es un aviso");
const nombre = prompt("¿Cómo te llamas?");
const seguro = confirm("¿Seguro que quieres continuar?");
console.log("Hola,", nombre, "| respondió:", seguro);

Sirven para experimentar rápido, pero bloquean por completo la página mientras están abiertas, no se pueden estilizar y en algunos contextos el navegador directamente las suprime. Úsalas para probar, nunca para una interfaz real. La consola es un mejor lugar para ver resultados mientras aprendes.

Tipos de datos

Todo valor en JavaScript tiene un tipo. El tipo determina qué puedes hacer con ese valor: puedes sumar números, puedes poner un texto en mayúsculas, no puedes poner un número en mayúsculas.

JavaScript distingue dos grandes familias:

flowchart TD
    V["Todo valor en JavaScript"] --> P["Tipos primitivos<br/>valores simples e inmutables"]
    V --> O["Tipos de referencia<br/>colecciones de valores"]
    P --> N["number<br/>42, -9.8, 0.5"]
    P --> S["string<br/>'texto'"]
    P --> B["boolean<br/>true, false"]
    P --> U["undefined<br/>no tiene valor todavia"]
    P --> NU["null<br/>vacio a proposito"]
    P --> BI["bigint<br/>enteros muy grandes"]
    P --> SY["symbol<br/>identificadores unicos"]
    O --> OB["object<br/>coleccion de pares clave-valor"]
    OB --> AR["array<br/>lista ordenada"]
    OB --> FN["function<br/>bloque invocable"]
    OB --> DT["date, map, set y otros"]

Vamos uno por uno.

number

Representa cualquier número, con o sin decimales. A diferencia de otros lenguajes, JavaScript no distingue entre enteros y decimales: todo es el mismo tipo.

const entero = 42;
const negativo = -9.8;
const grande = 1_000_000;      // el guion bajo es solo separador visual, vale 1000000
const cientifico = 1.5e6;      // notación científica: 1500000
const hexadecimal = 0xff;      // 255 en base 16
const binario = 0b1010;        // 10 en base 2
console.log(entero, negativo, grande, cientifico, hexadecimal, binario);

// Operaciones aritméticas
console.log(7 + 3);   // 10
console.log(7 - 3);   // 4
console.log(7 * 3);   // 21
console.log(7 / 3);   // 2.3333333333333335
console.log(7 % 3);   // 1   (resto de la división)
console.log(7 ** 3);  // 343 (potencia)

Hay tres valores numéricos especiales que conviene conocer desde ya:

console.log(1 / 0);              // Infinity
console.log(-1 / 0);             // -Infinity
console.log("hola" * 2);         // NaN

NaN significa “Not a Number” y aparece cuando una operación aritmética no tiene resultado numérico posible. Su rareza más famosa es que no es igual a sí mismo:

console.log(NaN === NaN);        // false
console.log(Number.isNaN(NaN));  // true  <- así se comprueba

Y la trampa clásica de los decimales, que no es exclusiva de JavaScript sino de cómo casi todos los lenguajes representan números con punto flotante:

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

La regla práctica: nunca uses números decimales para dinero. Para trabajar con pesos, guarda el valor en la unidad más pequeña (los pesos completos, o los centavos si aplica) como entero, y formatea solo al mostrar.

string

Representa texto. Es el tipo con el que más vas a trabajar y tiene su propia sección más abajo, así que por ahora basta con reconocerlo: "Hola mundo", 'Hola mundo' y `Hola mundo` son tres strings equivalentes.

boolean

Solo tiene dos valores posibles: true y false. Es el resultado de cualquier comparación y la base de las condicionales que veremos en el próximo capítulo.

const esMayor = 10 > 5;
const sonIguales = 3 === 3;
const distintos = 3 !== 4;

console.log(esMayor, sonIguales, distintos);   // true true true
console.log(typeof esMayor);                    // "boolean"

Ojo con una cosa: true y false van sin comillas. "true" con comillas es un string, no un booleano, y se comporta de forma muy distinta.

undefined

Es el valor que tiene una variable declarada a la que todavía no le has asignado nada. No lo escribes tú: aparece solo.

let sinValor;
console.log(sinValor);          // undefined
console.log(typeof sinValor);   // "undefined"

También es lo que devuelve una función que no retorna nada explícitamente, y lo que obtienes al pedir una propiedad que no existe.

null

Representa la ausencia intencional de valor. La diferencia con undefined es de intención: undefined significa “esto todavía no tiene valor”, null significa “esto está vacío y yo lo dejé así a propósito”.

let usuarioActual = null;   // todavía nadie inició sesión, y lo digo explícitamente
console.log(usuarioActual);         // null
console.log(typeof usuarioActual);  // "object"  <- este es un error histórico del lenguaje

Ese typeof null === "object" es un bug de la primera versión de JavaScript que nunca se pudo corregir porque habría roto demasiadas páginas. Es la rareza más citada del lenguaje. Simplemente memorízala.

object

Un objeto es una colección de pares clave-valor. Es la estructura de datos general del lenguaje.

const persona = {
  nombre: "Luigi",
  apellido: "Mario",
  edad: 33,
  activo: true
};

console.log(persona);
console.log(persona.nombre);       // "Luigi"
console.log(persona["apellido"]);  // "Mario"
console.log(typeof persona);       // "object"

array

Un array es una lista ordenada de valores. Técnicamente es un objeto, pero con comportamiento propio.

const numeros = [10, 20, 30, 40];
const mezclado = ["texto", 42, true, null];

console.log(numeros[0]);          // 10   (el primer elemento es el índice 0)
console.log(numeros.length);      // 4
console.log(typeof numeros);      // "object"
console.log(Array.isArray(numeros)); // true  <- así se comprueba de verdad

Los arrays y las funciones son el tema central del capítulo 9, así que aquí solo los nombramos.

function

Una función es un bloque de código que se puede invocar. En JavaScript las funciones son valores: se pueden guardar en variables, pasar como argumento y devolver como resultado.

function saludar(nombre) {
  return "Hola, " + nombre;
}
console.log(saludar("Luigi"));  // "Hola, Luigi"
console.log(typeof saludar);    // "function"

bigint y symbol

Los dos tipos que casi nunca vas a necesitar al principio, pero que existen y conviene reconocer.

const enorme = 9007199254740993n;    // el sufijo n lo convierte en bigint
const id = Symbol("identificador");  // valor único e irrepetible
console.log(typeof enorme, typeof id);   // "bigint" "symbol"

bigint sirve para enteros más grandes que los que number puede representar con precisión. symbol sirve para crear claves de objeto que nunca colisionan con otras.

El operador typeof

typeof te dice el tipo de cualquier valor. Es la herramienta que usaste implícitamente en todos los ejemplos anteriores.

console.log(typeof 42);           // "number"
console.log(typeof "texto");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);         // "object"   <- el bug histórico
console.log(typeof {});           // "object"
console.log(typeof []);           // "object"
console.log(typeof function(){}); // "function"
console.log(typeof 10n);          // "bigint"
console.log(typeof Symbol());     // "symbol"

Tabla resumen para tener a mano:

TipoEjemplo de valortypeof devuelveFamiliaNotas
number42, -9.8, NaN"number"PrimitivoEnteros y decimales son el mismo tipo
string"hola""string"PrimitivoInmutable
booleantrue, false"boolean"PrimitivoSin comillas
undefinedundefined"undefined"PrimitivoAparece solo
nullnull"object"PrimitivoEl typeof está mal por razones históricas
bigint10n"bigint"PrimitivoEnteros arbitrariamente grandes
symbolSymbol("x")"symbol"PrimitivoIdentificadores únicos
object{ a: 1 }"object"ReferenciaBase de todo lo compuesto
array[1, 2]"object"ReferenciaComprobar con Array.isArray()
functionfunction(){}"function"ReferenciaTécnicamente es un objeto

Primitivo contra referencia

La diferencia entre las dos familias no es decorativa: cambia el comportamiento al copiar.

Cuando asignas un primitivo a otra variable, se copia el valor:

let a = 10;
let b = a;
b = 20;
console.log(a);  // 10  <- a no cambió
console.log(b);  // 20

Cuando asignas un objeto o un array, se copia la referencia: ambas variables apuntan al mismo objeto en memoria.

const lista1 = [1, 2, 3];
const lista2 = lista1;
lista2.push(4);
console.log(lista1);  // [1, 2, 3, 4]  <- lista1 también cambió
console.log(lista2);  // [1, 2, 3, 4]
flowchart LR
    subgraph prim["Primitivo: se copia el valor"]
        A1["a = 10"] --> V1["10"]
        B1["b = a"] --> V2["10<br/>(copia independiente)"]
    end
    subgraph ref["Referencia: se copia el puntero"]
        A2["lista1"] --> V3["[1, 2, 3]<br/>un solo array en memoria"]
        B2["lista2"] --> V3
    end

Esta es una de las fuentes de bugs más comunes cuando se empieza. Vas a volver a chocar con ella en el capítulo de arreglos.

Variables

Una variable es un nombre asociado a un valor. Se declara una vez y después se usa por su nombre.

JavaScript tiene tres palabras para declarar variables: var, let y const. Las tres funcionan, pero no son equivalentes, y elegir mal produce errores difíciles de rastrear.

const

const declara una variable que no se puede reasignar. Es la que deberías usar por defecto.

const pi = 3.14159;
console.log(pi);   // 3.14159

pi = 3;            // TypeError: Assignment to constant variable.

Una const obliga a asignar un valor en la misma línea de la declaración:

const sinValor;    // SyntaxError: Missing initializer in const declaration

Un matiz importante: const impide reasignar la variable, no impide modificar el contenido de un objeto o array. La constante es el enlace entre el nombre y el valor, no el valor en sí.

const colores = ["rojo", "verde"];
colores.push("azul");        // permitido: el array sigue siendo el mismo
console.log(colores);        // ["rojo", "verde", "azul"]

colores = ["negro"];         // TypeError: el nombre no puede apuntar a otro array
const config = { tema: "oscuro" };
config.tema = "claro";       // permitido
config.idioma = "es";        // permitido
console.log(config);         // { tema: "claro", idioma: "es" }

config = {};                 // TypeError

let

let declara una variable que sí se puede reasignar. Úsala cuando el valor tenga que cambiar.

let contador = 0;
contador = contador + 1;
contador = contador + 1;
console.log(contador);   // 2

let pendiente;           // permitido: se puede declarar sin valor
console.log(pendiente);  // undefined
pendiente = "ya tiene valor";
console.log(pendiente);

No se puede declarar dos veces la misma variable con let en el mismo ámbito:

let nombre = "Luigi";
let nombre = "Mario";   // SyntaxError: Identifier 'nombre' has already been declared

Eso, que parece una molestia, es en realidad una protección: te avisa cuando estás pisando sin querer una variable que ya existía.

var y por qué ya no se usa

var es la forma original de declarar variables, la única que existió hasta 2015. Sigue funcionando, pero tiene tres comportamientos que causan problemas.

Problema 1: el ámbito es la función, no el bloque.

En JavaScript, un bloque es cualquier cosa entre llaves: el cuerpo de un if, de un for, de una función. let y const viven dentro del bloque donde se declaran. var ignora los bloques y se escapa a la función completa.

function ejemploVar() {
  if (true) { var mensaje = "declarada dentro del if"; }
  console.log(mensaje);   // "declarada dentro del if"  <- se escapó del bloque
}
ejemploVar();

function ejemploLet() {
  if (true) { let mensaje = "declarada dentro del if"; }
  console.log(mensaje);   // ReferenceError: mensaje is not defined
}
ejemploLet();

Ese escape es exactamente lo que no quieres: una variable que solo tenía sentido dentro del if queda contaminando toda la función.

Problema 2: permite redeclarar sin avisar.

var usuario = "Luigi";
var usuario = "Mario";    // ningún error, simplemente pisa el valor anterior
console.log(usuario);     // "Mario"

En un archivo de trescientas líneas, esto significa que puedes destruir una variable existente sin recibir ninguna advertencia.

Problema 3: el hoisting con valor undefined.

Antes de ejecutar tu código, JavaScript recorre el ámbito y “eleva” las declaraciones al principio. A eso se le llama hoisting. Con var, la variable existe desde el inicio del ámbito con valor undefined:

console.log(saludo);   // undefined  <- no falla, pero el valor no es el esperado
var saludo = "Hola";
console.log(saludo);   // "Hola"

Con let y const el hoisting también ocurre, pero la variable queda en un estado llamado zona muerta temporal (Temporal Dead Zone): existe, pero cualquier acceso antes de la línea de declaración lanza un error.

console.log(saludo);   // ReferenceError: Cannot access 'saludo' before initialization
let saludo = "Hola";

Que falle es mejor que devolver undefined en silencio: el error te apunta al problema en vez de dejar que se propague.

stateDiagram-v2
    [*] --> Hoisted: el motor recorre el ambito
    Hoisted --> VarUndefined: declarada con var
    Hoisted --> ZonaMuerta: declarada con let o const
    VarUndefined --> Asignada: se ejecuta la linea de asignacion
    ZonaMuerta --> Asignada: se ejecuta la linea de declaracion
    VarUndefined --> VarUndefined: acceso previo devuelve undefined
    ZonaMuerta --> Error: acceso previo lanza ReferenceError
    Asignada --> [*]

Tabla comparativa de las tres:

Característicavarletconst
ÁmbitoFunciónBloqueBloque
Se puede reasignarNo
Se puede redeclarar en el mismo ámbitoNoNo
Requiere valor inicialNoNo
Antes de la declaraciónundefinedReferenceErrorReferenceError
Crea propiedad en el objeto globalSí (en el ámbito global)NoNo
RecomendaciónEvitarSolo si el valor cambiaPor defecto

La regla práctica, en una línea: usa const siempre; cambia a let solo cuando el compilador te obligue porque necesitas reasignar; no uses var nunca.

Nombres de variables

Las reglas del lenguaje son pocas:

  • Pueden contener letras, dígitos, _ y $.
  • No pueden empezar con un dígito.
  • Distinguen mayúsculas de minúsculas: nombre, Nombre y NOMBRE son tres variables distintas.
  • No pueden ser palabras reservadas del lenguaje (let, const, function, if, return, class, new, entre otras).
  • Sí admiten acentos y ñ, porque JavaScript acepta Unicode en los identificadores.
const nombre = "válido";
const _privado = "válido";
const $elemento = "válido";
const contador2 = "válido";
const año = "válido, aunque poco común";
// const 2contador = "inválido: empieza con dígito";
// const const = "inválido: palabra reservada";
// const mi-variable = "inválido: el guion es el operador de resta";

Las convenciones, que no son reglas pero sí lo que espera cualquiera que lea tu código:

ConvenciónFormatoSe usa paraEjemplo
camelCasePrimera palabra en minúscula, las siguientes con inicial mayúsculaVariables y funcionesnombreCompleto, calcularTotal
PascalCaseTodas las palabras con inicial mayúsculaClases y componentesUsuario, CarritoDeCompras
MAYUSCULAS_CON_GUION_BAJOTodo en mayúsculasConstantes fijas conocidas de antemanoIVA, URL_BASE

Y sobre el contenido del nombre: descriptivo gana a corto. d no dice nada; diasRestantes sí. La única excepción aceptada son los índices de bucle (i, j), donde el contexto ya es obvio.

const d = 30;                 // no dice nada
const x = d * 24;

const diasDelMes = 30;        // se explica solo
const horasDelMes = diasDelMes * 24;
console.log(horasDelMes);

Strings

Las cadenas de texto son el tipo de dato con el que más vas a trabajar en la web: títulos, nombres, mensajes, direcciones, contenido de elementos HTML. Merecen su propia sección.

Las tres formas de escribir un string

JavaScript acepta tres delimitadores distintos:

const conDobles = "Texto entre comillas dobles";
const conSimples = 'Texto entre comillas simples';
const conInvertidas = `Texto entre comillas invertidas`;

console.log(conDobles, conSimples, conInvertidas);

Las comillas dobles y simples son intercambiables: no hay ninguna diferencia de comportamiento entre ellas. La utilidad de tener dos es poder incluir una dentro de la otra sin escaparla:

const cita = "Ella dijo 'hola' y se fue";
const otra = 'El atributo se escribe class="destacado"';
console.log(cita);
console.log(otra);

Las comillas invertidas (backticks, el carácter que en muchos teclados está junto al 1 o cerca de la P) crean lo que se llama una plantilla de texto o template literal, y tienen dos capacidades que las otras dos no tienen.

Capacidad 1: múltiples líneas

Con comillas simples o dobles, un salto de línea rompe el string:

// SyntaxError: el string no puede cortarse así
// const roto = "primera línea
// segunda línea";

Con comillas invertidas, el salto de línea forma parte del texto:

const poema = `Primera línea
Segunda línea
Tercera línea`;

console.log(poema);

Esto es especialmente útil cuando construyes fragmentos de HTML desde JavaScript, algo que harás mucho más adelante.

Capacidad 2: interpolación

La interpolación permite insertar el resultado de cualquier expresión JavaScript dentro del texto, usando la sintaxis ${...}.

const nombre = "Luigi";
const apellido = "Mario";

const saludo = `Hola, ${nombre} ${apellido}`;
console.log(saludo);   // "Hola, Luigi Mario"

Dentro de ${} puede ir cualquier expresión válida, no solo un nombre de variable:

const precio = 12000;
const cantidad = 3;

console.log(`Total: ${precio * cantidad} pesos`);
console.log(`Con IVA: ${Math.round(precio * cantidad * 1.19)} pesos`);
console.log(`El nombre en mayúsculas es ${"luigi".toUpperCase()}`);
console.log(`Tiene ${cantidad > 1 ? "varios items" : "un item"}`);

La alternativa sin interpolación es la concatenación con el operador +: "Hola, " + nombre + " " + apellido produce el mismo resultado. Pero la concatenación se vuelve ilegible en cuanto hay más de dos o tres piezas, y es muy fácil olvidar un espacio dentro de las comillas. Compara:

const producto = "Teclado";
const stock = 5;
const bodega = "Santiago";

// Difícil de leer, fácil de romper
const a = "El producto " + producto + " tiene " + stock + " unidades en " + bodega + ".";

// Se lee como la frase final
const b = `El producto ${producto} tiene ${stock} unidades en ${bodega}.`;

console.log(a);
console.log(b);

La recomendación general: usa comillas invertidas por defecto. Sirven para todo, incluyendo los casos simples, y te evitan tener que reescribir el string cuando de pronto necesitas meter una variable.

Un cuidado con el operador +: cuando uno de los operandos es un string, JavaScript convierte el otro a string y concatena. Cuando ambos son números, suma. Eso produce resultados sorprendentes:

console.log(1 + 2);         // 3
console.log("1" + 2);       // "12"     <- concatenación, no suma
console.log(1 + 2 + "3");   // "33"     <- suma 1+2, luego concatena "3"
console.log("1" + 2 + 3);   // "123"    <- concatena todo de izquierda a derecha
console.log("5" - 2);       // 3        <- el operador - sí convierte a número

Ese comportamiento es una de las fuentes de bugs más frecuentes cuando lees valores desde un formulario, porque todo lo que viene de un campo de formulario es un string, incluso si el usuario escribió números. Se resuelve convirtiendo explícitamente:

const entrada = "42";              // simula lo que devuelve un input
const numero = Number(entrada);
console.log(numero + 8);           // 50
console.log(typeof numero);        // "number"

console.log(parseInt("42px", 10)); // 42   (ignora lo que no es número al final)
console.log(parseFloat("3.14"));   // 3.14
console.log(String(42));           // "42"

Secuencias de escape

Algunos caracteres no se pueden escribir directamente dentro de un string. Para incluirlos se usa la barra invertida:

console.log("Ella dijo \"hola\"");    // Ella dijo "hola"
console.log('No es lo mismo que \'esto\'');
console.log("Primera línea\nSegunda línea");
console.log("Columna1\tColumna2");
console.log("Una barra invertida: \\");
console.log(`Un dólar con llave literal: \${no es interpolación}`);
SecuenciaResultado
\nSalto de línea
\tTabulación
\\Barra invertida literal
\"Comilla doble literal
\'Comilla simple literal
\`Comilla invertida literal
\${Llave de interpolación literal (solo en plantillas)
áCarácter Unicode por código: á

La propiedad length

Para saber cuántos caracteres tiene un string se usa length. No es una función: no lleva paréntesis.

console.log("Luigi".length);          // 5

const frase = "Hola mundo";
console.log(frase.length);            // 10  (el espacio cuenta)

console.log("".length);               // 0   (string vacío)
console.log(" ".length);              // 1   (un espacio es un carácter)

Se puede aplicar directamente sobre un literal, sobre una variable, o sobre el resultado de cualquier expresión que devuelva un string:

const nombre = "Luigi";
const apellido = "Mario";

console.log(`${nombre} ${apellido}`.length);       // 11
console.log((nombre + apellido).length);           // 10
console.log(nombre.toUpperCase().length);          // 5

Casos de uso típicos: validar un largo mínimo, mostrar un contador de caracteres restantes o detectar un campo vacío.

const clave = "abc";
if (clave.length < 8) {
  console.log("La clave es demasiado corta");
}

const comentario = "Un comentario cualquiera del usuario";
console.log(`Escribiste ${comentario.length} de 280 caracteres`);
console.log(`Te quedan ${280 - comentario.length}`);

El detalle que casi nadie explica sobre length

length no cuenta exactamente “caracteres”. Cuenta unidades de código UTF-16, que es la manera interna en que JavaScript guarda el texto. Para el alfabeto latino, los números y la puntuación, una unidad equivale a un carácter y todo funciona como esperas. Pero hay excepciones:

console.log("año".length);      // 3   correcto
console.log("ñandú".length);    // 5   correcto

console.log("😀".length);        // 2   un solo emoji, pero length dice 2
console.log("👨‍👩‍👧".length);       // 8   una familia, ocho unidades
console.log("🇨🇱".length);        // 4   una bandera, cuatro unidades

La razón es que los emojis y muchos caracteres poco comunes ocupan dos unidades UTF-16, y algunos emojis compuestos son en realidad varios emojis unidos por un carácter invisible.

Si necesitas contar de verdad los caracteres visibles, una aproximación mucho mejor es convertir el string a array con el operador de propagación, que sí respeta los puntos de código:

console.log([..."😀"].length);       // 1
console.log([..."hola 😀"].length);  // 6
console.log("hola 😀".length);       // 7

Esto tampoco resuelve el caso de los emojis compuestos por varias piezas, pero cubre la mayoría de las situaciones reales. Es un detalle que importa mucho el día que hagas un contador de caracteres estilo red social y descubras que un emoji te resta dos.

Otras propiedades y métodos útiles de string

Aunque el tema central es length, vale la pena conocer las operaciones más comunes, porque las vas a necesitar de inmediato:

const texto = "  Super Club Net  ";
console.log(texto.trim());                   // "Super Club Net"   quita espacios de los extremos
console.log(texto.trim().toUpperCase());     // "SUPER CLUB NET"
console.log(texto.trim().toLowerCase());     // "super club net"
console.log(texto.trim().length);            // 14

const nombre = "Luigi Mario";
console.log(nombre[0]);                      // "L"    acceso por índice
console.log(nombre[nombre.length - 1]);      // "o"    último carácter
console.log(nombre.charAt(6));               // "M"
console.log(nombre.at(-1));                  // "o"    índice negativo cuenta desde el final

console.log(nombre.includes("Mario"));       // true
console.log(nombre.startsWith("Luigi"));     // true
console.log(nombre.endsWith("Mario"));       // true
console.log(nombre.indexOf("Mario"));        // 6      posición donde empieza
console.log(nombre.indexOf("Peach"));        // -1     no encontrado

console.log(nombre.slice(0, 5));             // "Luigi"
console.log(nombre.slice(6));                // "Mario"
console.log(nombre.slice(-5));               // "Mario"

console.log(nombre.replace("Luigi", "Mario"));   // "Mario Mario"  solo la primera
console.log("a-b-c".replaceAll("-", "+"));       // "a+b+c"        todas
console.log(nombre.split(" "));                  // ["Luigi", "Mario"]
console.log("ab".repeat(3));                     // "ababab"
console.log("5".padStart(3, "0"));               // "005"
console.log("5".padEnd(3, "0"));                 // "500"

Un punto conceptual importante: los strings son inmutables. Ninguno de estos métodos modifica el string original; todos devuelven uno nuevo.

let saludo = "hola";
saludo.toUpperCase();
console.log(saludo);              // "hola"   <- no cambió

saludo = saludo.toUpperCase();    // hay que reasignar
console.log(saludo);              // "HOLA"

Lo mismo aplica al acceso por índice:

let palabra = "casa";
palabra[0] = "m";
console.log(palabra);   // "casa"  <- la asignación se ignora en silencio

palabra = "m" + palabra.slice(1);
console.log(palabra);   // "masa"

Un programa completo que junta todo

Este ejemplo usa cada concepto del capítulo. Guárdalo como scripts/main.js y enlázalo desde la plantilla HTML de más arriba.

// ============================================
// Ficha de personaje - Super Club Net
// Demuestra: variables, tipos, strings, consola
// ============================================

// Constantes de configuración: valores fijos, conocidos de antemano
const NOMBRE_JUEGO = "Super Club Net";
const LIMITE_BIO = 140;

// Datos del personaje
const nombre = "Luigi";
const apellido = "Mario";
const edad = 33;
const esJugable = true;
const compañero = null;        // existe el campo, pero está vacío a propósito
let puntaje;                   // todavía no tiene valor: será undefined

console.group(`Ficha de personaje - ${NOMBRE_JUEGO}`);

// Interpolación y length
const nombreCompleto = `${nombre} ${apellido}`;
console.log("Nombre completo:", nombreCompleto);
console.log("Largo del nombre completo:", nombreCompleto.length);
console.log("Iniciales:", `${nombre[0]}.${apellido[0]}.`);

// Tipos de cada dato
console.log("typeof nombre:", typeof nombre);
console.log("typeof edad:", typeof edad);
console.log("typeof esJugable:", typeof esJugable);
console.log("typeof compañero:", typeof compañero, "(el bug histórico de null)");
console.log("typeof puntaje:", typeof puntaje);

// let sí se puede reasignar
puntaje = 0;
puntaje = puntaje + 150;
puntaje = puntaje + 75;
console.log("Puntaje final:", puntaje);

console.groupEnd();

// ============================================
// Biografía multilínea con plantilla de texto
// ============================================

const bio = `${nombreCompleto} tiene ${edad} años.
Es un personaje ${esJugable ? "jugable" : "no jugable"} de ${NOMBRE_JUEGO}.
Su puntaje acumulado es de ${puntaje} puntos.`;

console.group("Biografía");
console.log(bio);
console.log("Caracteres usados:", bio.length, "de", LIMITE_BIO);
console.log("Caracteres restantes:", LIMITE_BIO - bio.length);
console.log("Cabe en el límite:", bio.length <= LIMITE_BIO);
console.groupEnd();

// ============================================
// Manipulación de strings
// ============================================

const entradaSucia = "   LUIGI mario   ";
const limpio = entradaSucia.trim().toLowerCase();
const partes = limpio.split(" ");

console.group("Limpieza de entrada");
console.log("Original:", JSON.stringify(entradaSucia), "largo:", entradaSucia.length);
console.log("Sin espacios ni mayúsculas:", limpio, "largo:", limpio.length);
console.log("Partes:", partes);
console.groupEnd();

// ============================================
// Tabla comparativa en la consola
// ============================================

console.table([
  { dato: "nombre", valor: nombre, tipo: typeof nombre, largo: nombre.length },
  { dato: "edad", valor: edad, tipo: typeof edad, largo: String(edad).length },
  { dato: "esJugable", valor: esJugable, tipo: typeof esJugable, largo: String(esJugable).length }
]);

// ============================================
// const con contenido mutable, y length con emojis
// ============================================

const inventario = ["hongo", "flor de fuego"];
inventario.push("estrella");        // permitido: el array sigue siendo el mismo
console.log("Inventario:", inventario, "| items:", inventario.length);
// inventario = [];                 // esto lanzaría TypeError

const conEmoji = "Nivel superado 🎉";
console.log("length dice:", conEmoji.length, "| reales:", [...conEmoji].length);

Abre la consola y recorre la salida completa. Todo lo que ves ahí es material de este capítulo.

Errores comunes

ErrorQué pasaCausaSolución
El script no hace nadaLa consola está vacía y no hay erroresLa ruta del src está mal o el archivo no existeAbre la pestaña Network en DevTools y busca un 404 sobre el archivo .js; corrige la ruta relativa
Uncaught SyntaxError: Unexpected tokenEl navegador no puede leer el archivoFalta un paréntesis, una llave o una comilla de cierreRevisa la línea que indica el error y la anterior; los editores marcan los pares desbalanceados
El código dentro de <script src="..."> no correSe ignora en silencioUna etiqueta con src descarta su contenido internoUsa dos etiquetas <script> separadas
Assignment to constant variableEl programa se detieneSe intentó reasignar una constSi el valor debe cambiar, declárala con let; si no, revisa por qué estás reasignando
Missing initializer in const declarationError de sintaxis al cargarSe declaró una const sin valorAsigna el valor en la misma línea o usa let
Cannot access 'x' before initializationEl programa se detieneSe usó una variable let o const antes de su línea de declaraciónMueve la declaración arriba del primer uso
x is not definedEl programa se detieneLa variable nunca se declaró, o está fuera del ámbito, o hay un error de tipeo en el nombreRevisa la ortografía y en qué bloque de llaves declaraste la variable
Identifier 'x' has already been declaredError de sintaxisSe declaró dos veces la misma variable con let o constElimina la segunda declaración y deja solo la asignación
1 + 1 da "11"Concatena en vez de sumarUno de los valores es un string, típicamente venido de un formularioConvierte con Number() antes de operar
variable.length() da is not a functionEl programa se detienelength es una propiedad, no un métodoEscríbelo sin paréntesis: variable.length
toUpperCase() no cambia nadaEl string sigue igualLos strings son inmutables y el método devuelve uno nuevoReasigna: texto = texto.toUpperCase()
0.1 + 0.2 !== 0.3Una comparación falla sin razón aparentePrecisión de punto flotanteNo compares decimales por igualdad; trabaja con enteros o compara con una tolerancia
typeof null devuelve "object"Una comprobación de tipo fallaBug histórico del lenguaje que nunca se corrigióCompara directamente: valor === null
typeof [] devuelve "object"No distingue arrays de objetosLos arrays son objetos técnicamenteUsa Array.isArray(valor)
Una variable var “se escapa” de un ifValor inesperado más abajo en la funciónvar tiene ámbito de función, no de bloqueReemplaza var por let o const
El emoji cuenta como 2 en el contadorEl contador de caracteres da números raroslength cuenta unidades UTF-16Usa [...texto].length para contar caracteres visibles
document is not defined al usar el script en el <head>El script no encuentra elementosEl script se ejecutó antes de que existiera el HTMLAgrega defer a la etiqueta, o mueve el script antes de </body>

Ejercicios

1. Las tres formas de incluir un script. Crea tres archivos HTML idénticos. En el primero pon el script en línea dentro del <head>, en el segundo un archivo externo antes de </body>, y en el tercero un archivo externo con defer en el <head>. Los tres deben imprimir el mismo mensaje. Confirma que los tres funcionan y anota en qué orden ves los mensajes si abres los tres a la vez.

2. El script bloqueante. Escribe una página con un <h1> visible y, justo antes de él, un <script> en línea que ejecute un bucle vacío de varios millones de iteraciones. Recarga y observa cuánto demora en aparecer el título. Luego mueve el script al final del body y compara.

3. Explora la consola. Abre la consola de tu navegador y prueba, escribiendo directamente ahí: 2 ** 10, typeof typeof 1, "hola".length, [1,2,3].length, null == undefined, null === undefined. Anota qué devuelve cada uno y por qué crees que da ese resultado.

4. Tabla de tipos. Escribe un script que declare una variable de cada uno de los siete tipos primitivos más un objeto, un array y una función. Imprime los diez con console.table(), mostrando el valor y su typeof. Marca cuáles resultados te sorprenden.

5. Convierte de tipo. Declara const texto = "25" y const numero = 25. Imprime el resultado de texto + numero, texto - numero, texto * numero, Number(texto) + numero y texto + String(numero). Explica cada resultado en un comentario.

6. const con contenido mutable. Declara un array con const, agrégale tres elementos con push() e imprímelo. Luego intenta reasignar la variable a un array vacío. Anota el mensaje de error exacto y explica en un comentario por qué lo primero se permite y lo segundo no.

7. Ámbito de var contra let. Escribe una función que declare una variable con var dentro de un if y la imprima fuera del if. Luego escribe la misma función con let. Compara qué pasa en cada caso y anota el error exacto de la segunda.

8. Zona muerta temporal. Escribe un archivo que imprima una variable antes de declararla con var, y otro que haga lo mismo con let. Compara los dos resultados. Explica en un comentario por qué el segundo comportamiento es preferible.

9. Interpolación contra concatenación. Toma esta frase: “El pedido 4521 de Luigi Mario contiene 3 productos por un total de 45000 pesos.” Escríbela dos veces: una construida con concatenación y +, otra con una plantilla de texto. Guarda cada parte en su propia variable. Compara cuál te resultó más fácil de escribir sin errores.

10. Contador de caracteres. Crea una variable con un texto largo y un límite de 280 caracteres. Imprime en consola: el largo del texto, cuántos caracteres quedan disponibles, si cabe o no en el límite, y qué porcentaje del límite ocupa. Después agrega un emoji al texto y observa cómo cambia el conteo.

11. Normalizador de nombres. Escribe un script que tome la cadena " pedro PABLO ", le quite los espacios de los extremos, la pase a minúsculas, la separe en palabras y produzca "Pedro Pablo" con cada inicial en mayúscula. Imprime el largo del string en cada paso intermedio.

12. Inmutabilidad. Declara let palabra = "gato". Intenta cambiar la primera letra por una p usando palabra[0] = "p". Imprime el resultado. Luego consíguelo de verdad usando slice() y concatenación. Explica en un comentario qué pasó en el primer intento y por qué no hubo error.

13. Emojis y length. Crea un array de strings con: una palabra normal, una palabra con tildes, un emoji simple, una bandera y un emoji compuesto de familia. Para cada uno imprime texto.length y [...texto].length con console.table(). Documenta las diferencias.

14. Renombra el mal código. Toma este fragmento y reescríbelo aplicando todo lo del capítulo: nombres descriptivos, const donde corresponda, plantilla de texto y sin var.

var a = "Luigi";
var b = 33;
var c = a + " tiene " + b + " años";
console.log(c);
var a = "Mario";
console.log(a + " tiene " + b + " años");

15. Ficha propia. Adapta el programa completo del capítulo a un tema tuyo: una película, un libro, un equipo deportivo. Debe usar al menos cinco variables de tipos distintos, una plantilla multilínea, length en al menos dos lugares, un console.group() y un console.table().

Lo que viene

Ya tienes lo básico del lenguaje: sabes conectar un script a una página, reconoces los tipos de datos y sus rarezas, hablas con la consola, declaras variables sabiendo exactamente qué hace const, let y por qué var quedó atrás, y manejas cadenas de texto con interpolación y length. Con eso puedes escribir programas que calculan y muestran resultados.

Lo que todavía no puedes hacer es que el programa decida o repita. Todos los ejemplos de este capítulo se ejecutan de arriba hacia abajo, línea por línea, siempre igual. Un programa de verdad tiene que poder tomar caminos distintos según los datos, y tiene que poder trabajar con colecciones de valores sin escribir una línea por cada uno.

Eso es el capítulo 9: funciones para empaquetar y reutilizar bloques de código (declaraciones clásicas y funciones flecha, parámetros, valores de retorno), condicionales para bifurcar la ejecución (if, else, los operadores de comparación y la diferencia crítica entre == y ===), y arreglos para manejar listas de datos, con los métodos que los recorren y transforman. Con esas tres piezas más lo de este capítulo, ya vas a tener un lenguaje de programación completo en las manos.

Antes de seguir, haz los ejercicios en la consola. Es el único lugar donde el error aparece al instante y la corrección cuesta un Enter.