Tecnologías y Aplicaciones Web · Proyecto Full Stack
Pokédex Web
Aplicación Full Stack con PokeAPI · JSON · Node.js · MySQL
HTML5
JavaScript
Node.js + Express
MySQL
PokeAPI
Introducción

Conceptos Fundamentales

Lo que necesitas entender antes de comenzar el proyecto

🏗️
Arquitectura Web
Es la estructura que define cómo se organizan y comunican los componentes de una app. Como los planos de un edificio antes de construirlo.
↔️
Cliente-Servidor
El cliente (navegador) pide datos. El servidor los procesa y responde. Como ordenar en un restaurante: tú pides, la cocina prepara.
🔌
API REST
Interfaz que permite a dos sistemas comunicarse por HTTP. Como un menú que define qué puedes pedir y cómo recibirás la respuesta.
📦
JSON
Formato ligero para intercambiar datos entre sistemas. Es texto estructurado que cualquier lenguaje puede leer y escribir.
🗄️
MySQL
Sistema de base de datos relacional. Guarda datos en tablas con filas y columnas, como una hoja de Excel muy poderosa.
Express.js
Framework de Node.js para crear servidores web. Define rutas que responden a peticiones HTTP del cliente.
Arquitectura

Flujo Completo del Proyecto

Qué ocurre en cada paso cuando el usuario busca un Pokémon

Frontend (HTML + JS)
fetch() → PokeAPI
PokeAPI Externa
Respuesta JSON
JSON → Mostrar UI
fetch() POST
Express.js (Backend)
INSERT INTO
MySQL Database
Usuario busca "Pikachu"

El navegador hace fetch() a la PokeAPI y recibe un objeto JSON con todos los datos del Pokémon.

Se muestra en pantalla

El JavaScript extrae imagen, nombre, tipo y estadísticas del JSON y los inserta en el DOM.

Se guarda en la BD

Al hacer clic en "Guardar", el frontend hace fetch() POST a Express, que ejecuta un INSERT INTO en MySQL.

Se consulta la BD

Un GET al backend hace SELECT * FROM pokemon y regresa todos los Pokémon guardados como JSON.

PokeAPI

La PokeAPI

API pública y gratuita con datos de todos los Pokémon — perfecta para aprender

Endpoint que usaremos
https://pokeapi.co/api/v2/pokemon/{nombre}

Reemplaza {nombre} con cualquier Pokémon: pikachu, charizard, bulbasaur

Lo que devuelve
  • name — Nombre del Pokémon
  • id — Número en la Pokédex
  • types — Tipos (fuego, agua…)
  • height — Altura en decímetros
  • weight — Peso en hectogramos
  • sprites.front_default — Imagen PNG
  • base_experience — Experiencia base
Respuesta JSON de PokeAPI
{
 "name": "pikachu",
 "id": 25,
 "height": 4,
 "weight": 60,
 "base_experience": 112,
 "types": [
  {
   "slot": 1,
   "type": {
    "name": "electric"
   }
  }
 ],
 "sprites": {
  "front_default":
  "https://raw...pikachu.png"
 }
}
Setup del proyecto

Configuración del Entorno

Instalar herramientas y crear la estructura de carpetas

1
Instalar Node.js
Descarga desde nodejs.org → versión LTS. Verifica: node -v en terminal.
2
Instalar MySQL Workbench
Descarga MySQL Community desde dev.mysql.com. Incluye servidor + interfaz gráfica.
3
Instalar VS Code
Editor de código desde code.visualstudio.com. Instala extensión REST Client para probar APIs.
4
Crear estructura de carpetas
Crea la carpeta raíz y subcarpetas como se muestra a la derecha.
Estructura del proyecto
📁 pokedex-app/

├── 📁 frontend/  ← Lo que ve el usuario
│    ├── index.html
│    ├── style.css
│    └── app.js

├── 📁 backend/  ← Servidor Node.js
│    ├── server.js
│    ├── db.js
│    └── package.json

└── 📁 database/  ← Scripts SQL
      └── schema.sql
Terminal — iniciar backend
# Entrar a la carpeta backend
cd pokedex-app/backend

