FASE 1 · SESIÓN 3

JavaScript Esencial

El único lenguaje que corre en el navegador. Aprenderás desde variables hasta llamadas a APIs reales, con ejercicios que construyen un proyecto completo.

⏱ 3 horas ⚡ DOM + Eventos 🔌 Fetch API

AL TERMINAR ESTA SESIÓN VAS A SABER

Variables: dónde vive tu información

En JavaScript moderno (ES6+) solo usas const y let. El var es legacy y tiene comportamientos confusos. La regla simple: empieza con const, cambia a let solo si necesitas reasignar.

// CONST: valor que no cambia (usa esto por defecto)
const nombre = 'Ana García';
const edad = 28;
const activo = true;
const colores = ['rojo', 'verde', 'azul']; // array = const aunque se modifique
const usuario = { nombre: 'Ana', rol: 'admin' }; // objeto = const

// LET: valor que necesita reasignarse
let intentos = 0;
let mensaje = '';
let loading = false;

// REASIGNACIÓN
intentos = intentos + 1;   // ✓ OK, reasignación con let
nombre = 'Otra persona';   // ✗ ERROR: const no se puede reasignar
string "Hola" / 'texto' `template ${var}`

Texto. Las backticks permiten interpolación de variables.

number 42 / 3.14 / -7

Enteros y decimales son el mismo tipo.

boolean true / false

Solo dos valores posibles.

null null

Ausencia intencional de valor.

undefined undefined

Variable declarada sin asignar.

array [1, 'dos', true]

Lista ordenada, acceso por índice.

object { clave: valor }

Pares clave-valor, acceso por nombre.

function () => {}

También es un tipo de dato, asignable a variables.

Arrays — Operaciones esenciales

const frutas = ['manzana', 'pera', 'uva'];

// LEER
frutas[0]               // 'manzana' (índice empieza en 0)
frutas.length           // 3
frutas[frutas.length-1] // último elemento: 'uva'

// AÑADIR / QUITAR
frutas.push('kiwi');      // añade al final → ['manzana','pera','uva','kiwi']
frutas.pop();             // quita el último → devuelve 'kiwi'
frutas.unshift('limón');  // añade al inicio
frutas.shift();           // quita el primero

// TRANSFORMAR (devuelven NUEVO array, no mutan el original)
const mayusculas = frutas.map(f => f.toUpperCase());
const con_p      = frutas.filter(f => f.includes('p'));
const primero    = frutas.find(f => f.startsWith('m'));
const existe     = frutas.includes('pera'); // true/false
const indice     = frutas.indexOf('uva');   // 2

// REDUCIR a un valor
const total = [10, 20, 30].reduce((acc, n) => acc + n, 0); // 60

// ITERAR
frutas.forEach((fruta, i) => {
  console.log(i, fruta);
});

Objects — Acceso y desestructuración

const usuario = {
  nombre: 'Ana García',
  email: 'ana@example.com',
  edad: 28,
  direccion: {
    ciudad: 'Bogotá',
    pais: 'Colombia'
  }
};

// ACCESO
usuario.nombre           // 'Ana García'
usuario['email']         // 'ana@example.com' (útil con variables)
usuario.direccion.ciudad // 'Bogotá'

// MODIFICAR
usuario.telefono = '+57 300 000 0000'; // añadir propiedad
delete usuario.edad;                    // eliminar

// DESESTRUCTURACIÓN (ES6) — la forma moderna
const { nombre, email } = usuario;
const { ciudad } = usuario.direccion;

// Con alias
const { nombre: nombreCompleto } = usuario;

// En arrays
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];

// Iterar propiedades
Object.keys(usuario).forEach(clave => console.log(clave));
Object.entries(usuario).forEach(([clave, valor]) => console.log(clave, valor));

Funciones: el corazón de JavaScript

Todo en JavaScript gira alrededor de funciones. Hay tres formas de definirlas y cada una tiene su contexto de uso. Entender la diferencia es crucial.

Function Declaration

// Se puede llamar ANTES de su definición (hoisting)
calcular(5, 3); // ✓ funciona

function calcular(a, b) {
  return a + b;
}

Las declarations se "suben" al inicio del scope. Úsalas para funciones principales del módulo.

Function Expression

// Solo disponible DESPUÉS de su definición
// calcular(5, 3); // ✗ ERROR

