Has practicado el patrón State & Render construyendo un carrito de la compra. Ahora vas a aplicar el mismo patrón para crear algo más épico: tu propia Pokédex interactiva con los 150 Pokémon originales de Kanto.
Tu misión es completar IronDex, una aplicación donde puedes explorar los Pokémon, buscarlos por nombre, filtrarlos por tipo y marcar los que has capturado.
Recuerda las reglas:
- El estado (
state) es la única fuente de verdad - Las funciones de renderizado leen el estado y actualizan el DOM
- Los eventos del usuario modifican el estado y llaman a
render() - NUNCA manipules el DOM fuera de las funciones de renderizado
Evento del usuario (click, input, change)
│
▼
Modificar el estado (state)
│
▼
Llamar a render()
│
▼
render() lee state y actualiza el DOM
- Haz fork de este repo a tu cuenta de GitHub
- Clona el fork a tu máquina local
- Abre el proyecto en VS Code
Al terminar:
git add .
git commit -m "done"
git push origin mainCrea un Pull Request de tu fork hacia el repositorio original.
El HTML y el CSS ya están completos. Tu único trabajo es completar las funciones en src/index.js.
Abre index.html en el navegador. Verás la estructura de la página pero sin Pokémon — las funciones de renderizado aún no están implementadas.
En tu proyecto encontrarás:
src/data.js— Arraypokemoncon los 150 Pokémon (id, nombre, tipos, sprite). No lo modifiques.src/index.js— Estado, funciones auxiliares, funciones de estado y funciones de renderizado. Aquí escribes tu código.
El estado tiene esta estructura:
const state = {
search: '', // texto del buscador
typeFilter: 'all', // tipo seleccionado ('all' = todos)
captured: [], // array de ids de Pokémon capturados
};Implementa la función renderPokemon().
Esta función debe:
- Seleccionar el elemento
#pokemon-list - Obtener la lista de Pokémon a mostrar (por ahora, usa directamente el array
pokemon— en una iteración posterior usarásgetFilteredPokemon()) - Para cada Pokémon, generar HTML con esta estructura:
<div class="pokemon-card">
<span class="pokemon-id">#001</span>
<span class="pokeball-icon">⬤</span>
<img src="https://raw.githubusercontent.com/.../1.png" alt="Bulbasaur" />
<p class="pokemon-name">Bulbasaur</p>
<div class="pokemon-types">
<span class="type-badge type-Grass">Grass</span>
<span class="type-badge type-Poison">Poison</span>
</div>
</div>- Insertar el HTML en
#pokemon-list
Nota: Para formatear el id con ceros (001, 025, 150), usa
String(id).padStart(3, '0').
Nota: Cada tipo necesita la clase
type-{Tipo}(ej:type-Fire,type-Water) para que el CSS le aplique su color.
💡 Pista
Para generar los badges de tipo de cada Pokémon:
var typeBadges = p.types.map(function (type) {
return '<span class="type-badge type-' + type + '">' + type + '</span>';
}).join('');Al terminar esta iteración deberías ver los 150 Pokémon en el navegador.
Implementa las funciones isCaptured(pokemonId) y toggleCapture(pokemonId).
isCaptured(pokemonId) debe devolver true si el id está en state.captured, y false si no.
toggleCapture(pokemonId) debe:
- Si el Pokémon ya está capturado, eliminarlo de
state.captured - Si no está capturado, añadir su id a
state.captured - Llamar a
render()
Luego, modifica renderPokemon() para:
- Añadir la clase CSS
captureda las tarjetas de Pokémon que estén capturados - Hacer que al hacer clic en una tarjeta se llame a
toggleCapture(pokemon.id)
💡 Pista
Para añadir condicionalmente la clase captured:
var capturedClass = isCaptured(p.id) ? ' captured' : '';
'<div class="pokemon-card' + capturedClass + '" onclick="toggleCapture(' + p.id + ')">'Para eliminar un id del array:
state.captured = state.captured.filter(function (id) {
return id !== pokemonId;
});Al hacer clic en un Pokémon, su tarjeta debería cambiar de estilo (borde dorado). Haciendo clic de nuevo, vuelve a su estado normal.
Implementa la función renderCapturedPanel().
Esta función debe:
- Actualizar
#captured-countcon el formato(X / 150)donde X es el número de capturados - Para cada id en
state.captured, buscar el Pokémon en el arraypokemony generar HTML:
<div class="captured-item">
<img src="..." alt="Pikachu" />
<span class="captured-name">Pikachu</span>
<span class="captured-id">#025</span>
<button class="btn-release">✕</button>
</div>- Insertar el HTML en
#captured-list - El botón
✕de cada captura debe llamar atoggleCapture(pokemon.id)para liberar ese Pokémon - Mostrar/ocultar
#empty-captured-messagesegún si hay capturas o no - Mostrar/ocultar
#btn-release-allsegún si hay capturas o no
💡 Pista
Para buscar un Pokémon por id:
var p = pokemon.find(function (poke) {
return poke.id === id;
});Implementa la función setSearch(text) y la función getFilteredPokemon().
setSearch(text) debe:
- Actualizar
state.searchcon el texto recibido - Llamar a
render()
getFilteredPokemon() debe:
- Filtrar el array
pokemoncomparando el nombre constate.search(case-insensitive) - Por ahora, ignora
state.typeFilter(lo añadirás en la siguiente iteración) - Devolver el array filtrado
Luego:
- Conecta el
#search-inputal eventoinputpara que llame asetSearchcon el valor actual del input - Modifica
renderPokemon()para usargetFilteredPokemon()en lugar del arraypokemondirectamente
Implementa también renderPokemonCount() para actualizar #pokemon-count con el texto: "Mostrando X de 150 Pokémon".
💡 Pista
Filtro case-insensitive:
var matchesName = p.name.toLowerCase().includes(state.search.toLowerCase());Conectar el input:
document.getElementById('search-input').addEventListener('input', function (event) {
setSearch(event.target.value);
});Escribe "pika" en el buscador y deberías ver solo a Pikachu.
Implementa la función setTypeFilter(type).
setTypeFilter(type) debe:
- Actualizar
state.typeFiltercon el tipo recibido - Llamar a
render()
Luego:
- Conecta el
#type-filter(select) al eventochangepara que llame asetTypeFiltercon el valor seleccionado - Completa
getFilteredPokemon()para que también filtre por tipo: sistate.typeFilterno es'all', solo mostrar Pokémon que incluyan ese tipo en su arraytypes
💡 Pista
Ambos filtros deben aplicarse a la vez:
var matchesName = p.name.toLowerCase().includes(state.search.toLowerCase());
var matchesType = state.typeFilter === 'all' || p.types.includes(state.typeFilter);
return matchesName && matchesType;Selecciona "Fire" en el desplegable y deberías ver solo los Pokémon de tipo Fuego. Si además escribes "char", solo deberías ver Charmander, Charmeleon y Charizard.
Implementa la función releaseAll() y conéctala al botón #btn-release-all dentro de renderCapturedPanel().
releaseAll() debe:
- Vaciar
state.captured - Llamar a
render()
Implementa persistencia con localStorage:
- Después de cada cambio en
state.captured(entoggleCaptureyreleaseAll), guarda el array enlocalStorage - Al cargar la página (antes del
render()inicial), recupera los capturados delocalStoragesi existen
💡 Pista
// Guardar
localStorage.setItem('captured', JSON.stringify(state.captured));
// Recuperar (antes del render inicial)
var saved = localStorage.getItem('captured');
if (saved) {
state.captured = JSON.parse(saved);
}Happy coding! ❤️