# Inicializar proyecto Node
npm init -y

# Instalar dependencias
npm install express cors mysql2
express = servidor web · cors = permite peticiones cross-origin · mysql2 = conecta Node con MySQL
Base de datos

Crear la Base de Datos

SQL para crear la tabla donde guardaremos los Pokémon

database/schema.sql
-- 1. Crear la base de datos
CREATE DATABASE pokedex_web;

-- 2. Seleccionarla para usarla
USE pokedex_web;

-- 3. Crear la tabla de Pokémon
CREATE TABLE pokemon (
  id         INT AUTO_INCREMENT
               PRIMARY KEY,
  nombre     VARCHAR(100),
  tipo       VARCHAR(100),
  altura     INT,
  peso       INT,
  experiencia INT,
  imagen     TEXT
);
Cada columna explicada
  • id — número único auto-generado. La llave primaria identifica cada fila
  • nombre — texto hasta 100 caracteres (pikachu, charizard…)
  • tipo — texto (electric, fire, water…)
  • altura / peso — números enteros de la PokeAPI
  • experiencia — base_experience del JSON
  • imagen — URL larga → necesita TEXT no VARCHAR
AUTO_INCREMENT significa que MySQL asigna el ID automáticamente — tú no tienes que escribirlo al insertar. PRIMARY KEY garantiza que no haya dos filas con el mismo ID.
Ejecuta este SQL en MySQL Workbench: abre una nueva pestaña de query, pega el código y presiona Ctrl+Shift+Enter.
Backend

Conexión a MySQL — db.js

Archivo que conecta Node.js con la base de datos MySQL

backend/db.js
// 1. Importar el módulo mysql2
const mysql = require('mysql2');

// 2. Crear la conexión con credenciales
const connection = mysql.createConnection({
  host: 'localhost', // servidor local
  user: 'root',     // usuario MySQL
  password: 'tu_contraseña',
  database: 'pokedex_web'
});

// 3. Conectar y verificar
connection.connect((err) => {
  if (err) {
    console.error('❌ Error:', err);
    return;
  }
  console.log('✅ MySQL conectado');
});

// 4. Exportar para usarlo en otros archivos
module.exports = connection;
Qué hace cada parte
  • require('mysql2') — importa el paquete que instalamos
  • createConnection() — configura la conexión con la BD
  • host — dónde está el servidor MySQL (localhost = tu PC)
  • user/password — credenciales de MySQL Workbench
  • database — nombre que le pusimos al crear la BD
  • connect() — abre la conexión y verifica que funcione
  • module.exports — permite que server.js use este archivo
Cambia 'tu_contraseña' por la contraseña que definiste al instalar MySQL. Si dejaste la contraseña vacía, pon ''.
Backend

Servidor Express — server.js

El corazón del backend: recibe peticiones y las procesa

backend/server.js — Configuración
// 1. Importar dependencias
const express = require('express');
const cors    = require('cors');
const db      = require('./db');

// 2. Crear la aplicación Express
const app = express();

// 3. Middlewares (configuraciones globales)
app.use(cors()); // Permitir peticiones del frontend
app.use(express.json()); // Leer cuerpo JSON

// 4. ─── RUTAS van aquí ───

// 5. Iniciar servidor en puerto 3000
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`✅ Servidor en puerto ${PORT}`);
});
Middlewares explicados
  • app.use(cors()) — sin esto, el navegador bloquea las peticiones del frontend al backend (error CORS)
  • app.use(express.json()) — sin esto, req.body estaría vacío al recibir JSON
  • app.listen(3000) — el servidor escucha en http://localhost:3000
Cómo ejecutar el servidor
node backend/server.js
# O con nodemon (auto-recarga):
npx nodemon backend/server.js
Instala nodemon para que el servidor se reinicie automáticamente al guardar: npm install -g nodemon
Endpoint POST

Guardar Pokémon — POST /pokemon

Ruta que recibe el JSON del frontend y lo inserta en MySQL

backend/server.js — Ruta POST
// Ruta POST: guardar un Pokémon
app.post('/pokemon', (req, res) => {

  // Extraer datos del cuerpo JSON
  const {
    nombre, tipo, altura,
    peso, experiencia, imagen
  } = req.body;

  // Query SQL con parámetros seguros (?)
  const sql = `
    INSERT INTO pokemon
    (nombre, tipo, altura, peso,
     experiencia, imagen)
    VALUES (?, ?, ?, ?, ?, ?)
  `
;

  // Ejecutar la consulta
  db.query(sql,
    [nombre, tipo, altura,
     peso, experiencia, imagen],
    (err, result) => {
      if (err) {
        return res.status(500)
          .json({ error: err });
      }
      res.json({
        success: true,
        id: result.insertId
      });
    }
  );
});
Conceptos clave del POST
  • app.post('/pokemon') — define la ruta que escucha peticiones POST
  • req.body — el JSON que envió el frontend llega aquí
  • VALUES (?, ?, …) — los ? evitan inyección SQL. Seguridad básica
  • db.query() — ejecuta el SQL en MySQL
  • result.insertId — el ID auto-generado del nuevo registro
  • res.json() — devuelve la respuesta al frontend en JSON
🔒 Nunca concatenes el SQL así: WHERE nombre = '${nombre}' — eso permite SQL Injection. Usa siempre los ? parametrizados.
Endpoint GET

Leer Pokémon — GET /pokemon

Ruta que consulta todos los Pokémon guardados en MySQL

backend/server.js — Ruta GET
// Ruta GET: obtener todos los Pokémon
app.get('/pokemon', (req, res) => {

  const sql = 'SELECT * FROM pokemon';

  db.query(sql, (err, results) => {
    if (err) {
      return res.status(500)
        .json({ error: err });
    }
    // results = array de filas de la tabla
    res.json(results);
  });
});
Qué devuelve el GET
Respuesta JSON
[
 {
  "id":1,
  "nombre":"pikachu",
  "tipo":"electric",
  "altura":4,
  "peso":60,
  "experiencia":112,
  "imagen":"https://..."
 },
 { /* más pokémon */ }
]
Puedes probar la ruta GET directamente en el navegador: ve a http://localhost:3000/pokemon y verás el JSON de respuesta.
Frontend

Estructura HTML — index.html

La interfaz que ve el usuario: búsqueda, tarjeta y lista de guardados

frontend/index.html
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8"/>
  <title>Pokédex Web</title>
  <link rel="stylesheet" href="style.css"/>
</head>
<body>
  <h1>⚡ Pokédex Web</h1>

  <!-- Sección de búsqueda -->
  <section id="busqueda">
    <input
      type="text"
      id="inputPokemon"
      placeholder="Buscar Pokémon..."/>
    <button id="btnBuscar">
      🔍 Buscar
    </button>
  </section>

  <!-- Tarjeta del Pokémon encontrado -->
  <section id="resultado"></section>

  <!-- Lista de Pokémon guardados -->
  <section id="guardados">
    <h2>🗄️ Guardados</h2>
    <div id="listaGuardados"></div>
  </section>

  <script src="app.js"></script>
</body></html>
Secciones de la página
  • #busqueda — input para el nombre y botón buscar
  • #resultado — aquí el JS inserta la tarjeta del Pokémon
  • #guardados — lista de Pokémon que tenemos en MySQL
  • app.js al final — se carga al último para que el DOM ya exista
Los id="" en cada elemento son fundamentales: el JavaScript los usará con document.getElementById() para manipular el DOM.
JavaScript

Consumir PokeAPI — app.js

Usar fetch() y async/await para pedir datos a la API externa

frontend/app.js — Buscar Pokémon
// Referencias al DOM
const input = document.getElementById('inputPokemon');
const btn   = document.getElementById('btnBuscar');
const resultado = document.getElementById('resultado');

// Escuchar clic en el botón
btn.addEventListener('click', buscarPokemon);

