Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusDiferencias entre LocalStorage y SessionStorage
El almacenamiento web es una característica fundamental de HTML5 que permite a las aplicaciones web guardar datos directamente en el navegador del usuario. Existen dos mecanismos principales para esto: LocalStorage y SessionStorage, ambos parte de la API de Web Storage.
Aunque estos mecanismos comparten una interfaz común, presentan diferencias importantes en cuanto a su persistencia, alcance y casos de uso. Vamos a explorar estas diferencias para entender cuándo utilizar cada uno.
Persistencia de datos
La diferencia más significativa entre ambos mecanismos está en la duración del almacenamiento:
- LocalStorage mantiene los datos indefinidamente hasta que:
- El usuario los borra manualmente a través de las herramientas del navegador
- La aplicación web los elimina mediante código
- El usuario limpia la caché del navegador
<!-- Los datos guardados en LocalStorage permanecerán incluso después de cerrar el navegador -->
<button onclick="saveToLocalStorage()">Guardar preferencia</button>
<script>
function saveToLocalStorage() {
localStorage.setItem("theme", "dark");
alert("Preferencia guardada permanentemente");
}
</script>
- SessionStorage mantiene los datos temporalmente durante la sesión de navegación:
- Los datos persisten durante recargas de página y restauración de pestañas
- Los datos se eliminan automáticamente al cerrar la pestaña o ventana del navegador
<!-- Los datos en SessionStorage se perderán al cerrar la pestaña -->
<button onclick="saveToSessionStorage()">Guardar estado temporal</button>
<script>
function saveToSessionStorage() {
sessionStorage.setItem("formProgress", "step2");
alert("Estado guardado para esta sesión");
}
</script>
Alcance de almacenamiento
El contexto en el que los datos están disponibles también varía entre ambos mecanismos:
- LocalStorage tiene un alcance de origen (mismo protocolo, dominio y puerto):
- Los datos están disponibles para todas las pestañas y ventanas que comparten el mismo origen
- Permite la comunicación indirecta entre diferentes pestañas del mismo sitio web
- SessionStorage tiene un alcance de pestaña/ventana:
- Los datos están limitados a la pestaña o ventana donde se crearon
- Incluso otras pestañas del mismo sitio web no pueden acceder a estos datos
- Cada nueva pestaña crea su propio almacenamiento de sesión independiente
Visualización práctica de las diferencias
Esta tabla resume las principales diferencias entre ambos mecanismos:
| Característica | LocalStorage | SessionStorage | |----------------|--------------|----------------| | Persistencia | Permanente (sin fecha de expiración) | Temporal (dura solo la sesión) | | Disponibilidad | Todas las ventanas/pestañas del mismo origen | Solo en la ventana/pestaña donde se creó | | Capacidad | Aproximadamente 5-10 MB (varía según navegador) | Aproximadamente 5-10 MB (varía según navegador) | | Almacenamiento entre sesiones | Sí | No | | Envío al servidor | No (solo almacenamiento local) | No (solo almacenamiento local) |
Casos de uso recomendados
Cada tipo de almacenamiento es ideal para diferentes escenarios:
- LocalStorage es mejor para:
- Preferencias de usuario (tema oscuro/claro, configuración de idioma)
- Datos de inicio de sesión no sensibles (nombre de usuario para mostrar)
- Datos de aplicación que deben persistir entre sesiones (borradores, configuraciones)
- Caché de datos para mejorar el rendimiento
<!-- Ejemplo: Guardar preferencia de tema -->
<select id="themeSelector" onchange="saveTheme()">
<option value="light">Tema claro</option>
<option value="dark">Tema oscuro</option>
</select>
<script>
// Cargar preferencia guardada al iniciar
window.onload = function() {
const savedTheme = localStorage.getItem("userTheme");
if (savedTheme) {
document.getElementById("themeSelector").value = savedTheme;
applyTheme(savedTheme);
}
}
function saveTheme() {
const theme = document.getElementById("themeSelector").value;
localStorage.setItem("userTheme", theme);
applyTheme(theme);
}
function applyTheme(theme) {
// Código para aplicar el tema
document.body.className = theme;
}
</script>
- SessionStorage es mejor para:
- Datos temporales de formularios (progreso parcial)
- Historial de navegación dentro de la aplicación
- Datos de una sola sesión (carrito de compras temporal)
- Información sensible que no debe persistir después de cerrar la sesión
<!-- Ejemplo: Guardar progreso de formulario -->
<form id="multiStepForm">
<div id="step1">
<input type="text" id="name" placeholder="Nombre">
<button type="button" onclick="goToStep2()">Siguiente</button>
</div>
<div id="step2" style="display:none;">
<input type="email" id="email" placeholder="Email">
<button type="button" onclick="submitForm()">Enviar</button>
</div>
</form>
<script>
// Verificar si hay progreso guardado
window.onload = function() {
const currentStep = sessionStorage.getItem("formStep");
if (currentStep === "step2") {
// Recuperar datos guardados
document.getElementById("name").value = sessionStorage.getItem("userName") || "";
showStep2();
}
}
function goToStep2() {
// Guardar datos del paso 1
const name = document.getElementById("name").value;
sessionStorage.setItem("userName", name);
sessionStorage.setItem("formStep", "step2");
showStep2();
}
function showStep2() {
document.getElementById("step1").style.display = "none";
document.getElementById("step2").style.display = "block";
}
function submitForm() {
// Al enviar el formulario, podríamos limpiar el sessionStorage
sessionStorage.removeItem("formStep");
sessionStorage.removeItem("userName");
// Código para enviar el formulario
}
</script>
Comportamiento en navegación privada
Es importante considerar cómo estos mecanismos se comportan en modos de navegación privada:
- En modo de navegación privada o incógnito:
- LocalStorage puede comportarse como SessionStorage (datos eliminados al cerrar la ventana)
- Algunos navegadores pueden limitar o desactivar completamente el almacenamiento
- La capacidad de almacenamiento puede ser reducida
- Es recomendable implementar alternativas cuando el almacenamiento no está disponible:
<script>
// Verificar si el almacenamiento está disponible
function isStorageAvailable(type) {
try {
const storage = window[type];
const testKey = "__storage_test__";
storage.setItem(testKey, testKey);
storage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
}
// Usar almacenamiento solo si está disponible
if (isStorageAvailable("localStorage")) {
// Usar localStorage
} else {
// Proporcionar alternativa o notificar al usuario
console.log("El almacenamiento local no está disponible");
}
</script>
Entender estas diferencias te permitirá elegir el mecanismo de almacenamiento más adecuado para cada situación en tus aplicaciones web, mejorando tanto la experiencia del usuario como la funcionalidad de tu sitio.
Métodos para guardar, recuperar y eliminar datos
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La API de Web Storage proporciona una serie de métodos sencillos que permiten manipular los datos almacenados tanto en LocalStorage como en SessionStorage. Estos métodos comparten la misma sintaxis y funcionamiento en ambos mecanismos, lo que facilita su implementación.
Métodos principales
Tanto LocalStorage como SessionStorage utilizan los mismos cuatro métodos básicos para gestionar la información:
- setItem(): Guarda un valor asociado a una clave
- getItem(): Recupera un valor usando su clave
- removeItem(): Elimina un elemento específico
- clear(): Elimina todos los datos almacenados
Veamos cómo utilizar cada uno de estos métodos con ejemplos prácticos:
Guardar datos con setItem()
El método setItem() requiere dos parámetros: la clave (key) y el valor (value) que deseas almacenar.
<button onclick="guardarDato()">Guardar nombre</button>
<script>
function guardarDato() {
// Sintaxis: storage.setItem(clave, valor)
localStorage.setItem("username", "Ana");
// También funciona con sessionStorage
sessionStorage.setItem("lastVisited", "productos");
}
</script>
Es importante recordar que Web Storage solo almacena cadenas de texto. Si necesitas guardar objetos o arrays, debes convertirlos a formato JSON:
<script>
// Guardar un objeto
const userPreferences = {
theme: "dark",
fontSize: "large",
notifications: true
};
// Convertir a JSON antes de guardar
localStorage.setItem("preferences", JSON.stringify(userPreferences));
</script>
Recuperar datos con getItem()
Para recuperar información almacenada, utilizamos el método getItem() con la clave correspondiente:
<button onclick="mostrarNombre()">Mostrar nombre guardado</button>
<script>
function mostrarNombre() {
// Sintaxis: storage.getItem(clave)
const username = localStorage.getItem("username");
if (username) {
alert("Nombre guardado: " + username);
} else {
alert("No hay nombre guardado");
}
}
</script>
Cuando recuperamos objetos o arrays, necesitamos convertirlos de vuelta desde JSON:
<script>
// Recuperar y parsear un objeto JSON
const savedPreferences = localStorage.getItem("preferences");
if (savedPreferences) {
// Convertir de JSON a objeto JavaScript
const userPreferences = JSON.parse(savedPreferences);
// Ahora podemos acceder a las propiedades
console.log("Tema seleccionado:", userPreferences.theme);
console.log("Tamaño de fuente:", userPreferences.fontSize);
}
</script>
Eliminar datos específicos con removeItem()
Para eliminar un elemento individual utilizamos el método removeItem() con la clave del elemento que queremos borrar:
<button onclick="eliminarNombre()">Eliminar nombre</button>
<script>
function eliminarNombre() {
// Sintaxis: storage.removeItem(clave)
localStorage.removeItem("username");
alert("Nombre eliminado");
}
</script>
Este método es útil cuando necesitamos eliminar datos específicos sin afectar al resto de la información almacenada:
<script>
// Eliminar solo la preferencia de tema
localStorage.removeItem("theme");
// Eliminar un elemento del carrito de compras
sessionStorage.removeItem("cartItem_123");
</script>
Borrar todo el almacenamiento con clear()
Cuando necesitamos eliminar todos los datos almacenados de una vez, utilizamos el método clear():
<button onclick="borrarTodo()">Borrar todos los datos</button>
<script>
function borrarTodo() {
// Elimina todos los datos de localStorage
localStorage.clear();
// También podemos limpiar sessionStorage
// sessionStorage.clear();
alert("Todos los datos han sido eliminados");
}
</script>
Este método es especialmente útil para funciones como cerrar sesión o restablecer configuraciones:
<button onclick="cerrarSesion()">Cerrar sesión</button>
<script>
function cerrarSesion() {
// Eliminar todos los datos de la sesión
sessionStorage.clear();
// Eliminar solo datos de autenticación de localStorage
localStorage.removeItem("userToken");
localStorage.removeItem("username");
// Redirigir a la página de inicio
window.location.href = "index.html";
}
</script>
Verificar la existencia de datos
Podemos comprobar si existe un elemento específico antes de intentar utilizarlo:
<script>
// Verificar si existe un elemento
if (localStorage.getItem("username")) {
// El elemento existe, podemos usarlo
console.log("Usuario guardado:", localStorage.getItem("username"));
} else {
// El elemento no existe
console.log("No hay usuario guardado");
}
</script>
Acceso mediante notación de corchetes
Además de los métodos anteriores, podemos acceder al almacenamiento utilizando la notación de corchetes, similar a como accedemos a las propiedades de un objeto:
<script>
// Guardar datos
localStorage["lastLogin"] = "2023-05-15";
// Recuperar datos
const lastLogin = localStorage["lastLogin"];
console.log("Último acceso:", lastLogin);
// Eliminar datos
delete localStorage["lastLogin"];
</script>
Sin embargo, se recomienda utilizar los métodos estándar (setItem, getItem, removeItem) por su mayor claridad y consistencia.
Ejemplo práctico: Formulario con autoguardado
Veamos un ejemplo completo que utiliza estos métodos para crear un formulario con autoguardado:
<form id="contactForm">
<input type="text" id="name" placeholder="Nombre" oninput="autoSave()">
<input type="email" id="email" placeholder="Email" oninput="autoSave()">
<textarea id="message" placeholder="Mensaje" oninput="autoSave()"></textarea>
<button type="button" onclick="submitForm()">Enviar</button>
<button type="button" onclick="clearForm()">Limpiar</button>
</form>
<script>
// Cargar datos guardados al iniciar
window.onload = function() {
document.getElementById("name").value = localStorage.getItem("form_name") || "";
document.getElementById("email").value = localStorage.getItem("form_email") || "";
document.getElementById("message").value = localStorage.getItem("form_message") || "";
}
// Guardar automáticamente al escribir
function autoSave() {
localStorage.setItem("form_name", document.getElementById("name").value);
localStorage.setItem("form_email", document.getElementById("email").value);
localStorage.setItem("form_message", document.getElementById("message").value);
}
// Enviar formulario y limpiar almacenamiento
function submitForm() {
// Código para enviar el formulario
alert("Formulario enviado");
// Limpiar datos guardados
localStorage.removeItem("form_name");
localStorage.removeItem("form_email");
localStorage.removeItem("form_message");
}
// Limpiar formulario y almacenamiento
function clearForm() {
// Limpiar campos
document.getElementById("name").value = "";
document.getElementById("email").value = "";
document.getElementById("message").value = "";
// Limpiar almacenamiento
localStorage.removeItem("form_name");
localStorage.removeItem("form_email");
localStorage.removeItem("form_message");
}
</script>
Iterando sobre los datos almacenados
Si necesitamos recorrer todos los elementos almacenados, podemos utilizar la propiedad length y el método key():
<button onclick="mostrarTodosLosDatos()">Mostrar todos los datos</button>
<script>
function mostrarTodosLosDatos() {
let lista = "Datos guardados:\n";
// Recorrer todos los elementos en localStorage
for (let i = 0; i < localStorage.length; i++) {
// Obtener la clave en la posición i
const clave = localStorage.key(i);
// Obtener el valor asociado a esa clave
const valor = localStorage.getItem(clave);
lista += `- ${clave}: ${valor}\n`;
}
alert(lista);
}
</script>
Este método es útil para depuración o para crear funciones de exportación de datos.
Los métodos de Web Storage son simples pero potentes, permitiéndonos implementar funcionalidades como preferencias de usuario, autoguardado de formularios y estados de aplicación con pocas líneas de código y sin necesidad de configuraciones complejas.
Limitaciones y consideraciones de seguridad
Aunque el almacenamiento web ofrece una forma conveniente de guardar datos en el navegador, presenta importantes limitaciones técnicas y consideraciones de seguridad que debemos tener en cuenta al desarrollar aplicaciones web. Entender estas restricciones nos ayudará a implementar soluciones más robustas y seguras.
Limitaciones de capacidad
El espacio disponible para almacenar datos en LocalStorage y SessionStorage está restringido por el navegador:
- La mayoría de navegadores modernos asignan entre 5-10 MB por origen (dominio)
- Este límite es compartido entre LocalStorage y SessionStorage
- No existe un método estándar para solicitar más espacio
<script>
// Función para estimar el espacio utilizado en LocalStorage
function calcularEspacioUtilizado() {
let totalBytes = 0;
for (let i = 0; i < localStorage.length; i++) {
const clave = localStorage.key(i);
const valor = localStorage.getItem(clave);
// Cada carácter en JavaScript ocupa 2 bytes (UTF-16)
totalBytes += (clave.length + valor.length) * 2;
}
// Convertir a KB para mejor legibilidad
const kilobytes = totalBytes / 1024;
return kilobytes.toFixed(2) + " KB";
}
console.log("Espacio utilizado: " + calcularEspacioUtilizado());
</script>
Cuando se alcanza el límite, los intentos de almacenar más datos provocarán un error de cuota excedida:
<script>
try {
// Intentar guardar datos grandes
const datosGrandes = "x".repeat(10 * 1024 * 1024); // ~10MB de datos
localStorage.setItem("datosGrandes", datosGrandes);
} catch (e) {
console.error("Error al guardar: " + e.message);
alert("No se pudieron guardar los datos. El almacenamiento está lleno.");
}
</script>
Limitaciones de tipo de datos
Web Storage solo puede almacenar cadenas de texto (strings):
- Los valores no string se convierten automáticamente a string
- Los objetos complejos pierden su estructura al guardarse directamente
- Para objetos y arrays, es necesario usar JSON.stringify() y JSON.parse()
<script>
// Problema: almacenar directamente un número
localStorage.setItem("contador", 42);
const contador = localStorage.getItem("contador");
console.log(typeof contador); // "string", no "number"
// Problema: almacenar directamente un objeto
const usuario = { id: 1, nombre: "Carlos" };
localStorage.setItem("usuario", usuario);
console.log(localStorage.getItem("usuario")); // "[object Object]" (¡perdimos los datos!)
// Solución: usar JSON para objetos complejos
localStorage.setItem("usuarioJSON", JSON.stringify(usuario));
const usuarioRecuperado = JSON.parse(localStorage.getItem("usuarioJSON"));
console.log(usuarioRecuperado.nombre); // "Carlos" (datos preservados)
</script>
Limitaciones de sincronización
El almacenamiento web es sincrónico, lo que puede afectar al rendimiento:
- Las operaciones de lectura/escritura se ejecutan en el hilo principal
- Guardar o recuperar grandes cantidades de datos puede bloquear la interfaz
- No hay soporte nativo para operaciones asíncronas
<script>
// Función que podría bloquear la interfaz con datos grandes
function guardarDatosGrandes() {
const inicio = performance.now();
// Generar y guardar datos grandes
const datosGrandes = "x".repeat(1 * 1024 * 1024); // ~1MB
localStorage.setItem("datosGrandes", datosGrandes);
const tiempo = performance.now() - inicio;
console.log(`Operación completada en ${tiempo.toFixed(2)} ms`);
}
// Mejor práctica: usar setTimeout para operaciones grandes
function guardarDatosSinBloquear() {
setTimeout(() => {
guardarDatosGrandes();
}, 0);
}
</script>
Consideraciones de seguridad
Vulnerabilidad a ataques XSS
Los datos en Web Storage son accesibles mediante JavaScript, lo que los hace vulnerables a ataques de Cross-Site Scripting (XSS):
- Un script malicioso puede acceder a todos los datos almacenados en el mismo origen
- Nunca almacenes información sensible como contraseñas o tokens de autenticación
<script>
// INCORRECTO: almacenar datos sensibles
localStorage.setItem("password", "contraseña123"); // ¡NUNCA HAGAS ESTO!
// Mejor enfoque: almacenar solo identificadores o datos no sensibles
localStorage.setItem("userId", "user_12345");
localStorage.setItem("lastVisited", "products");
</script>
Falta de control de caducidad
A diferencia de las cookies, Web Storage no ofrece mecanismos nativos para:
- Establecer fechas de caducidad automáticas
- Configurar flags de seguridad como HttpOnly o Secure
Para implementar expiración, debemos crear nuestra propia solución:
<script>
// Función para guardar datos con fecha de expiración
function setItemWithExpiry(key, value, ttl) {
const now = new Date();
const item = {
value: value,
expiry: now.getTime() + ttl,
};
localStorage.setItem(key, JSON.stringify(item));
}
// Función para recuperar datos verificando expiración
function getItemWithExpiry(key) {
const itemStr = localStorage.getItem(key);
// Retornar null si no existe
if (!itemStr) {
return null;
}
const item = JSON.parse(itemStr);
const now = new Date();
// Comparar con la fecha actual
if (now.getTime() > item.expiry) {
// Si expiró, eliminar y retornar null
localStorage.removeItem(key);
return null;
}
return item.value;
}
// Ejemplo: guardar datos que expiran en 24 horas
setItemWithExpiry("sessionToken", "abc123", 24 * 60 * 60 * 1000);
// Más tarde, recuperar verificando expiración
const token = getItemWithExpiry("sessionToken");
if (token) {
console.log("Token válido:", token);
} else {
console.log("Token expirado o no existe");
}
</script>
Compartido entre pestañas y ventanas
Los datos en LocalStorage son compartidos entre todas las pestañas del mismo origen:
- Esto puede causar problemas de concurrencia si múltiples pestañas modifican los mismos datos
- No hay bloqueo de transacciones ni mecanismos de resolución de conflictos
<script>
// Problema potencial: múltiples pestañas modificando el mismo contador
let contador = parseInt(localStorage.getItem("contador") || "0");
contador++;
localStorage.setItem("contador", contador.toString());
// Solución: usar eventos de almacenamiento para sincronización
window.addEventListener("storage", (event) => {
if (event.key === "contador") {
console.log("El contador cambió en otra pestaña:", event.newValue);
// Actualizar la interfaz con el nuevo valor
}
});
</script>
Problemas de privacidad
El almacenamiento web puede utilizarse para rastrear usuarios entre sesiones:
- Funciona como una forma de "supercookie" persistente
- Los usuarios pueden desactivar o limpiar el almacenamiento web
- Algunos navegadores bloquean el almacenamiento en modo incógnito
<script>
// Función para verificar si el almacenamiento está disponible
function isStorageAvailable() {
try {
const testKey = "__storage_test__";
localStorage.setItem(testKey, testKey);
localStorage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
}
// Comprobar disponibilidad antes de usar
if (isStorageAvailable()) {
// El almacenamiento está disponible
localStorage.setItem("visita", "true");
} else {
// El almacenamiento no está disponible (modo incógnito o desactivado)
console.log("El almacenamiento web no está disponible");
// Implementar alternativa o mostrar mensaje al usuario
}
</script>
Mejores prácticas de seguridad
Para utilizar Web Storage de forma segura, sigue estas recomendaciones:
- Nunca almacenes información sensible (contraseñas, tokens de sesión, datos personales)
- Valida siempre los datos recuperados antes de utilizarlos
- Implementa expiración para datos temporales
- Minimiza la cantidad de datos almacenados
- Proporciona alternativas cuando el almacenamiento no esté disponible
<script>
// Validar datos antes de usarlos
function getValidatedUserPreferences() {
try {
const prefsStr = localStorage.getItem("userPreferences");
if (!prefsStr) {
return getDefaultPreferences();
}
const prefs = JSON.parse(prefsStr);
// Validar estructura y valores
if (!prefs || typeof prefs !== "object" || !prefs.theme) {
console.warn("Preferencias inválidas, usando valores predeterminados");
return getDefaultPreferences();
}
// Validar valores específicos
if (!["light", "dark", "system"].includes(prefs.theme)) {
prefs.theme = "system";
}
return prefs;
} catch (e) {
console.error("Error al recuperar preferencias:", e);
return getDefaultPreferences();
}
}
function getDefaultPreferences() {
return { theme: "system", fontSize: "medium" };
}
</script>
Alternativas para casos complejos
Cuando las limitaciones de Web Storage son problemáticas, considera estas alternativas:
- IndexedDB: Para almacenamiento estructurado de grandes cantidades de datos
- Cache API: Para almacenar respuestas HTTP y recursos
- Cookies: Para datos que necesitan enviarse al servidor
<script>
// Ejemplo básico de IndexedDB para datos más complejos
function iniciarBaseDatos() {
const request = indexedDB.open("MiAplicacion", 1);
request.onupgradeneeded = function(event) {
const db = event.target.result;
// Crear almacén de objetos
const objectStore = db.createObjectStore("usuarios", { keyPath: "id" });
// Definir índices
objectStore.createIndex("nombre", "nombre", { unique: false });
};
request.onsuccess = function(event) {
console.log("Base de datos IndexedDB inicializada correctamente");
};
request.onerror = function(event) {
console.error("Error al abrir la base de datos:", event.target.error);
};
}
</script>
Entender estas limitaciones y consideraciones de seguridad es fundamental para implementar soluciones de almacenamiento web que sean robustas, seguras y que proporcionen una buena experiencia de usuario. El almacenamiento web es una herramienta poderosa, pero debe utilizarse con conocimiento de sus restricciones.
Aprendizajes de esta lección de HTML
- Comprender las diferencias fundamentales entre LocalStorage y SessionStorage en cuanto a persistencia y alcance.
- Aprender a utilizar los métodos básicos para guardar, recuperar y eliminar datos en ambos mecanismos.
- Identificar casos de uso recomendados para cada tipo de almacenamiento.
- Conocer las limitaciones técnicas y de seguridad asociadas al almacenamiento web.
- Aplicar buenas prácticas para el uso seguro y eficiente de Web Storage en aplicaciones web.
Completa este curso de HTML y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs