JS 15: LocalStorage y SessionStorage

Guarda datos en el navegador: persistencia sin servidor

1. ¿Qué es Web Storage?

Web Storage proporciona dos mecanismos para almacenar datos clave-valor en el navegador del usuario: localStorage y sessionStorage.

Diferencias Clave

Aspecto localStorage sessionStorage
Persistencia Permanente (hasta borrar) Mientras dure la pestaña
Alcance Todas las pestañas del mismo origen Solo la pestaña actual
Capacidad ~5-10 MB ~5-10 MB
Expira ? Nunca ? Al cerrar pestaña

Ventajas y Limitaciones

2. localStorage - Métodos Básicos

setItem() - Guardar Datos

// Guardar un valor

localStorage.setItem('nombre', 'Carlos');

localStorage.setItem('edad', '28');

localStorage.setItem('tema', 'oscuro');

getItem() - Obtener Datos

const nombre = localStorage.getItem('nombre');

console.log(nombre); // "Carlos"



const edad = localStorage.getItem('edad');

console.log(edad); // "28" (es string!)



// Si no existe, devuelve null

const ciudad = localStorage.getItem('ciudad');

console.log(ciudad); // null

removeItem() - Eliminar un Dato

localStorage.removeItem('edad');



const edad = localStorage.getItem('edad');

console.log(edad); // null (ya no existe)

clear() - Borrar Todo

// ?? Borra TODO el localStorage de este dominio

localStorage.clear();



console.log(localStorage.getItem('nombre')); // null

Atajo con Notación de Objeto

// También puedes usar notación de objeto (menos recomendado)

localStorage.nombre = 'Ana';

localStorage['edad'] = 25;



console.log(localStorage.nombre); // "Ana"



delete localStorage.edad;

3. Almacenar Objetos y Arrays

El storage solo acepta strings. Para objetos y arrays, usa JSON.stringify() y JSON.parse().

Guardar un Objeto

const usuario = {

  nombre: 'Laura',

  edad: 30,

  email: 'laura@example.com',

  preferencias: {

    tema: 'oscuro',

    idioma: 'es'

  }

};



// ? Convertir a JSON string antes de guardar

localStorage.setItem('usuario', JSON.stringify(usuario));

Recuperar el Objeto

const usuarioJSON = localStorage.getItem('usuario');



if (usuarioJSON) {

  const usuario = JSON.parse(usuarioJSON);

  console.log(usuario.nombre);  // "Laura"

  console.log(usuario.preferencias.tema); // "oscuro"

} else {

  console.log('No hay usuario guardado');

}

Guardar un Array

const tareas = [

  { id: 1, texto: 'Comprar leche', completada: false },

  { id: 2, texto: 'Estudiar JS', completada: true }

];



localStorage.setItem('tareas', JSON.stringify(tareas));



// Recuperar

const tareasJSON = localStorage.getItem('tareas');

if (tareasJSON) {

  const tareas = JSON.parse(tareasJSON);

  console.log(tareas.length); // 2

  console.log(tareas[0].texto); // "Comprar leche"

}

Helper Functions (Utilidades)

// Funciones helper para simplificar

const storage = {

  set(key, value) {

    localStorage.setItem(key, JSON.stringify(value));

  },

  

  get(key, defaultValue = null) {

    const item = localStorage.getItem(key);

    if (item === null) return defaultValue;

    

    try {

      return JSON.parse(item);

    } catch (error) {

      return item; // Si no es JSON, devolver como string

    }

  },

  

  remove(key) {

    localStorage.removeItem(key);

  },

  

  clear() {

    localStorage.clear();

  }

};



// Uso

storage.set('usuario', { nombre: 'Pedro', edad: 25 });

const usuario = storage.get('usuario');

console.log(usuario.nombre); // "Pedro"



const config = storage.get('config', { tema: 'claro' }); // default

console.log(config);

4. sessionStorage

Funciona exactamente igual que localStorage, pero los datos se borran al cerrar la pestaña/ventana.

Uso Idéntico

// Guardar

sessionStorage.setItem('tokenTemporal', 'abc123');

sessionStorage.setItem('paginaActual', JSON.stringify({ ruta: '/home' }));



// Obtener

const token = sessionStorage.getItem('tokenTemporal');

console.log(token); // "abc123"



// Eliminar

sessionStorage.removeItem('tokenTemporal');



// Borrar todo

sessionStorage.clear();

Cuándo Usar Cada Uno

Usa localStorage para: Usa sessionStorage para:
Preferencias de usuario Datos de formularios en progreso
Tema (claro/oscuro) Estado temporal de navegación
Idioma seleccionado Carrito de compras temporal
Tokens de auth (con cuidado) Paginación/filtros temporales
Favoritos, historial Wizard/paso a paso