// Función principal de búsqueda
async function buscarPokemon() {
  const nombre = input.value
    .trim().toLowerCase();

  if (!nombre) return;

  try {
    // Pedir a la PokeAPI
    const url = `https://pokeapi.co/api/v2/
                pokemon/${nombre}`
;
    const res   = await fetch(url);
    const data = await res.json();

    // Mostrar resultado en pantalla
    mostrarPokemon(data);
  } catch (err) {
    resultado.innerHTML = '❌ No encontrado';
  }
}
async/await explicado
  • async function — declara que la función puede esperar respuestas
  • await fetch(url) — hace la petición HTTP y espera la respuesta
  • await res.json() — convierte la respuesta en un objeto JS
  • try/catch — si el Pokémon no existe, captura el error y muestra mensaje
  • .trim().toLowerCase() — limpia espacios y convierte a minúsculas para que la API lo acepte
💡 fetch() es como pedirle datos a un mesero (la API). await es esperar pacientemente a que traiga lo que pediste antes de continuar.
JavaScript

Mostrar Pokémon y Guardar

Insertar la tarjeta en el DOM y enviar JSON al backend

app.js — mostrarPokemon()
function mostrarPokemon(data) {
  // Extraer datos del JSON
  const nombre = data.name;
  const tipo   = data.types[0].type.name;
  const imagen = data.sprites.front_default;
  const altura = data.height;
  const peso   = data.weight;
  const exp    = data.base_experience;

  // Insertar HTML con template literal
  resultado.innerHTML = `
    <div class="pokemon-card">
      <img src="${imagen}" />
      <h2>${nombre}</h2>
      <p>Tipo: ${tipo}</p>
      <p>Altura: ${altura}</p>
      <p>Peso: ${peso}</p>
      <button onclick="guardar(
        '${nombre}','${tipo}',${altura},
        ${peso},${exp},'${imagen}'
      )">💾 Guardar</button>
    </div>
  `
;
}
app.js — guardar() + cargar()
// Enviar al backend con fetch POST
async function guardar(
  nombre, tipo, altura, peso, exp, img
) {
  await fetch('http://localhost:3000/pokemon', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      nombre, tipo, altura, peso,
      experiencia: exp,
      imagen: img
    })
  });
  cargarGuardados(); // Actualizar lista
}

// Cargar Pokémon guardados desde BD
async function cargarGuardados() {
  const res = await
    fetch('http://localhost:3000/pokemon');
  const lista = await res.json();
  const div = document.getElementById('listaGuardados');
  div.innerHTML = lista.map(p =>
    `<div><img src="${p.imagen}">
     <strong>${p.nombre}</strong></div>`

  ).join('');
}

cargarGuardados(); // Al cargar la página
🔑 JSON.stringify() convierte el objeto JS a texto JSON para enviarlo. Content-Type: application/json le dice al servidor que el cuerpo es JSON.
Resumen

Flujo Completo del Proyecto

Todo el recorrido de los datos de principio a fin

🧑‍💻
Usuario
Escribe nombre
fetch() GET
PokeAPI
API externa
JSON resp.
🌐
Frontend
Muestra tarjeta
POST JSON
⚙️
Express
Recibe req.body
INSERT SQL
🗄️
MySQL
Guarda registro
PokeAPI devuelve JSON Frontend procesa y muestra Express guarda en MySQL SELECT devuelve lista
Errores comunes

Errores Frecuentes y Soluciones

Los problemas más comunes que encontrarás — y cómo resolverlos

Error CORS
Síntoma: "Access to fetch blocked by CORS policy" en la consola del navegador.
Causa: El servidor no permite peticiones desde otro origen (el frontend).
Solución: Asegúrate de tener app.use(cors()) en server.js y que instalaste el paquete cors.
req.body undefined
Síntoma: Los datos llegan vacíos al backend aunque los envías desde el frontend.
Causa: Falta el middleware express.json() o el header Content-Type: application/json.
Solución: Agrega app.use(express.json()) en server.js y verifica el fetch del frontend.
MySQL desconectado
Síntoma: Error "ECONNREFUSED" o "Access denied for user" al arrancar el servidor.
Causa: MySQL no está ejecutándose o la contraseña en db.js es incorrecta.
Solución: Inicia el servicio MySQL Workbench y verifica usuario/contraseña en db.js.
Puerto incorrecto
Síntoma: fetch() falla con "net::ERR_CONNECTION_REFUSED".
Causa: El servidor no está corriendo o el puerto en el fetch del frontend no coincide.
Solución: Ejecuta node server.js y verifica que el puerto sea el mismo en server.js y en app.js.
Pokémon no encontrado en API
Síntoma: La tarjeta no aparece aunque escribiste el nombre.
Causa: Nombre con mayúsculas o con espacios. La PokeAPI solo acepta nombres en minúsculas sin espacios.
Solución: Usa .trim().toLowerCase() antes del fetch. Prueba en el navegador: pokeapi.co/api/v2/pokemon/pikachu
Buenas prácticas

