Prometimos que el alivio llegaría en la primera página, así que empecemos por ahí. Durante todo el Módulo 3 has arrastrado seis arrays paralelos —titulos[i], responsables[i], prioridades[i], estados[i], horas[i], fechasLimite[i]— y una función, describirTarea, que necesitaba ocho argumentos para pintar una sola línea. Cada vez que querías filtrar, filtrabas índices en lugar de tareas. Cada vez que añadías un campo al modelo, tocabas todas las funciones. En esta lección eso se acaba: aprenderás qué es un objeto en JavaScript, cómo se crea, cómo se leen y modifican sus propiedades, y usarás ese conocimiento para escribir por fin el backlog real de Nómada Tareas como un array de objetos, con la firma limpia describirTarea(tarea, hoy). Es el cambio más importante del curso hasta ahora, porque a partir de aquí el código deja de simular el modelo de datos y pasa a contenerlo.
Contenido
- El problema: seis arrays que hay que mantener alineados
- Qué es un objeto: literales, propiedades y valores
- Acceso con punto y acceso con corchetes
- Añadir, modificar y borrar propiedades
- Propiedades que no existen:
undefinedy el encadenamiento opcional - Comprobar si una propiedad existe
- El backlog real: un array de objetos
- La recompensa:
describirTarea(tarea, hoy) - Objetos anidados: la tarea con sus subtareas
- Nombres de propiedad computados
- Recorrer un objeto:
for...inyObject.keys/values/entries - Objetos como diccionario de consulta
- Una mención breve a
MapySet - Errores Comunes y Consejos
- Ejercicios
- Conclusión
- El problema: seis arrays que hay que mantener alineados
Antes de celebrar la solución, mira el problema con toda su crudeza. Así estaba el backlog al final del Módulo 3:
const titulos = ['Rediseñar la sala polivalente', 'Cartelería del taller de serigrafía', /* ... */];
const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const prioridades = ['alta', 'media', 'alta', 'baja', 'media', 'alta'];
const estados = ['en-curso', 'pendiente', 'pendiente', 'hecha', 'en-curso', 'pendiente'];
const horas = [12, 6, 14, 3, 8, 5];
const fechasLimite = ['2026-09-30', '2026-10-15', '2026-10-02', '2026-09-12', '2026-11-05', '2026-09-05'];Este diseño tiene tres defectos graves, y conviene nombrarlos:
| Defecto | Qué pasa en la práctica |
|---|---|
| Fragilidad | Si borras la tarea 3 de titulos y olvidas borrarla de horas, a partir de ese índice todo el backlog está desalineado y el programa no da ningún error: simplemente miente |
| Firmas enormes | describirTarea(id, titulo, responsable, prioridad, estado, horasEstimadas, fechaLimite, hoy) — ocho parámetros posicionales que hay que recordar en orden |
| No hay "la tarea" | No existe ningún valor en el programa que sea una tarea. Solo existe el número 3, y el acuerdo tácito de que el índice 3 significa lo mismo en seis sitios |
Un objeto resuelve los tres de una vez: agrupa datos relacionados bajo un mismo valor, con nombres en lugar de posiciones.
- Qué es un objeto: literales, propiedades y valores
Un objeto es una colección de pares clave → valor. La forma de crearlo es el literal de objeto: llaves {} con las parejas dentro.
const tarea = {
id: 1,
titulo: 'Rediseñar la sala polivalente',
responsable: 'Iván',
prioridad: 'alta',
estado: 'en-curso',
etiquetas: ['espacio', 'diseño'],
horasEstimadas: 12,
fechaLimite: '2026-09-30',
revisor: 'Marta'
};Vocabulario preciso, porque lo usarás en todo el módulo:
- Propiedad: cada pareja completa, por ejemplo
titulo: 'Rediseñar la sala polivalente'. - Clave (o nombre de propiedad): la parte izquierda,
titulo. Siempre es un string internamente (o unSymbol, que no veremos aquí). Aunque escribas{ 1: 'uno' }, la clave real es'1'. - Valor: la parte derecha. Puede ser cualquier cosa: un número, un string,
null, un array (etiquetas), otro objeto, o incluso una función (eso son los métodos, y llegan en la lección siguiente).
Tres detalles de sintaxis que ahorran disgustos:
const ejemplo = {
titulo: 'Sin comillas si es un identificador válido',
'fecha-limite': 'Con comillas si lleva guiones, espacios o empieza por dígito',
estado: 'pendiente', // la coma final (trailing comma) está permitida
};Las claves no llevan comillas si son identificadores válidos de JavaScript (letras, dígitos que no van al principio, _ y $). Si la clave tiene un guion, un espacio o un acento problemático, hay que entrecomillarla. En Nómada Tareas usamos siempre claves en camelCase sin comillas: horasEstimadas, fechaLimite.
Y recuerda lo que ya sabes de 01-05: typeof de un objeto es 'object', y los objetos son valores de referencia. Dos objetos con el mismo contenido no son iguales:
console.log(typeof tarea); // 'object'
console.log({ id: 1 } === { id: 1 }); // false ← son dos objetos distintos
- Acceso con punto y acceso con corchetes
Hay dos formas de leer una propiedad, y no son intercambiables.
// Notación de punto: la clave se escribe literalmente en el código
console.log(tarea.titulo); // 'Rediseñar la sala polivalente'
console.log(tarea.horasEstimadas); // 12
// Notación de corchetes: la clave es una EXPRESIÓN que se evalúa a un string
console.log(tarea['titulo']); // 'Rediseñar la sala polivalente'
const campo = 'responsable';
console.log(tarea[campo]); // 'Iván' ← imposible con el punto
console.log(tarea.campo); // undefined ← busca la clave literal "campo"Esa última línea es el error clásico: tarea.campo busca una propiedad llamada campo, no el contenido de la variable campo. Cuándo hace falta cada notación:
| Situación | Notación | Ejemplo |
|---|---|---|
| Sabes el nombre al escribir el código | Punto (preferida: más corta y legible) | tarea.estado |
| El nombre está en una variable | Corchetes | tarea[campo] |
| El nombre se calcula | Corchetes | tarea['horas' + 'Estimadas'] |
| El nombre no es un identificador válido | Corchetes | config['modo-oscuro'] |
| El nombre es un número | Corchetes | pesos[3] |
Un caso práctico donde los corchetes son imprescindibles: una función genérica que ordena o compara por el campo que le digas.
function valorDelCampo(tarea, campo) {
return tarea[campo]; // con punto sería siempre la propiedad "campo"
}
console.log(valorDelCampo(tarea, 'prioridad')); // 'alta'
console.log(valorDelCampo(tarea, 'revisor')); // 'Marta'
- Añadir, modificar y borrar propiedades
Los objetos declarados con const sí se pueden modificar. Lo que const impide es reasignar la variable, no cambiar el contenido del objeto al que apunta (esto lo viste en 01-05 con primitivos frente a referencias).
const tarea = { id: 1, titulo: 'Rediseñar la sala polivalente', estado: 'pendiente' };
// Modificar: la clave ya existe
tarea.estado = 'en-curso';
// Añadir: la clave no existía, se crea sobre la marcha
tarea.responsable = 'Iván';
tarea['horasEstimadas'] = 12;
console.log(tarea);
// { id: 1, titulo: 'Rediseñar la sala polivalente', estado: 'en-curso',
// responsable: 'Iván', horasEstimadas: 12 }
// Borrar: el operador delete elimina la propiedad entera
delete tarea.horasEstimadas;
console.log(tarea.horasEstimadas); // undefined
// Reasignar la variable SÍ falla
// tarea = { id: 2 }; // ✗ TypeError: Assignment to constant variableSobre delete, dos matices importantes:
deleteno es lo mismo que ponerundefined. Trasdelete tarea.revisor, la claverevisorya no existe en el objeto. Trastarea.revisor = undefined, la clave existe y su valor esundefined. Leyendo con el punto no notas la diferencia, pero'revisor' in tareasí (apartado 6), y también la notanObject.keysyJSON.stringify(lección 04-08).- En Nómada Tareas casi nunca usamos
delete. El modelo dice querevisores «string onull»: un revisor ausente se representa connull, que es un valor explícito, no borrando la clave. Borrar campos del modelo canónico rompe las funciones que esperan encontrarlos.
- Propiedades que no existen:
undefined y el encadenamiento opcional
undefined y el encadenamiento opcionalLeer una propiedad inexistente no da error: devuelve undefined.
const tarea = { id: 1, titulo: 'Rediseñar la sala polivalente' };
console.log(tarea.responsable); // undefined ← no existe, pero no falla
console.log(tarea.titluo); // undefined ← ¡una errata también devuelve undefined!Esa segunda línea explica por qué las erratas en nombres de propiedad son tan traicioneras: JavaScript no te avisa, simplemente te da undefined y el fallo aparece tres funciones más allá. (En 08-02 verás cómo un linter y las anotaciones de tipo detectan esto antes de ejecutar.)
Lo que sí revienta es intentar leer una propiedad de undefined:
const tarea = { id: 1, titulo: 'Rediseñar la sala polivalente' };
console.log(tarea.revision.autor);
// ✗ TypeError: Cannot read properties of undefined (reading 'autor')Aquí es donde vuelve el encadenamiento opcional ?. que conociste en 01-06. Si lo que hay a la izquierda es null o undefined, la expresión entera se detiene y devuelve undefined en lugar de lanzar el error:
console.log(tarea.revision?.autor); // undefined ← sin TypeError
console.log(tarea.revision?.autor ?? 'sin revisar'); // 'sin revisar'Combinado con ?? (fusión de nulos), tienes la pareja perfecta para leer datos que puede que no estén: ?. evita el error y ?? pone el valor por defecto. Un ejemplo directo del proyecto, donde revisor puede ser null:
function nombreDelRevisor(tarea) {
return tarea.revisor ?? 'pendiente de asignar';
}
console.log(nombreDelRevisor({ revisor: 'Marta' })); // 'Marta'
console.log(nombreDelRevisor({ revisor: null })); // 'pendiente de asignar'Cuidado con abusar de
?.. Si escribestarea?.titulo?.lengthen todas partes, estás tapando el hecho de que no sabes qué datos recibes. Úsalo donde el dato es legítimamente opcional (revisor, un campo que aún no existe), no como red de seguridad universal.
- Comprobar si una propiedad existe
Hay cuatro maneras de preguntar «¿tiene este objeto esta propiedad?», y no responden exactamente a la misma pregunta.
| Forma | Devuelve true cuando… |
Con { revisor: undefined } |
Con propiedades heredadas |
|---|---|---|---|
tarea.revisor !== undefined |
El valor no es undefined |
false |
Sí las ve |
'revisor' in tarea |
La clave existe, con el valor que sea | true |
Sí las ve |
tarea.hasOwnProperty('revisor') |
La clave es propia del objeto | true |
No |
Object.hasOwn(tarea, 'revisor') |
La clave es propia (versión moderna, recomendada) | true |
No |
const tarea = { id: 1, titulo: 'Presupuesto de la carpintería', revisor: undefined };
console.log(tarea.revisor !== undefined); // false ← el valor es undefined
console.log('revisor' in tarea); // true ← pero la clave sí está
console.log(Object.hasOwn(tarea, 'revisor')); // true
console.log(Object.hasOwn(tarea, 'prioridad')); // false¿Cuál usar? La regla práctica:
Object.hasOwn(obj, clave)cuando quieres saber si la clave está presente. Es la forma moderna y la que deberías escribir por defecto.obj.clave !== undefined(o directamenteif (obj.clave), con cuidado con los falsy de 01-07) cuando lo que te importa es el valor.infunciona, pero también encuentra propiedades heredadas del prototipo (Módulo 5), lo que a veces sorprende.hasOwnPropertyes el veterano;Object.hasOwnexiste precisamente para sustituirlo con menos trampas.
Ejemplo real: validar que un objeto recibido tiene todos los campos obligatorios del modelo.
const CAMPOS_OBLIGATORIOS = ['id', 'titulo', 'responsable', 'prioridad',
'estado', 'etiquetas', 'horasEstimadas', 'fechaLimite'];
function camposQueFaltan(tarea) {
const faltan = [];
for (const campo of CAMPOS_OBLIGATORIOS) {
if (!Object.hasOwn(tarea, campo)) faltan.push(campo);
}
return faltan;
}
console.log(camposQueFaltan({ id: 7, titulo: 'Revisar la guillotina' }));
// [ 'responsable', 'prioridad', 'estado', 'etiquetas', 'horasEstimadas', 'fechaLimite' ]Fíjate en que revisor no está en la lista: es opcional, y su ausencia se representa con null.
- El backlog real: un array de objetos
Ha llegado el momento. Este bloque de código es la fuente de verdad del resto del módulo; lo vas a ver reaparecer en las siete lecciones siguientes.
'use strict';
const HOY = '2026-09-20';
const backlog = [
{
id: 1,
titulo: 'Rediseñar la sala polivalente',
responsable: 'Iván',
prioridad: 'alta',
estado: 'en-curso',
etiquetas: ['espacio', 'diseño'],
horasEstimadas: 12,
fechaLimite: '2026-09-30',
revisor: 'Marta'
},
{
id: 2,
titulo: 'Cartelería del taller de serigrafía',
responsable: 'Marta',
prioridad: 'media',
estado: 'pendiente',
etiquetas: ['serigrafía', 'comunicación'],
horasEstimadas: 6,
fechaLimite: '2026-10-15',
revisor: null
},
{
id: 3,
titulo: 'Actualizar la web de reservas',
responsable: 'Lucía',
prioridad: 'alta',
estado: 'pendiente',
etiquetas: ['web', 'reservas'],
horasEstimadas: 14,
fechaLimite: '2026-10-02',
revisor: 'Iván'
},
{
id: 4,
titulo: 'Inventario de tintas de serigrafía',
responsable: 'Marta',
prioridad: 'baja',
estado: 'hecha',
etiquetas: ['serigrafía', 'almacén'],
horasEstimadas: 3,
fechaLimite: '2026-09-12',
revisor: null
},
{
id: 5,
titulo: 'Guía de encuadernación para residentes',
responsable: 'Iván',
prioridad: 'media',
estado: 'en-curso',
etiquetas: ['encuadernación', 'documentación'],
horasEstimadas: 8,
fechaLimite: '2026-11-05',
revisor: 'Lucía'
},
{
id: 6,
titulo: 'Presupuesto de la carpintería',
responsable: 'Iván',
prioridad: 'alta',
estado: 'pendiente',
etiquetas: ['carpintería', 'compras'],
horasEstimadas: 5,
fechaLimite: '2026-09-05',
revisor: 'Marta'
}
];Los datos son exactamente los mismos de siempre —48 h totales, 45 h abiertas, Iván con tres tareas y 25 h, «Presupuesto de la carpintería» vencida— pero ahora cada tarea es una unidad. Compruébalo:
console.log(backlog.length); // 6
console.log(backlog[0].titulo); // 'Rediseñar la sala polivalente'
console.log(backlog[5].fechaLimite); // '2026-09-05'
console.log(backlog[2].etiquetas[0]); // 'web'Lee despacio backlog[2].etiquetas[0]: del array backlog coge el elemento en la posición 2 (un objeto), de ese objeto lee la propiedad etiquetas (un array) y de ese array coge el elemento 0 (un string). El acceso se encadena de izquierda a derecha, paso a paso.
flowchart TD
A["backlog<br/>(array de 6 elementos)"] --> B0["[0] objeto tarea"]
A --> B1["[1] objeto tarea"]
A --> B2["[2] objeto tarea"]
A --> B5["[5] objeto tarea"]
B2 --> C1["id: 3"]
B2 --> C2["titulo: 'Actualizar la web de reservas'"]
B2 --> C3["etiquetas: ['web', 'reservas']"]
B2 --> C4["revisor: 'Iván'"]
Y ahora la comprobación que importa: borrar una tarea ya no puede desalinear nada.
// Con arrays paralelos había que borrar el índice 3 en SEIS arrays.
// Ahora la tarea entera desaparece de un golpe, y nada se descoloca.
- La recompensa:
describirTarea(tarea, hoy)
describirTarea(tarea, hoy)Recuperemos las funciones del Módulo 3 y reescribámoslas con el modelo real.
function pesoDePrioridad(prioridad) {
if (prioridad === 'alta') return 3;
if (prioridad === 'media') return 2;
if (prioridad === 'baja') return 1;
return 0;
}
// R10: vencida = fecha límite pasada y la tarea no está terminada.
function estaVencida(tarea, hoy) {
return tarea.fechaLimite < hoy && tarea.estado !== 'hecha';
}
function marcaDeEstado(estado) {
if (estado === 'hecha') return '✓';
if (estado === 'en-curso') return '▸';
return '○';
}
function describirTarea(tarea, hoy) {
const marca = marcaDeEstado(tarea.estado);
const aviso = estaVencida(tarea, hoy) ? ' ⚠ VENCIDA' : '';
const revision = tarea.revisor ?? 'sin revisor';
return `${marca} [${tarea.id}] ${tarea.titulo} · ${tarea.responsable} · ` +
`${tarea.prioridad} · ${tarea.horasEstimadas} h · rev: ${revision}${aviso}`;
}
console.log(describirTarea(backlog[0], HOY));
// ▸ [1] Rediseñar la sala polivalente · Iván · alta · 12 h · rev: Marta
console.log(describirTarea(backlog[5], HOY));
// ○ [6] Presupuesto de la carpintería · Iván · alta · 5 h · rev: Marta ⚠ VENCIDA
console.log(describirTarea(backlog[3], HOY));
// ✓ [4] Inventario de tintas de serigrafía · Marta · baja · 3 h · sin revisorCompara las dos firmas de describirTarea:
| Módulo 3 | Ahora | |
|---|---|---|
| Firma | describirTarea(id, titulo, responsable, prioridad, estado, horasEstimadas, fechaLimite, hoy) |
describirTarea(tarea, hoy) |
| Llamada | describirTarea(ids[i], titulos[i], responsables[i], prioridades[i], estados[i], horas[i], fechasLimite[i], HOY) |
describirTarea(backlog[i], HOY) |
Añadir el campo revisor |
Cambiar la firma y todas las llamadas | No cambia nada: el campo ya viaja dentro |
| Riesgo de equivocar el orden | Alto: siete strings y números seguidos | Ninguno |
Este es el beneficio real de los objetos: los datos que van juntos viajan juntos. Y fíjate en el efecto colateral en estaVencida(tarea, hoy): pasó de tres parámetros a dos, y ya no puede recibir la fecha de una tarea con el estado de otra.
- Objetos anidados: la tarea con sus subtareas
En 03-07 usaste, a regañadientes, un objeto con subtareas anidadas. Ahora ya puedes leerlo con propiedad:
const tareaRediseno = {
id: 1,
titulo: 'Rediseñar la sala polivalente',
responsable: 'Iván',
estado: 'en-curso',
horasEstimadas: 0,
subtareas: [
{ id: 11, titulo: 'Medir y levantar el plano', responsable: 'Iván',
estado: 'hecha', horasEstimadas: 3, subtareas: [] },
{ id: 12, titulo: 'Elegir el mobiliario', responsable: 'Marta',
estado: 'en-curso', horasEstimadas: 0,
subtareas: [
{ id: 121, titulo: 'Pedir presupuestos', responsable: 'Marta',
estado: 'hecha', horasEstimadas: 2, subtareas: [] },
{ id: 122, titulo: 'Visitar a dos proveedores', responsable: 'Marta',
estado: 'pendiente', horasEstimadas: 2, subtareas: [] }
]
},
{ id: 13, titulo: 'Pintar y montar', responsable: 'Iván',
estado: 'pendiente', horasEstimadas: 5, subtareas: [] }
]
};
console.log(tareaRediseno.subtareas[1].subtareas[0].titulo); // 'Pedir presupuestos'
console.log(tareaRediseno.subtareas[0].subtareas.length); // 0
console.log(tareaRediseno.subtareas[9]?.titulo); // undefined (sin error)La última línea muestra por qué ?. es tan útil con estructuras anidadas: subtareas[9] no existe, y sin ?. obtendrías un TypeError. Un valor puede anidarse tan profundo como quieras: un objeto dentro de un array dentro de un objeto dentro de un array. El acceso siempre se lee de izquierda a derecha, un paso cada vez.
- Nombres de propiedad computados
A veces la clave de una propiedad no se conoce hasta que el programa corre. La sintaxis de nombre de propiedad computado permite calcularla dentro del propio literal, poniéndola entre corchetes:
const campo = 'estado';
const valor = 'en-curso';
const filtro = { [campo]: valor };
console.log(filtro); // { estado: 'en-curso' }
// Sin corchetes, la clave sería literalmente "campo"
const malFiltro = { campo: valor };
console.log(malFiltro); // { campo: 'en-curso' } ✗Se puede usar cualquier expresión que produzca un string:
const persona = 'Iván';
const contador = {
[`tareas_de_${persona}`]: 3,
[`horas_de_${persona}`]: 25
};
console.log(contador); // { tareas_de_Iván: 3, horas_de_Iván: 25 }Un uso práctico: una función que construye un criterio de búsqueda a partir del campo que el usuario elija en el futuro panel de Nómada Tareas.
function criterio(campo, valor) {
return { [campo]: valor };
}
console.log(criterio('responsable', 'Lucía')); // { responsable: 'Lucía' }
console.log(criterio('prioridad', 'alta')); // { prioridad: 'alta' }
- Recorrer un objeto:
for...in y Object.keys/values/entries
for...in y Object.keys/values/entriesUn array se recorre por posiciones; un objeto se recorre por claves. Hay dos caminos.
for...in recorre las claves (siempre como strings):
const tarea = backlog[0];
for (const clave in tarea) {
console.log(`${clave}: ${tarea[clave]}`);
}
// id: 1
// titulo: Rediseñar la sala polivalente
// responsable: Iván
// prioridad: alta
// estado: en-curso
// etiquetas: espacio,diseño
// horasEstimadas: 12
// fechaLimite: 2026-09-30
// revisor: MartaFíjate en el acceso: dentro del bucle hay que usar corchetes, tarea[clave], porque la clave está en una variable. tarea.clave daría undefined nueve veces.
Los tres métodos de Object convierten el objeto en arrays, que ya sabes manejar:
console.log(Object.keys(tarea));
// [ 'id', 'titulo', 'responsable', 'prioridad', 'estado',
// 'etiquetas', 'horasEstimadas', 'fechaLimite', 'revisor' ]
console.log(Object.values(tarea));
// [ 1, 'Rediseñar la sala polivalente', 'Iván', 'alta', 'en-curso',
// [ 'espacio', 'diseño' ], 12, '2026-09-30', 'Marta' ]
console.log(Object.entries(tarea));
// [ [ 'id', 1 ], [ 'titulo', 'Rediseñar la sala polivalente' ], ... ]
// ↑ un array de pares [clave, valor]Comparación práctica:
| Forma | Qué te da | Ventaja | Inconveniente |
|---|---|---|---|
for...in |
Una clave por vuelta | Sintaxis directa | También recorre propiedades heredadas del prototipo (Módulo 5) |
Object.keys(o) |
Array de claves propias | Solo propias; puedes usar métodos de array | Crea un array intermedio |
Object.values(o) |
Array de valores propios | Ideal para sumar o contar | Pierdes los nombres |
Object.entries(o) |
Array de pares [clave, valor] |
Lo tienes todo | Sintaxis algo más densa (mejora mucho con desestructuración, 04-06) |
En la práctica, Object.entries con un for...of es la opción por defecto, y for...in queda para casos puntuales:
En 04-06 verás cómo escribir eso mismo como for (const [clave, valor] of Object.entries(tarea)), que es mucho más legible.
Un ejemplo útil: contar cuántos campos del modelo están vacíos.
function camposVacios(tarea) {
const vacios = [];
for (const clave of Object.keys(tarea)) {
const valor = tarea[clave];
if (valor === null || valor === undefined || valor === '') vacios.push(clave);
}
return vacios;
}
console.log(camposVacios(backlog[1])); // [ 'revisor' ]
console.log(camposVacios(backlog[0])); // []
- Objetos como diccionario de consulta
Hay un segundo uso de los objetos, distinto de «agrupar los campos de una entidad»: usarlos como diccionario o tabla de consulta, donde las claves son datos y no nombres fijos. Ya lo viste de pasada en 02-03 como alternativa al switch:
const PESOS = { alta: 3, media: 2, baja: 1 };
function pesoDePrioridad(prioridad) {
return PESOS[prioridad] ?? 0; // corchetes: la clave viene en una variable
}
console.log(pesoDePrioridad('alta')); // 3
console.log(pesoDePrioridad('urgentí')); // 0 ← clave desconocida → undefined → ?? 0Compara las dos versiones de la misma función:
Cadena de if (03-01) |
Diccionario | |
|---|---|---|
| Añadir una prioridad nueva | Escribir otro if |
Añadir una línea a PESOS |
| Los datos están… | Mezclados con la lógica | Separados, en un solo sitio |
| Coste de la búsqueda | Recorre los if en orden |
Acceso directo por clave |
El patrón se generaliza a cualquier traducción valor → valor:
const MARCAS = { hecha: '✓', 'en-curso': '▸', pendiente: '○' };
const NOMBRES_ESTADO = { pendiente: 'Pendiente', 'en-curso': 'En curso', hecha: 'Terminada' };
function marcaDeEstado(estado) {
return MARCAS[estado] ?? '?';
}
function nombreDeEstado(estado) {
return NOMBRES_ESTADO[estado] ?? 'Desconocido';
}
console.log(`${marcaDeEstado('en-curso')} ${nombreDeEstado('en-curso')}`); // ▸ En cursoObserva que 'en-curso' necesita comillas como clave, porque el guion no es válido en un identificador. Al leerla, MARCAS['en-curso'] con corchetes; MARCAS.en-curso sería un error de sintaxis (JavaScript lo leería como una resta).
Y un tercer uso, el índice por identificador, que evita recorrer el backlog entero cada vez que buscas una tarea:
const porId = {};
for (const tarea of backlog) {
porId[tarea.id] = tarea; // la clave numérica se convierte en el string '1', '2'...
}
console.log(porId[3].titulo); // 'Actualizar la web de reservas'
console.log(porId[99]?.titulo); // undefinedEn 04-05 construirás este índice de forma mucho más elegante con reduce.
- Una mención breve a
Map y Set
Map y SetLos objetos como diccionario tienen dos límites: sus claves solo pueden ser strings (todo lo demás se convierte a string) y no llevan la cuenta de cuántas entradas tienen. Para esos casos JavaScript ofrece dos estructuras dedicadas:
Map: un diccionario donde la clave puede ser cualquier valor (un número de verdad, un objeto, una función), conserva el orden de inserción y tiene.size.Set: una colección de valores sin duplicados, ideal para deduplicar etiquetas.
const etiquetasUnicas = new Set(['serigrafía', 'compras', 'serigrafía']);
console.log(etiquetasUnicas.size); // 2No las desarrollamos aquí: aparecen con detalle en Buscar, Ordenar y Agregar Datos, cuando tengas que agrupar y deduplicar de verdad. Para el 95 % de lo que harás en este curso, un objeto literal es suficiente.
Errores Comunes y Consejos
1. Usar el punto cuando la clave está en una variable.
const campo = 'estado';
console.log(tarea.campo); // ✗ undefined
console.log(tarea[campo]); // ✓ 'en-curso'2. Olvidar las comillas al leer una clave con corchetes.
console.log(tarea[titulo]); // ✗ ReferenceError: titulo is not defined
console.log(tarea['titulo']); // ✓Con corchetes, lo de dentro es una expresión. Sin comillas, JavaScript busca una variable con ese nombre.
3. Creer que const congela el objeto. No lo hace: const protege la variable, no el contenido. Para impedir cambios en el objeto existe Object.freeze, que verás en 04-08.
4. Confundir «no existe» con «vale undefined». Usa Object.hasOwn cuando la pregunta sea sobre la clave; compara con undefined cuando sea sobre el valor.
5. Encadenar accesos sin protección. tarea.subtareas[0].titulo explota si subtareas está vacío. Con tarea.subtareas[0]?.titulo obtienes undefined y sigues vivo.
6. Poner una coma de más entre propiedades. { id: 1,, titulo: 'x' } es un error de sintaxis. La coma final ({ id: 1, }) sí está permitida y es buena práctica: los diffs de Git salen más limpios.
Consejo profesional. Elige un orden fijo para los campos —en Nómada Tareas: id, titulo, responsable, prioridad, estado, etiquetas, horasEstimadas, fechaLimite, revisor— y respétalo en todos los objetos. Leer código es comparar, y comparar es mucho más rápido cuando todo está en el mismo sitio.
Ejercicios
Ejercicio 1 — Fichas del taller. Crea un objeto taller que describa el espacio de Taller Nómada con las propiedades nombre ('Taller Nómada'), plazas (18), zonas (array con 'coworking', 'serigrafía', 'encuadernación', 'carpintería') y coordinadora ('Marta'). Después:
- Muestra por consola la segunda zona.
- Añade la propiedad
abiertoSabadoscon valortrue. - Cambia
plazasa 20. - Borra
coordinadoray comprueba conObject.hasOwnque ya no está. - Lee una propiedad inexistente
telefonosin que el programa falle, mostrando'sin teléfono'si no existe.
Ejercicio 2 — Diccionario de plazos. Escribe una función diasDeMargen(prioridad) que, usando un objeto de consulta (no if ni switch), devuelva cuántos días de margen tiene una tarea según su prioridad: alta → 3, media → 7, baja → 15. Si la prioridad es desconocida, debe devolver 30. Prueba con 'alta', 'baja' y 'ninguna'.
Ejercicio 3 — Inspector de tareas. Escribe una función inspeccionar(tarea) que reciba una tarea del backlog y devuelva un string multilínea con una línea por campo, en el formato clave: valor, saltándose el campo etiquetas. Después escribe resumenDeCampos(tarea), que devuelva un objeto con tres propiedades: total (número de campos), vacios (número de campos cuyo valor sea null) y nombres (array con los nombres de los campos). Pruébalo con backlog[1].
Soluciones
Ejercicio 1
const taller = {
nombre: 'Taller Nómada',
plazas: 18,
zonas: ['coworking', 'serigrafía', 'encuadernación', 'carpintería'],
coordinadora: 'Marta'
};
// 1. Segunda zona: el índice 1, porque los arrays empiezan en 0
console.log(taller.zonas[1]); // 'serigrafía'
// 2. Añadir una propiedad nueva
taller.abiertoSabados = true;
// 3. Modificar una existente
taller.plazas = 20;
// 4. Borrar y comprobar
delete taller.coordinadora;
console.log(Object.hasOwn(taller, 'coordinadora')); // false
// 5. Propiedad inexistente sin fallar
console.log(taller.telefono ?? 'sin teléfono'); // 'sin teléfono'
console.log(taller);
// { nombre: 'Taller Nómada', plazas: 20,
// zonas: [ 'coworking', 'serigrafía', 'encuadernación', 'carpintería' ],
// abiertoSabados: true }El error frecuente aquí es el punto 1: taller.zonas[2] devolvería 'encuadernación', la tercera. Recuerda que la posición 1 es el segundo elemento.
Ejercicio 2
const MARGEN_EN_DIAS = { alta: 3, media: 7, baja: 15 };
function diasDeMargen(prioridad) {
return MARGEN_EN_DIAS[prioridad] ?? 30;
}
console.log(diasDeMargen('alta')); // 3
console.log(diasDeMargen('baja')); // 15
console.log(diasDeMargen('ninguna')); // 30Detalle importante: aquí ?? es más correcto que ||. Si algún día una prioridad tuviera margen 0, MARGEN_EN_DIAS[p] || 30 devolvería 30 porque 0 es falsy, mientras que ?? solo actúa ante null o undefined. Es exactamente la distinción que estudiaste en 01-06.
Ejercicio 3
function inspeccionar(tarea) {
const lineas = [];
for (const clave of Object.keys(tarea)) {
if (clave === 'etiquetas') continue;
lineas.push(`${clave}: ${tarea[clave]}`);
}
return lineas.join('\n');
}
function resumenDeCampos(tarea) {
const nombres = Object.keys(tarea);
let vacios = 0;
for (const clave of nombres) {
if (tarea[clave] === null) vacios++;
}
return { total: nombres.length, vacios: vacios, nombres: nombres };
}
console.log(inspeccionar(backlog[1]));
// id: 2
// titulo: Cartelería del taller de serigrafía
// responsable: Marta
// prioridad: media
// estado: pendiente
// horasEstimadas: 6
// fechaLimite: 2026-10-15
// revisor: null
console.log(resumenDeCampos(backlog[1]));
// { total: 9, vacios: 1,
// nombres: [ 'id', 'titulo', 'responsable', 'prioridad', 'estado',
// 'etiquetas', 'horasEstimadas', 'fechaLimite', 'revisor' ] }Dos observaciones. Primera: resumenDeCampos devuelve un objeto, que es justo lo que recomendaba 03-03 para retornar varios valores con nombre; ahora ya sabes cómo se construye ese objeto. Segunda: el continue dentro del for...of es el mismo de 02-04, y aquí sirve para saltarse el campo etiquetas sin romper el bucle.
Conclusión
Has saldado la deuda. Un objeto es una colección de pares clave → valor que se escribe con un literal {}; sus propiedades se leen con punto cuando conoces el nombre y con corchetes cuando el nombre está en una variable o se calcula. Sabes añadir y modificar propiedades sobre la marcha, borrarlas con delete (y por qué en este proyecto casi nunca conviene), y qué ocurre al leer una que no existe: undefined, sin error, salvo que intentes seguir encadenando —para eso está ?., que ahora entiendes de verdad—. Tienes también la tabla de las cuatro formas de comprobar la existencia de una clave, con Object.hasOwn como opción recomendada.
Y sobre todo tienes el backlog real: seis objetos dentro de un array, con los mismos datos de siempre (48 h totales, 45 abiertas, Iván con 25 h, la carpintería vencida) pero por fin estructurados. Con él has reescrito estaVencida(tarea, hoy) y describirTarea(tarea, hoy), y has comprobado el cambio de escala: de ocho parámetros posicionales a dos, de seis arrays que había que mantener sincronizados a una lista que se borra y se ordena sin miedo. Has visto además los objetos anidados del árbol de subtareas, los nombres de propiedad computados { [clave]: valor }, las cuatro formas de recorrer un objeto (for...in, Object.keys, Object.values, Object.entries) y el uso de los objetos como diccionario de consulta, que convierte cuatro if en una línea.
Pero un objeto puede contener algo más que datos. Si PESOS puede vivir dentro de un objeto, ¿por qué no pesoDePrioridad? ¿Y si el tablero de Taller Nómada guardara, junto a su lista de tareas, las propias operaciones de agregar una tarea o cambiarle el estado? Eso es exactamente lo que veremos en Métodos de Objeto y la Palabra Clave this: funciones guardadas en propiedades, la sintaxis abreviada para escribirlas y esa palabra clave, this, que es a la vez la más útil y la más malentendida de JavaScript. De paso cerrarás otra deuda pendiente del Módulo 3: por qué las funciones flecha se comportan de forma distinta a las normales.
Curso de JavaScript: De Principiante a Avanzado
Módulo 1: Introducción a JavaScript
- ¿Qué es JavaScript?
- Configuración de tu Entorno de Desarrollo
- Tu Primer Programa en JavaScript
- Sintaxis y Conceptos Básicos de JavaScript
- Variables y Tipos de Datos
- Operadores Básicos
- Conversión de Tipos y Comparaciones
- El Proyecto del Curso: Nómada Tareas
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for, while, do-while
- Sentencias Switch
- Control del Flujo: break, continue y Bucles Anidados
- Manejo de Errores con try-catch
Módulo 3: Funciones
- Definición y Llamada de Funciones
- Expresiones de Función y Funciones Flecha
- Parámetros y Valores de Retorno
- Ámbito y Closures
- Hoisting y el Contexto de Ejecución
- Funciones de Orden Superior
- Recursividad
Módulo 4: Objetos y Arrays
- Introducción a los Objetos
- Métodos de Objeto y la Palabra Clave
this - Arrays: Conceptos Básicos y Métodos
- Iteración sobre Arrays
- Buscar, Ordenar y Agregar Datos: find, sort y reduce
- Desestructuración de Arrays
- Desestructuración de Objetos, Spread y Rest
- JSON y Copias de Objetos
Módulo 5: Objetos y Funciones Avanzadas
- Prototipos y Herencia
- Clases y Programación Orientada a Objetos
- Encapsulación: Getters, Setters y Campos Privados
- Módulos e Importación/Exportación
- JavaScript Asíncrono: Callbacks
- Promesas y Async/Await
- El Bucle de Eventos y la Cola de Microtareas
- Iteradores y Generadores
Módulo 6: El Modelo de Objetos del Documento (DOM)
- Introducción al DOM
- Selección y Manipulación de Elementos del DOM
- Manejo de Eventos
- Propagación, Delegación y Eventos Personalizados
- Creación y Eliminación de Elementos del DOM
- Renderizado de Listas y Plantillas HTML
- Manejo y Validación de Formularios
Módulo 7: APIs del Navegador y Temas Avanzados
- Almacenamiento Local y de Sesión
- Fetch API y AJAX
- Peticiones Robustas: Errores, Timeouts y AbortController
- WebSockets
- Service Workers y Aplicaciones Web Progresivas (PWAs)
- APIs del Navegador Esenciales
- Introducción a WebAssembly
Módulo 8: Pruebas y Depuración
- Depuración de JavaScript
- Calidad de Código: ESLint, Prettier y Convenciones
- Pruebas Unitarias con Jest
- Dobles de Prueba: Mocks, Stubs y Spies
- Pruebas de Integración
- Pruebas de Extremo a Extremo con Cypress
Módulo 9: Rendimiento y Optimización
- Medir Antes de Optimizar: DevTools y Web Vitals
- Optimización del Rendimiento de JavaScript
- Gestión de Memoria
- Manipulación Eficiente del DOM
- Carga Perezosa y División de Código
Módulo 10: Frameworks y Librerías de JavaScript
- Por Qué Existen los Frameworks
- Introducción a React
- Gestión de Estado con Redux
- Conceptos Básicos de Vue.js
- Conceptos Básicos de Angular
- Elegir el Framework Adecuado