5. Verificar Disponibilidad de Storage

Algunos navegadores en modo privado pueden bloquear storage:

function storageDisponible(tipo) {

  try {

    const storage = window[tipo];

    const test = '__storage_test__';

    storage.setItem(test, test);

    storage.removeItem(test);

    return true;

  } catch (e) {

    return false;

  }

}



if (storageDisponible('localStorage')) {

  console.log('localStorage disponible');

} else {

  console.log('localStorage NO disponible (modo privado?)');

}



if (storageDisponible('sessionStorage')) {

  console.log('sessionStorage disponible');

}

6. Escuchar Cambios (storage event)

Si cambias el localStorage en una pestaña, otras pestañas del mismo dominio pueden detectarlo:

// En pestaña A y B, escuchar cambios

window.addEventListener('storage', (e) => {

  console.log('Storage cambió!');

  console.log('Clave:', e.key);

  console.log('Valor antiguo:', e.oldValue);

  console.log('Valor nuevo:', e.newValue);

  console.log('URL:', e.url);

  

  // Ejemplo: actualizar UI si cambia el tema

  if (e.key === 'tema') {

    document.body.className = e.newValue; // aplicar nuevo tema

  }

});



// En pestaña B, cambiar el tema

localStorage.setItem('tema', 'oscuro');



// La pestaña A detectará el cambio automáticamente

3. IndexedDB (La Base de Datos)

Para aplicaciones que manejen muchos datos (megas o gigas) de forma asíncrona. No se suele explicar en cursos básicos por su complejidad.

🏆 Gran Comparativa de Almacenamiento

¿Cuándo usar cada cosa? Aquí tienes la guía definitiva:

Característica Storage (Local/Session) Cookies IndexedDB
Capacidad 5 - 10 MB ~4 KB (muy pequeña) Casi ilimitada
Sencillez Extrema (Clave: Valor) Compleja (Gestión de texto) Alta (Asíncrona/Eventos)
Envío al Servidor No Sí (en cada petición HTTP) No
Uso principal Preferencias y Estado UI Sesiones y Rastreo Offline Apps / Grandes datos
Seguridad Baja (Acceso por JS) Alta (con HttpOnly) Baja (Acceso por JS)

7. Ejemplo Real: Sistema de Preferencias

// Clase para manejar preferencias de usuario

class PreferenciasUsuario {

  constructor() {

    this.STORAGE_KEY = 'preferencias';

    this.defaults = {

      tema: 'claro',

      idioma: 'es',

      notificaciones: true,

      volumen: 50

    };

  }

  

  // Obtener todas las preferencias

  obtener() {

    const guardado = localStorage.getItem(this.STORAGE_KEY);

    if (!guardado) return { ...this.defaults };

    

    try {

      return { ...this.defaults, ...JSON.parse(guardado) };

    } catch (error) {

      console.error('Error al parsear preferencias');

      return { ...this.defaults };

    }

  }

  

  // Obtener una preferencia específica

  obtenerPref(clave) {

    const prefs = this.obtener();

    return prefs[clave];

  }

  

  // Guardar preferencias (merge con existentes)

  guardar(nuevasPrefs) {

    const prefsActuales = this.obtener();

    const prefsActualizadas = { ...prefsActuales, ...nuevasPrefs };

    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(prefsActualizadas));

    return prefsActualizadas;

  }

  

  // Restablecer a valores por defecto

  restablecer() {

    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.defaults));

    return this.defaults;

  }

  

  // Aplicar tema al DOM

  aplicarTema() {

    const tema = this.obtenerPref('tema');

    document.body.classList.toggle('tema-oscuro', tema === 'oscuro');

  }

}



// Uso

const prefs = new PreferenciasUsuario();



// Al cargar la página

document.addEventListener('DOMContentLoaded', () => {

  prefs.aplicarTema();

  

  const config = prefs.obtener();

  console.log('Configuración actual:', config);

});



// Cambiar tema

function cambiarTema(nuevoTema) {

  prefs.guardar({ tema: nuevoTema });

  prefs.aplicarTema();

}



// Botón de toggle

document.getElementById('btnTema')?.addEventListener('click', () => {

  const temaActual = prefs.obtenerPref('tema');

  const nuevoTema = temaActual === 'claro' ? 'oscuro' : 'claro';

  cambiarTema(nuevoTema);

});

8. Ejemplo Real: TODO List Persistente

class TodoList {

  constructor() {

    this.STORAGE_KEY = 'todos';

    this.todos = this.cargar();

  }

  

  cargar() {

    const data = localStorage.getItem(this.STORAGE_KEY);

    return data ? JSON.parse(data) : [];

  }

  