const calcular = function(a, b) {
  return a + b;
};

calcular(5, 3); // ✓ funciona

Una función asignada a una variable. Más explícita sobre cuándo está disponible.

Arrow Function (moderna)

// Forma corta: una sola expresión
const calcular = (a, b) => a + b;

// Forma larga: bloque con return
const calcular = (a, b) => {
  const resultado = a + b;
  return resultado;
};

// Sin parámetros
const saludar = () => 'Hola!';

// Un parámetro: sin paréntesis
const doble = n => n * 2;

La forma más común en código moderno. Ideal para callbacks (map, filter, addEventListener).

Default params + Rest + Spread

// Parámetros por defecto
function saludar(nombre = 'amigo', saludo = 'Hola') {
  return `${saludo}, ${nombre}!`;
}
saludar();               // 'Hola, amigo!'
saludar('Ana', 'Hey');   // 'Hey, Ana!'

// Rest (...): recibe múltiples argumentos como array
function suma(...numeros) {
  return numeros.reduce((total, n) => total + n, 0);
}
suma(1, 2, 3, 4, 5); // 15

// Spread (...): expande un array o un objeto
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinado = [...arr1, ...arr2]; // [1,2,3,4,5,6]

const original = { nombre: 'Ana', rol: 'user' };
const admin    = { ...original, rol: 'admin' }; // override: rol = 'admin'

DOM: el árbol que puedes modificar

El DOM (Document Object Model) es la representación del HTML como un árbol de objetos JavaScript. Puedes seleccionar cualquier elemento y modificarlo en tiempo real, sin recargar la página.

Selección de elementos

// UN elemento (el primero que encuentre)
const titulo  = document.querySelector('h1');
const boton   = document.querySelector('#enviar');
const card    = document.querySelector('.card');
const input   = document.querySelector('input[type="email"]');

// MÚLTIPLES elementos (devuelve NodeList)
const cards = document.querySelectorAll('.card');
const links = document.querySelectorAll('nav a');

// Iterar sobre múltiples
cards.forEach(card => card.classList.add('visible'));

// También puedes convertirlo a array para usar map/filter
const cardsArr = Array.from(cards);

// Dentro de un elemento (más eficiente, solo busca dentro)
const nav      = document.querySelector('nav');
const linksNav = nav.querySelectorAll('a');

Leer y modificar contenido

const elemento = document.querySelector('.titulo');

// LEER
elemento.textContent            // texto plano (seguro)
elemento.innerHTML              // HTML interno (cuidado con XSS)
elemento.value                  // valor de inputs y textareas
elemento.getAttribute('href')   // cualquier atributo HTML

// ESCRIBIR
elemento.textContent = 'Nuevo título';
elemento.innerHTML   = '<strong>Negrita</strong> y texto';
elemento.value       = 'nuevo valor';
elemento.setAttribute('data-id', '42');

// CLASES
elemento.classList.add('activo');
elemento.classList.remove('inactivo');
elemento.classList.toggle('visible');
elemento.classList.contains('activo');        // true/false
elemento.classList.replace('viejo', 'nuevo');

// ESTILOS INLINE (usa clases cuando puedas, esto es para casos dinámicos)
elemento.style.display          = 'none';
elemento.style.backgroundColor  = '#f0e040';
elemento.style.cssText          = 'display:flex; gap:16px;';

Crear y eliminar elementos

// CREAR
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `
  <h3>Título</h3>
  <p>Descripción</p>
`;

// INSERTAR
document.body.appendChild(div);              // al final del body
container.prepend(div);                      // al inicio del container
lista.insertBefore(div, lista.children[2]);  // en posición específica

// insertAdjacentHTML: más control sobre dónde insertas
referencia.insertAdjacentHTML('afterend', '<p>Después del elemento</p>');
// posiciones: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'

// ELIMINAR
elemento.remove();
container.removeChild(hijo);

Tip DOM

innerHTML vs createElement

Para contenido estático o templates, innerHTML es conveniente pero nunca insertes datos del usuario sin sanitizar (riesgo de XSS). Para elementos dinámicos donde insertas datos del usuario, usa createElement y textContent que escapa el HTML automáticamente.

Eventos: responder al usuario

