LocalStorage con AI: repository pattern sin código basura

Clase 10 de 24Curso de Manipulación Avanzada de Datos con JavaScript

Resumen

Perder tus hábitos tras cada recarga frustra y dificulta el debug. Aquí verás cómo integrar persistencia con localStorage guiándote de la AI sin sacrificar buenas prácticas: usa back coding con criterio, aplica repository pattern y respeta Open-Closed para cambiar de fuente de datos sin tocar la lógica central.

¿Por qué la persistencia con localStorage mejora tu app de hábitos?

Agregar localStorage permite que los datos sobrevivan a recargas y hard reloads. No reemplaza una base de datos, pero es ideal para prototipos y para enfocarte en la UX mientras preparas un backend.

  • Mejora la experiencia: los hábitos y sus rachas permanecen entre sesiones.
  • Facilita el desarrollo: hace más simple debuggear al mantener el estado.
  • Es inmediata: no dependes aún de APIs ni servidores.

La validación es directa: crea hábitos, marca check-ins, recarga o abre en otra pestaña; la data debe persistir. Si ocurre, la integración es correcta y limpia.

¿Cómo guiar a la AI con prompts claros y buenas prácticas?

Un prompt vago como “Implementar persistencia” es insuficiente. Detalla la técnica, el archivo y el patrón deseado. Itera: trata la primera respuesta como borrador, revisa cambios y pide explicaciones si algo no cuadra. Aplica rollback cuando sea necesario.

  • Sé específico: “usar localStorage en app.js”.
  • Indica arquitectura: “seguir repository pattern y principio Open-Closed”.
  • Define reglas de identidad: “el ID se recibe en el constructor; si viene nulo, usar createID()”.
  • Solicita serialización clara: “objetos a plano con JSON y reconstrucción al leer”.

Cuando la AI propuso un options para el constructor, se evaluó y se prefirió pasar el ID como tercer parámetro. También se movió la sincronización al servicio de dominio, evitando llamadas imperativas en clics.

¿Qué ajustes de código son clave?

  • Serializar entidades antes de guardar: el navegador almacena datos planos.
  • Reconstruir objetos al leer: mapear a instancias preservando el ID.
  • Sincronizar en el servicio (no en manejadores de eventos): más limpio y confiable.
// Entidad Habit: preserva o genera ID class Habit { constructor(name, frequency, id) { this.id = id ?? createID(); this.name = name; this.frequency = frequency; // ...otras props. } toJSON() { return { id: this.id, name: this.name, frequency: this.frequency /* ... */ }; } }
  • Guarda en plano con JSON.stringify y lee con JSON.parse.
  • Usa map para serializar y deserializar de forma consistente.
// Utilidad de persistencia básica const KEY = 'habits'; const readAll = () => JSON.parse(localStorage.getItem(KEY) || '[]'); const writeAll = (items) => localStorage.setItem(KEY, JSON.stringify(items));

¿Cómo aplicar repository pattern y principio open-closed en JavaScript?

El repository pattern permite cambiar de fuente de datos (in memory, localStorage, IndexedDB o un API) sin tocar la lógica de negocio. Así se respeta Open-Closed: abierto a extensión, cerrado a modificación.

  • Define un contrato común para repositorios.
  • Implementa una variante para localStorage.
  • Conecta el servicio de hábitos al contrato, no a una implementación.

Algunas AI incluso propusieron una clase base (p. ej., HabitRepository) que otras extienden con extends, lo que facilita pruebas y reemplazos.

¿Ejemplo de repository para localStorage?

// Contrato base (opcional, útil para guiar a la AI) class HabitRepository { save(habit) { throw new Error('not implemented'); } remove(id) { throw new Error('not implemented'); } findById(id) { throw new Error('not implemented'); } findAll() { throw new Error('not implemented'); } } // Implementación con localStorage class LocalStorageHabitRepository extends HabitRepository { save(habit) { const list = readAll(); const idx = list.findIndex(h => h.id === habit.id); if (idx >= 0) list[idx] = habit.toJSON(); else list.push(habit.toJSON()); writeAll(list); } remove(id) { writeAll(readAll().filter(h => h.id !== id)); } findById(id) { const raw = readAll().find(h => h.id === id); return raw ? new Habit(raw.name, raw.frequency, raw.id) : null; } findAll() { return readAll().map(raw => new Habit(raw.name, raw.frequency, raw.id)); } }

Integra la sincronización dentro del servicio de dominio (p. ej., al hacer check-in o remove), no en clics individuales. Así el estado se guarda siempre que las entidades cambian y el código queda más limpio.

¿Qué habilidades y conceptos se refuerzan?

  • Redacción de prompts efectivos y específicos.
  • Criterio al usar back coding: nunca implementar lo que no se entiende.
  • Serialización con JSON y uso de map para transformar colecciones.
  • Gestión de IDs estables entre sesiones.
  • Iteración con rollback y revisión crítica de cambios.
  • Aplicación de repository pattern y Open-Closed.
  • Diseño orientado a entidades y servicios, evitando lógica imperativa dispersa.
  • Pruebas manuales en navegador con nuevas pestañas y recargas.
  • Uso comparado de Cursor, Gemini y ChatGPT con contexto adjunto.

¿Quieres comentar cómo estructurarías el contrato del repositorio o qué prompt te funcionó mejor con tu stack?