  guardar() {

    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos));

  }

  

  agregar(texto) {

    const todo = {

      id: Date.now(),

      texto,

      completada: false,

      fecha: new Date().toISOString()

    };

    this.todos.push(todo);

    this.guardar();

    return todo;

  }

  

  toggleCompletada(id) {

    const todo = this.todos.find(t => t.id === id);

    if (todo) {

      todo.completada = !todo.completada;

      this.guardar();

    }

  }

  

  eliminar(id) {

    this.todos = this.todos.filter(t => t.id !== id);

    this.guardar();

  }

  

  obtenerTodos() {

    return this.todos;

  }

  

  obtenerPendientes() {

    return this.todos.filter(t => !t.completada);

  }

  

  limpiarCompletadas() {

    this.todos = this.todos.filter(t => !t.completada);

    this.guardar();

  }

}



// Uso

const todoList = new TodoList();



// Agregar tarea

todoList.agregar('Estudiar JavaScript');

todoList.agregar('Hacer ejercicios');



// Obtener todas

console.log('Tareas:', todoList.obtenerTodos());



// Completar una tarea

const primeraId = todoList.obtenerTodos()[0].id;

todoList.toggleCompletada(primeraId);



// Ver pendientes

console.log('Pendientes:', todoList.obtenerPendientes());



// Los datos persisten al recargar la página!

9. Seguridad y Mejores Prácticas

?? NO Guardes Información Sensible

// ? NUNCA hagas esto

localStorage.setItem('contraseña', '12345');

localStorage.setItem('tarjetaCredito', '1234-5678-9012');

localStorage.setItem('tokenBancario', 'abc123');



// ? Storage es para datos NO sensibles

localStorage.setItem('tema', 'oscuro');

localStorage.setItem('idioma', 'es');

Validar Datos al Recuperar

function obtenerUsuarioSeguro() {

  const data = localStorage.getItem('usuario');

  if (!data) return null;

  

  try {

    const usuario = JSON.parse(data);

    

    // Validar estructura

    if (!usuario.nombre || !usuario.email) {

      console.warn('Datos de usuario inválidos');

      localStorage.removeItem('usuario');

      return null;

    }

    

    return usuario;

  } catch (error) {

    console.error('Error al parsear usuario:', error);

    localStorage.removeItem('usuario');

    return null;

  }

}

Manejar Cuotas Excedidas

function guardarSeguro(clave, valor) {

  try {

    localStorage.setItem(clave, JSON.stringify(valor));

    return true;

  } catch (e) {

    if (e.name === 'QuotaExceededError') {

      console.error('Storage lleno!');

      // Opción: limpiar datos antiguos

      localStorage.clear();

      // Reintentar

      try {

        localStorage.setItem(clave, JSON.stringify(valor));

        return true;

      } catch (e2) {

        return false;

      }

    }

    return false;

  }

}

Expiración Manual de Datos

// Guardar con timestamp de expiración

function guardarConExpiracion(clave, valor, diasExpiracion) {

  const ahora = new Date();

  const item = {

    valor: valor,

    expira: ahora.getTime() + (diasExpiracion * 24 * 60 * 60 * 1000)

  };

  localStorage.setItem(clave, JSON.stringify(item));

}



// Recuperar verificando expiración

function obtenerSiNoExpirado(clave) {

  const itemJSON = localStorage.getItem(clave);

  if (!itemJSON) return null;

  

  const item = JSON.parse(itemJSON);

  const ahora = new Date();

  

  if (ahora.getTime() > item.expira) {

    // Expirado, eliminar

    localStorage.removeItem(clave);

    return null;

  }

  

  return item.valor;

}



// Uso

guardarConExpiracion('tokenTemporal', 'abc123', 7); // Expira en 7 días

const token = obtenerSiNoExpirado('tokenTemporal');

10. Ejercicios Prácticos

Ejercicio 1: Contador de Visitas

Crea un contador que incremente cada vez que se carga la página:

// 1. Obtener contador actual (o 0 si no existe)

// 2. Incrementar en 1

// 3. Guardar nuevo valor

// 4. Mostrar en consola: "Has visitado esta página X veces"

Ejercicio 2: Carrito de Compras

Implementa funciones básicas de carrito:

// agregarAlCarrito(producto)

// obtenerCarrito()

// eliminarDelCarrito(id)

// calcularTotal()

// Los productos deben persistir al recargar

Ejercicio 3: Formulario con Auto-guardado

Guarda el contenido de un input automáticamente:

// 1. Escuchar evento 'input' en un campo de texto

// 2. Guardar el valor en localStorage cada vez que cambie

// 3. Al cargar, restaurar el valor guardado

Resumen

Volver a JS