Los eventos son la forma en que JavaScript reacciona a lo que hace el usuario. Cada interacción (clic, tecla, scroll, submit) dispara un evento que puedes escuchar con addEventListener.

click

Clic del mouse sobre un elemento

Botones, cards, links
submit

Envío de formulario

Forms
input

Cambio en tiempo real de un input

Búsqueda en vivo
change

Cuando el valor cambia y el input pierde foco

Select, checkbox
keydown

Tecla presionada

Atajos de teclado
keyup

Tecla soltada

Validación al escribir
mouseover

Mouse entra al elemento

Tooltips, previews
mouseout

Mouse sale del elemento

Cerrar tooltips
focus

Elemento recibe foco

Highlight de inputs
blur

Elemento pierde foco

Validación al salir
scroll

El usuario hace scroll

Infinite scroll, header sticky
resize

La ventana cambia de tamaño

Layouts dinámicos
DOMContentLoaded

HTML cargado y parseado

Inicialización de scripts
load

Todos los recursos cargados

Imágenes, scripts externos

El objeto event y event delegation

document.querySelector('form').addEventListener('submit', (event) => {
  event.preventDefault(); // evita el comportamiento por defecto (recargar)

  // El objeto event contiene info del evento
  console.log(event.target);  // el elemento que disparó el evento
  console.log(event.type);    // 'submit'

  const datos = new FormData(event.target);
  const email = datos.get('email');
  const nombre = datos.get('nombre');
});

document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape')                cerrarModal();
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault(); // evita guardar el html del navegador
    guardarDraft();
  }
});

// EVENT DELEGATION: un solo listener para múltiples elementos
// Más eficiente que añadir uno a cada elemento.
// Funciona incluso con elementos añadidos dinámicamente.
document.querySelector('.lista').addEventListener('click', (e) => {
  // closest() sube por el DOM hasta encontrar el selector
  const item = e.target.closest('.item');
  if (item) {
    item.classList.toggle('completado');
    const id = item.dataset.id; // lee atributo data-id
  }
});

Fetch API: datos de servidores

Fetch permite hacer peticiones HTTP desde JavaScript. Es así como tu frontend se comunica con backends, obtiene datos de APIs externas y envía formularios sin recargar la página.

Concepto clave

Qué es asíncrono

JavaScript es single-threaded: ejecuta una cosa a la vez. Cuando pides datos a un servidor (puede tardar 2 segundos), no quieres bloquear toda la página esperando la respuesta.

async/await te permite "pausar" una función mientras espera una promesa (como fetch), sin bloquear el resto de la página. El código parece síncrono pero no lo es.

// FETCH BÁSICO
async function obtenerUsuario(id) {
  try {
    const response = await fetch(`https://api.ejemplo.com/usuarios/${id}`);

    // Siempre verifica el status — fetch no lanza error en 404/500
    if (!response.ok) {
      throw new Error(`Error ${response.status}: ${response.statusText}`);
    }

    const usuario = await response.json(); // parsea el JSON
    return usuario;

  } catch (error) {
    console.error('Error al obtener usuario:', error);
    throw error; // propaga el error para que el llamador lo maneje
  }
}

// LLAMAR CON ESTADO DE LOADING
async function cargarPerfil() {
  const loading  = document.querySelector('.loading');
  const contenido = document.querySelector('.perfil');

  loading.style.display  = 'block';
  contenido.style.opacity = '0';

  try {
    const usuario = await obtenerUsuario(1);
    contenido.querySelector('.nombre').textContent = usuario.name;
    contenido.querySelector('.email').textContent  = usuario.email;
    contenido.style.opacity = '1';
  } catch (error) {
    contenido.innerHTML = '<p class="error">Error al cargar. Intenta de nuevo.</p>';
  } finally {
    loading.style.display = 'none'; // se ejecuta SIEMPRE, con o sin error
  }
}

// POST: enviar datos al servidor
async function crearUsuario(datos) {
  const response = await fetch('https://api.ejemplo.com/usuarios', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(datos) // convierte el objeto a string JSON
  });

  if (!response.ok) throw new Error('Error al crear usuario');
  return response.json();
}

// MÚLTIPLES PETICIONES EN PARALELO
async function cargarDashboard() {
  const [usuario, posts, stats] = await Promise.all([
    fetch('/api/usuario').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/stats').then(r => r.json())
  ]);
  // los tres se piden al mismo tiempo, no uno tras otro
}

