🎯 Objetivo del tema
El administrador aún no ha insertado un video para esta sección.
🗺️ Mapa del tema
Una de las cosas más poderosas del frontend moderno es que puedes construir apps INCREÍBLES sin tener tu propio backend: consumiendo APIs públicas y gratuitas. Hay miles: clima, mapas, películas, países, personajes, libros, GitHub, Pokémon, rick and morty, chistes, gatos, espacio... La mayoría no requieren autenticación y devuelven JSON. En este tema vas a aprender a consumir 8+ APIs reales, combinarlas en una app de 'directorio mundial' donde cada país tenga su clima, sus personajes de Rick and Morty, y un mapa de su ubicación. Es el patrón del 80% de las apps modernas.
Catálogo completo: clima, mapas, películas, países, personajes, GitHub, Pokémon, libros.
Cómo hacer peticiones HTTP desde el navegador, manejar respuestas, errores, y JSON.
Alternativa a fetch con mejor DX, interceptores, y cancelación.
Cómo manejar los 3 estados, reintentar automáticamente, y cachear respuestas.
Optimización para búsquedas en vivo: esperar 300ms antes de fetchar.
Página de país con clima + mapa + personajes de Rick and Morty según localización.
3.5.1 Las 8 APIs públicas gratuitas más útiles para tu web
El ecosistema de APIs públicas es ENORME. Hay miles de servicios que devuelven datos JSON, muchos sin necesitar autenticación. Te presento las 8 que más usarás en tu vida profesional, todas gratis, todas perfectas para aprender y construir proyectos reales. Con solo estas 8 puedes construir apps de prácticamente cualquier categoría.
| API | URL base | Auth | Datos | Casos de uso |
|---|---|---|---|---|
| 🌤️ Open-Meteo | https://api.open-meteo.com/v1/forecast | No requiere | Clima actual y pronóstico (temperatura, lluvia, viento). | App del clima, dashboards de viaje, widget del clima. |
| 🗺️ OpenStreetMap (Leaflet) | https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png | No requiere | Tiles de mapas del mundo entero. | Mapas interactivos, geolocalización, rutas. |
| 🌍 REST Countries | https://restcountries.com/v3.1 | No requiere | Datos de 250+ países (capital, población, idiomas, banderas). | Enciclopedias, juegos de geografía, dashboards. |
| 🧪 Rick and Morty | https://rickandmortyapi.com/api | No requiere | Personajes, episodios, ubicaciones de la serie. | Wikis, juegos, dashboards de fans. |
| 🎬 OMDB | https://www.omdbapi.com/?apikey=YOUR_KEY | API key (gratis) | Películas y series (título, año, poster, rating). | Catálogos de películas, reseñas, recomendaciones. |
| ⚡ GitHub API | https://api.github.com | No requiere (rate limit) | Usuarios, repos, issues, gists, estrellas. | Portafolios, dashboards de actividad, estadísticas. |
| 🎮 PokeAPI | https://pokeapi.co/api/v2 | No requiere | Datos de 1000+ Pokémon (tipos, stats, evoluciones). | Pokedex, juegos, calculadoras de daño. |
| 📚 Open Library | https://openlibrary.org | No requiere | Datos de millones de libros (título, autor, portada). | Catálogos de bibliotecas, reseñas, recomendaciones. |
El administrador aún no ha insertado un video para esta sección.
3.5.2 fetch + Async/Await: la base del consumo de APIs
fetch es la API nativa del navegador para hacer peticiones HTTP. Combinada con async/await, el código se lee como si fuera síncrono pero NO bloquea el hilo. Es el patrón fundamental del 95% de las apps que consumen APIs. En este bloque vas a ver cómo hacer fetch a la Rick and Morty API, REST Countries, y Open-Meteo, con manejo de errores robusto.
El patrón fetch + async/await
// 1. Fetch básico: GET a la Rick and Morty API
async function getPersonajes(page = 1) {
const response = await fetch(`https://rickandmortyapi.com/api/character?page=${page}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data.results; // array de personajes
}
// 2. Fetch con headers y método POST
async function crearUsuario(usuario) {
const response = await fetch('/api/usuarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(usuario),
});
if (!response.ok) throw new Error('Error al crear');
return await response.json();
}
// 3. Fetch con timeout (fetch no tiene timeout nativo)
async function fetchConTimeout(url, ms = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.json();
} finally {
clearTimeout(timeout);
}
}
// 4. Fetch en paralelo (más rápido)
async function cargarTodo() {
const [personajes, ubicaciones, episodios] = await Promise.all([
fetch('https://rickandmortyapi.com/api/character').then(r => r.json()),
fetch('https://rickandmortyapi.com/api/location').then(r => r.json()),
fetch('https://rickandmortyapi.com/api/episode').then(r => r.json()),
]);
return { personajes, ubicaciones, episodios };
}
// 5. Fetch con cancelación al desmontar (evita memory leaks)
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(err => { if (err.name !== 'AbortError') setError(err); });
return () => controller.abort(); // cleanup
}, [url]);5 patrones de fetch: básico, POST, timeout, paralelo, cancelación
El administrador aún no ha insertado un video para esta sección.
3.5.3 Axios: el rey de los clientes HTTP
Axios es una librería que envuelve fetch con una API más amigable: lanza error automáticamente con respuestas 4xx/5xx (a diferencia de fetch), tiene interceptores para añadir tokens automáticamente, soporta cancelación, y tiene mejor manejo de JSON. Es opcional (fetch es suficiente), pero muchos equipos la prefieren por DX. Pesa solo 14KB gzipped, vale la pena.
fetch vs Axios: el mismo trabajo, dos sintaxis
// Instalar: npm install axios
import axios from 'axios';
// 1. GET con fetch (lo que ya conoces):
const response = await fetch('https://rickandmortyapi.com/api/character');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
// 2. GET con Axios (más conciso, lanza error automáticamente):
const { data } = await axios.get('https://rickandmortyapi.com/api/character');
// data.results es el array de personajes
// 3. POST con Axios:
const { data: nuevoUsuario } = await axios.post('/api/usuarios', { nombre, email });
// 4. Interceptores: añade el token JWT automáticamente a TODAS las peticiones
const api = axios.create({ baseURL: 'https://mi-app.com/api' });
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 5. Interceptores de respuesta: maneja errores globalmente
api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login'; // sesión expirada
}
return Promise.reject(error);
}
);
// 6. Cancelación: equivalente a AbortController de fetch
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const { data } = await axios.get('/api/lenta', { signal: controller.signal });
} catch (err) {
if (axios.isCancel(err)) console.log('Petición cancelada');
}Axios: la alternativa a fetch con mejor DX
El administrador aún no ha insertado un video para esta sección.
3.5.4 Patrones profesionales: loading, error, retry, caché
Hacer fetch es solo el 20% del trabajo. El otro 80% es manejar los 3 estados (cargando, error, éxito), reintentar automáticamente cuando falla, y cachear respuestas para no hacer la misma petición 1000 veces. Estos patrones son los que separan una app amateur de una profesional. Vamos a verlos con un custom hook que combina todo.
useFetch profesional: con retry, caché, y cancelación
import { useState, useEffect, useRef } from 'react';
// Custom hook profesional: useFetch con retry, caché y cancelación
function useFetchAvanzado(url, opciones = {}) {
const {
reintentos = 3, // número de reintentos automáticos
delayReintento = 1000, // ms entre reintentos
cacheMs = 5 * 60 * 1000, // caché de 5 minutos por default
} = opciones;
const [data, setData] = useState(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(null);
const cacheRef = useRef(new Map());
useEffect(() => {
if (!url) return;
const controller = new AbortController();
let intentos = 0;
async function fetchConReintentos() {
// 1. Revisar caché
const cached = cacheRef.current.get(url);
if (cached && Date.now() - cached.timestamp < cacheMs) {
setData(cached.data);
setCargando(false);
return;
}
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
// Guardar en caché
cacheRef.current.set(url, { data: result, timestamp: Date.now() });
setData(result);
setError(null);
} catch (err) {
if (err.name === 'AbortError') return;
if (intentos < reintentos) {
intentos++;
setTimeout(fetchConReintentos, delayReintento);
} else {
setError(err.message);
}
} finally {
if (intentos === 0) setCargando(false);
if (intentos >= reintentos) setCargando(false);
}
}
setCargando(true);
fetchConReintentos();
return () => controller.abort();
}, [url]);
return { data, cargando, error };
}
// Uso:
function DetallePais({ id }) {
const { data, cargando, error } = useFetchAvanzado(
`https://restcountries.com/v3.1/alpha/${id}`,
{ reintentos: 3, cacheMs: 10 * 60 * 1000 } // 10 min de caché
);
if (cargando) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return <h1>{data[0].name.common}</h1>;
}useFetchAvanzado: con retry, caché, y cancelación
El administrador aún no ha insertado un video para esta sección.
3.5.5 Debounce y throttling: optimizando búsquedas en vivo
Si el usuario escribe 'argentina' en un campo de búsqueda, y cada tecla hace un fetch a la API, son 9 peticiones innecesarias. La solución: DEBOUNCE, que espera 300ms después de la última tecla antes de hacer el fetch. Es una de las optimizaciones más importantes y más simples. Vamos a implementarlo con un custom hook.
useDebounce: el hook que TODA búsqueda en vivo necesita
import { useState, useEffect } from 'react';
// Hook: devuelve el valor solo después de 'delay' ms sin cambios
function useDebounce(valor, delay = 300) {
const [debounced, setDebounced] = useState(valor);
useEffect(() => {
const timer = setTimeout(() => setDebounced(valor), delay);
return () => clearTimeout(timer); // cancela si el valor cambia antes del delay
}, [valor, delay]);
return debounced;
}
// Uso: buscador de países en vivo con debounce
function BuscadorPaises() {
const [termino, setTermino] = useState('');
const terminoDebounced = useDebounce(termino, 300); // espera 300ms
const { data, cargando } = useFetch(
terminoDebounced ? `https://restcountries.com/v3.1/name/${terminoDebounced}` : null
);
return (
<div>
<input
value={termino}
onChange={e => setTermino(e.target.value)}
placeholder="Buscar país..."
/>
{cargando && <p>Buscando...</p>}
{data && data.map(p => <p key={p.cca3}>{p.name.common}</p>)}
</div>
);
}
// SIN debounce: 9 fetches por escribir 'argentina'
// CON debounce: 1 fetch (después de 300ms de inactividad)useDebounce: optimiza búsquedas en vivo
El administrador aún no ha insertado un video para esta sección.
3.5.6 Proyecto integrador: 'Atlas Mundial' combinando 4 APIs
El proyecto final del tema: 'Atlas Mundial', una SPA que combina 4 APIs públicas en una sola experiencia. Al hacer clic en un país, verás: su bandera y datos básicos (REST Countries), su clima actual (Open-Meteo), un mapa interactivo con su ubicación (Leaflet + OpenStreetMap), y un personaje de Rick and Morty asignado aleatoriamente (Rick and Morty API). Es el patrón del 80% de las apps modernas: combinar servicios para crear una experiencia unificada.
El componente que combina 4 APIs
import { useState, useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
// Custom hook: useFetch (del Tema 3.2)
function useFetch(url) {
const [data, setData] = useState(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setCargando(true);
fetch(url, { signal: controller.signal })
.then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
.then(setData)
.catch(err => { if (err.name !== 'AbortError') setError(err.message); })
.finally(() => setCargando(false));
return () => controller.abort();
}, [url]);
return { data, cargando, error };
}
// Hook: usePersonajeRickMortyAleatorio
function usePersonajeAleatorio() {
const { data } = useFetch('https://rickandmortyapi.com/api/character/' + Math.floor(Math.random() * 826));
return data;
}
// Página principal: combina 4 APIs
function AtlasMundial() {
const { id } = useParams(); // código cca3 del país
// 1. REST Countries: datos del país
const { data: paises } = useFetch(
`https://restcountries.com/v3.1/alpha/${id}?fields=name,capital,population,flag,languages,latlng,region`
);
// 2. Open-Meteo: clima actual del país
const pais = paises?.[0];
const { data: clima } = useFetch(
pais ? `https://api.open-meteo.com/v1/forecast?latitude=${pais.latlng[0]}&longitude=${pais.latlng[1]}¤t_weather=true` : null
);
// 3. Rick and Morty: personaje aleatorio (para entretenimiento)
const personaje = usePersonajeAleatorio();
if (!pais) return <p>Cargando país...</p>;
return (
<article className="atlas-pais">
<header>
<img src={pais.flag} alt={pais.name.common} className="flag-grande" />
<h1>{pais.name.common}</h1>
<p><strong>Capital:</strong> {pais.capital?.[0]}</p>
<p><strong>Población:</strong> {pais.population.toLocaleString()}</p>
<p><strong>Idiomas:</strong> {Object.values(pais.languages || {}).join(', ')}</p>
</header>
<section className="seccion-clima">
<h2>🌤️ Clima actual</h2>
{clima ? (
<div>
<p className="temperatura">{clima.current_weather.temperature}°C</p>
<p>Viento: {clima.current_weather.windspeed} km/h</p>
<p>Hora: {new Date(clima.current_weather.time).toLocaleString()}</p>
</div>
) : <p>Cargando clima...</p>}
</section>
<section className="seccion-mapa">
<h2>🗺️ Ubicación</h2>
<MapContainer center={pais.latlng} zoom={4} style={{height: '300px'}}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<Marker position={pais.latlng}>
<Popup>{pais.name.common}</Popup>
</Marker>
</MapContainer>
</section>
<section className="seccion-personaje">
<h2>🧪 Personaje aleatorio de Rick and Morty</h2>
{personaje ? (
<div>
<img src={personaje.image} alt={personaje.name} />
<p><strong>{personaje.name}</strong> ({personaje.species})</p>
<p>Estado: {personaje.status}</p>
</div>
) : <p>Cargando personaje...</p>}
</section>
</article>
);
}
// Para usar el mapa, instala:
// npm install react-leaflet leafletAtlas Mundial: 4 APIs combinadas en una sola página
El administrador aún no ha insertado un video para esta sección.
📚 Contenido ampliado
Material adicional, referencias externas verificadas y ejemplos extendidos.
🤖 AI Mission
Las APIs públicas son tus bloques de Lego: combínalas para crear cualquier cosa.
Misión: Crea una app propia que combine al menos 4 APIs públicas de las 8 que vimos. Por ejemplo: una app de 'Viaje Perfecto' que combine REST Countries (destino) + Open-Meteo (clima) + Leaflet (mapa) + OMDB (películas filmas en ese país). Pídele a la IA que sugiera 2 combinaciones creativas. Anota: ¿cuál API te sorprendió más? ¿cuál fue más fácil de integrar?
Pasos sugeridos
- Elige una idea de app que combine 4 APIs (ej: 'Viaje Perfecto', 'Atlas Mundial', 'Cine por país', 'Pokédex geográfico').
- Diseña el layout: qué página muestra qué datos, cómo se navega entre secciones.
- Setup: Vite + React + react-router-dom + react-leaflet + axios (opcional).
- Crea components/ para cada componente: PaisCard, ClimaCard, Mapa, PeliculasList, etc.
- Crea hooks/useFetch.js (con caché y cancelación), hooks/useDebounce.js (para búsquedas).
- Integra las APIs en pages/: una página por cada 'sección' (país, clima, mapa, películas).
- Maneja los 3 estados en cada sección: loading (skeleton), error (mensaje + retry), éxito (datos).
- Estiliza con Tailwind o CSS Modules. Tema oscuro.
- Prueba: verifica que todas las APIs cargan, que los errores se manejan, que el caché funciona.
- Pídele a la IA: 'Tengo esta app con 4 APIs. Sugiere 2 mejoras: features (búsqueda, favoritos, comparador) o robustez (error boundaries, suspense). NO me des código, dime QUÉ agregar y por qué.'
- Aplica 2 mejoras.
- Sube a Vercel. Comparte URL.
- Anota: 3 APIs que aprendiste, 1 que te sorprendió, y qué combinación creativa podrías hacer con otras 4.
📓 Entregable: URL pública de tu app con 4 APIs combinadas, capturas de: cada API cargando datos, manejo de errores, caché funcionando, código de los hooks personalizados, y media página de cuaderno con tu combinación favorita.
🚫 Errores típicos de razonamiento
Por qué: fetch NO rechaza la Promise con 404 o 500. Solo rechaza con errores de red. Si no chequeas response.ok, intentas hacer .json() sobre un HTML de error y explota. Regla: SIEMPRE if (!response.ok) throw new Error(`HTTP ${response.status}`) ANTES del await response.json(). Es el error #1 con APIs.
Por qué: Si el usuario escribe 'argentina' (9 letras), son 9 peticiones innecesarias a la API. Usa DEBOUNCE: espera 300ms después de la última tecla antes de hacer el fetch. Reduce 90% de las peticiones, sin perder UX. Es la optimización más simple y con mayor impacto en apps con búsqueda en vivo.
Por qué: Si el usuario navega a otra página antes de que el fetch termine, React intentará hacer setState en un componente desmontado, mostrando el warning 'Can't perform a React state update on an unmounted component'. Usa SIEMPRE AbortController: return () => controller.abort() en el cleanup del useEffect. Es una línea que evita bugs difíciles de detectar.
Por qué: Si tu código se sube a Git con la API key hardcodeada, cualquiera puede usarla (y abusar de ella, haciéndote gastar tu cuota). SIEMPRE variables de entorno: const API_KEY = import.meta.env.VITE_OMDB_KEY. Y .env en .gitignore. Es un riesgo de seguridad Y financiero si tienes una API de pago.
Por qué: Si tu variable empieza con VITE_, se incluye en el bundle FINAL que se envía al navegador. Si usas import.meta.env.VITE_OMDB_KEY, el usuario puede verla en DevTools > Sources. Para keys SECRETAS (que NO deben verse), usa REACT_APP_ sin el prefijo, o mejor: haz las llamadas con keys en el BACKEND. La regla: keys públicas (deben verse, ej: anon de Supabase): OK en frontend. Keys secretas (nunca deben verse): backend SIEMPRE.
🧪 Laboratorio práctico
Las APIs públicas son tus bloques de Lego. Combínalas para construir cualquier cosa.
Laboratorio: Atlas Mundial: SPA con 4 APIs públicas combinadas
Objetivo: Construir una SPA completa que combine 4 APIs públicas gratuitas en una sola experiencia coherente: REST Countries (datos del país), Open-Meteo (clima actual), Leaflet + OpenStreetMap (mapa interactivo), y Rick and Morty (personaje aleatorio). Practicarás: fetch con caché, useDebounce, manejo de errores, y composición de APIs.
Pasos
- Crea el proyecto: npm create vite@latest atlas-mundial -- --template react.
- Instala: npm install react-router-dom react-leaflet leaflet axios.
- Importa los CSS de Leaflet en main.jsx: import 'leaflet/dist/leaflet.css';.
- Crea hooks/useFetch.js (con caché, retry, cancelación).
- Crea hooks/useDebounce.js (para búsquedas en vivo).
- Crea components/Map.jsx: MapContainer + TileLayer + Marker con latlng del país.
- Crea components/ClimaCard.jsx: usa Open-Meteo con las coordenadas del país.
- Crea components/PersonajeAleatorio.jsx: Rick and Morty API con ID aleatorio.
- Crea pages/Inicio.jsx: lista de países con búsqueda en vivo (useDebounce).
- Crea pages/DetallePais.jsx: integra los 4 componentes (datos, clima, mapa, personaje).
- App.jsx: BrowserRouter con rutas /, /pais/:id, /sobre, *.
- Maneja los 3 estados en cada componente: loading (skeleton), error (mensaje + retry), éxito.
- Estiliza con Tailwind o CSS Modules. Tema oscuro con custom properties.
- Prueba: Argentina → ver bandera, clima (-34.6, -58.4), mapa centrado en Buenos Aires, personaje aleatorio de Rick and Morty.
- Sube a Vercel. Comparte URL.
- Haz commit: 'feat: Atlas Mundial con 4 APIs públicas integradas'.
📓 Entregable: URL pública de Atlas Mundial, capturas de: país con todos los datos, clima actual, mapa interactivo, personaje de Rick and Morty, código de los hooks personalizados, y commit en Git.