Buenas Prácticas de Desarrollo

Organización del proyecto
  • Separa siempre frontend de backend
  • Un archivo para la conexión BD (db.js), otro para rutas (server.js)
  • Usa .env para contraseñas — nunca en el código
  • El archivo SQL en su propia carpeta /database
Seguridad básica
  • Siempre usar ? parametrizados en SQL
  • Validar los datos antes de guardarlos
  • Nunca exponer contraseñas en el frontend
  • Agregar manejo de errores en todos los fetch()
Nombres de variables
  • Usa camelCase en JavaScript: nombrePokemon
  • Nombres descriptivos: btnGuardar mejor que btn1
  • Constantes en MAYÚSCULAS: const PORT = 3000
  • Comenta las secciones importantes del código
Frontend / Backend
  • El frontend nunca accede directamente a MySQL
  • Toda lógica de BD va en el backend
  • El backend responde siempre en JSON
  • Usa códigos HTTP: 200 OK, 201 Created, 500 Error
Verificar datos

Verificar Datos en MySQL

Comprobar que los Pokémon se guardaron correctamente

MySQL Workbench — Consultas
-- Ver todos los Pokémon guardados
SELECT * FROM pokemon;

-- Buscar un Pokémon específico
SELECT * FROM pokemon
WHERE nombre = 'pikachu';

-- Ver solo nombre y tipo
SELECT nombre, tipo
FROM pokemon
ORDER BY nombre ASC;

-- Cuántos Pokémon tenemos
SELECT COUNT(*) AS total
FROM pokemon;
Resultado esperado
id | nombre   | tipo     | altura | peso
1 | pikachu  | electric |    4 |   60
2 | charizard| fire     |   17 | 905
3 | bulbasaur| grass    |    7 |   69
También puedes verificar haciendo GET en el navegador: http://localhost:3000/pokemon — debería devolver el array JSON con todos los registros.
Actividad final

Entregable del Proyecto

Lo que deberás entregar al finalizar el taller

1
Crear la base de datos en MySQL
Ejecutar el script schema.sql y verificar con SELECT * FROM pokemon.
2
Backend funcionando
El servidor Express debe correr sin errores. Rutas POST y GET deben responder correctamente.
3
Frontend conectado
La búsqueda de Pokémon debe mostrar la tarjeta con imagen, nombre, tipo, altura y peso.
4
Guardar en MySQL
El botón "Guardar" debe insertar el Pokémon en la tabla y actualizar la lista guardados.
5
Mostrar guardados
Al cargar la página, deben aparecer los Pokémon ya guardados consultando el GET del backend.
Extensión (puntos extra)
  • Agregar botón Eliminar Pokémon de MySQL (DELETE)
  • Búsqueda en la lista guardados con input de filtro
  • Mostrar estadísticas en una gráfica sencilla
  • Validar duplicados antes de guardar
  • Agregar estilos al tipo de Pokémon (colores por tipo)
Entrega: Comprime la carpeta pokedex-app/ en ZIP. Incluye una captura de pantalla del SELECT en MySQL Workbench mostrando los datos guardados.

Lo que construimos hoy

"Ahora entiendes cómo funciona internet por dentro.
Cada app que usas sigue este mismo flujo."

— Principios de Arquitectura Web · Aplicaciones Full Stack

Tecnologías y App Web
PokeAPI · JSON · Express · MySQL
¡A programar!