API PARA PRACTICAR

JSONPlaceholder — sin registro, sin auth

jsonplaceholder.typicode.com es una API REST gratuita con usuarios, posts, comentarios y todos. No necesita autenticación ni registro. Perfecta para practicar.

// Obtener 10 posts
const posts = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
  .then(r => r.json());

// Obtener un usuario
const user = await fetch('https://jsonplaceholder.typicode.com/users/1')
  .then(r => r.json());

// Crear un post (simulado, no persiste)
const nuevo = await fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Mi post', body: 'Contenido', userId: 1 })
}).then(r => r.json());

localStorage: persistir datos

localStorage guarda datos en el navegador del usuario. Sobrevive al cierre de la pestaña y al reinicio del navegador. Solo almacena texto — usa JSON.stringify/parse para objetos. Límite ~5MB.

// GUARDAR
localStorage.setItem('tema', 'dark');
localStorage.setItem('usuario', JSON.stringify({ nombre: 'Ana', rol: 'admin' }));

// LEER
const tema    = localStorage.getItem('tema');           // 'dark' o null si no existe
const usuario = JSON.parse(localStorage.getItem('usuario') || 'null');

// ELIMINAR
localStorage.removeItem('tema');
localStorage.clear(); // elimina TODO el localStorage de ese origen (cuidado)

// PATRÓN COMÚN: leer con fallback
const preferencias = JSON.parse(localStorage.getItem('preferencias')) || {
  tema: 'dark',
  idioma: 'es',
  notificaciones: true
};

// PATRÓN: CRUD de un array (notas, tareas, carrito)
function cargarNotas() {
  return JSON.parse(localStorage.getItem('notas')) || [];
}

function guardarNotas(notas) {
  localStorage.setItem('notas', JSON.stringify(notas));
}

function agregarNota(nota) {
  const notas = cargarNotas();
  notas.push({ id: Date.now(), ...nota });
  guardarNotas(notas);
}

function eliminarNota(id) {
  const notas = cargarNotas().filter(n => n.id !== id);
  guardarNotas(notas);
}

localStorage

  • Persiste indefinidamente
  • Compartida entre pestañas del mismo origen
  • Sobrevive al cierre del navegador
Úsalo para preferencias de usuario, tema, sesión de usuario, carrito de compras

sessionStorage

  • Se borra al cerrar la pestaña
  • Solo disponible en la pestaña actual
  • Misma API que localStorage
Úsalo para datos temporales de formulario, estado de un wizard multi-paso

Ejercicio: app de notas con localStorage

Construirás una app completa de notas que persiste datos en el navegador. Sin backend, sin librerías. Solo HTML + CSS + JavaScript puro.

2:00

App de notas — CRUD completo

Crear, leer, editar y borrar notas. Con persistencia en localStorage y filtro de búsqueda en tiempo real. Al terminar tendrás una app funcional que puedes mostrar en tu portafolio.

01

HTML y CSS base

Estructura: header con título y botón "Nueva nota", input de búsqueda, grid de cards de notas. CSS: grid auto-fit para las cards, dark theme, transiciones suaves.

25 min
02

Crear y mostrar notas

Función crearNota() que añade al array y guarda en localStorage. Función renderNotas() que vacía el grid y renderiza cada nota como card con título, texto y fecha.

30 min
03

Eliminar notas

Botón de borrar en cada card. Al clicar, elimina del array por id (usa Date.now() como id único), guarda y re-renderiza. Event delegation para no añadir un listener por card.

20 min
04

Búsqueda en tiempo real

Evento 'input' en el buscador. Filtra el array con .filter() antes de renderizar. Resalta el texto coincidente con un span de color diferente.

25 min
05

Pulido UX

Animación fadeIn al añadir notas. Confirmación al borrar. Contador de notas en el header. Estado vacío cuando no hay notas ni resultados de búsqueda.

20 min

Estructura sugerida

Cómo organizar el JS

Separa el estado (el array de notas) de la UI (el render). Nunca modifiques el DOM directamente al crear/borrar — siempre actualiza el array, guarda en localStorage y llama a renderNotas(). Este patrón es la base de React, Vue y cualquier framework moderno.

Recursos de esta sesión