HTTP en Serio — Qué Viaja Cuando Llamas a una API
Ya escribes fetch. Ahora entiende qué se manda realmente, por qué falla CORS, y la diferencia entre un 401 y un 403.
Etiquetas
Ya usas HTTP, aunque no lo hayas visto
Llevas semanas escribiendo fetch. Cada vez que lo haces, tu navegador arma un mensaje de texto con un formato muy específico, lo manda por la red, y recibe otro de vuelta. fetch es solo una fachada cómoda sobre ese intercambio.
Hoy abrimos la caja. No es un tema "de backend": es el idioma en el que tu frontend habla con el mundo, y es donde vas a pasar buena parte de tus horas de depuración durante toda tu carrera.
La conversación: petición y respuesta
HTTP funciona por turnos estrictos: el cliente pide, el servidor responde, y la conversación termina. El servidor no puede hablarte por iniciativa propia — esa limitación es justo la que motiva los WebSockets, que verás en el mapa del ecosistema.
Cada mensaje tiene tres partes: la línea inicial (método y ruta, o código de estado), los headers (metadatos) y el cuerpo (los datos, opcional). El primer snippet muestra ambos lados completos: vale la pena que lo leas de arriba abajo.
Y algo que sorprende: HTTP no tiene memoria. Es stateless: cada petición llega sin saber nada de las anteriores. El servidor no "recuerda" que iniciaste sesión — se lo tienes que recordar en cada llamada, normalmente con una cookie o un header Authorization. Todo el tema de autenticación existe para resolver esa amnesia.
Los métodos dicen la intención
GET para leer, POST para crear, PUT/PATCH para modificar, DELETE para borrar. Pero hay dos propiedades que importan más que la lista:
- Seguro: no modifica nada (solo
GET). Por eso el navegador puede pedir unGETde nuevo sin preguntarte, y precargarlo. - Idempotente: repetirlo N veces deja el sistema igual que hacerlo una vez.
POST no es idempotente, y de ahí sale el bug clásico del doble clic en "Pagar": dos cargos. Por eso los formularios deshabilitan el botón mientras envían — el mismo estado enviando que te faltaba en el reto de validación.
Los códigos de estado no son decoración
El código es lo primero que lee cualquier cliente, caché o monitor. Elegirlo mal hace que tu API mienta. Las tres confusiones que verás cada semana:
- 401 vs 403 — "no sé quién eres" contra "sé quién eres y no puedes". El primero se arregla iniciando sesión; el segundo no. Si los confundes, tu app manda al login a alguien que ya inició sesión, en bucle.
- 4xx vs 5xx — de quién es la culpa. Un dato inválido es 4xx (culpa del cliente); una excepción no controlada es 5xx (culpa del servidor). Los clientes bien hechos reintentan los 5xx: si respondes 500 a un formulario mal llenado, provocas reintentos infinitos.
- 200 con error adentro — el pecado capital. Rompe caché, monitoreo y reintentos, y obliga a escribir código defensivo en cada llamada.
Recuerda de la Auditoría IA #2:
fetchno rechaza ante un 404 o un 500 — solo ante fallos de red. Por eso siempre revisasresponse.okantes del.json().
Headers: los que sí vas a usar
De los cientos que existen, este puñado cubre tu semestre:
| Header | Para qué |
|---|---|
Content-Type | Qué formato mandas (application/json) |
Accept | Qué formato quieres recibir |
Authorization | Quién eres (Bearer <token>) |
Cache-Control | Cuánto se puede guardar la respuesta |
Location | Dónde quedó lo que acabas de crear (con 201) |
CORS: el error que todos sufren
El día que tu fetch truene con "blocked by CORS policy", acuérdate de esto: el bloqueo lo hace tu navegador, no el servidor. La petición salió y la respuesta llegó; el navegador simplemente no te deja leerla porque el servidor no autorizó tu origen.
De ahí las tres consecuencias del cuarto snippet, y sobre todo esta: CORS se arregla en el servidor. No hay opción de fetch, ni truco, ni librería del lado del cliente que lo resuelva. Si la API es ajena y no te autoriza, necesitas un proxy propio.
Caché: lo que hace rápida a la web
Una respuesta que no se pide es infinitamente más rápida que una optimizada. Con Cache-Control el servidor dice cuánto tiempo puede guardarse algo; con 304 Not Modified responde "no cambió, usa tu copia" sin reenviar el contenido.
Esto conecta con tu deploy: los sitios estáticos (como esta plataforma) son rápidos precisamente porque casi todo se cachea. Y es la razón de que GET sea cacheable y POST no — otra ventaja que se pierde cuando una API manda todo por POST.
HTTPS, en una línea
HTTPS es HTTP dentro de un túnel cifrado. Sin él, cualquiera en la misma red Wi-Fi puede leer los datos que viajan, incluidos tokens y contraseñas. Hoy es obligatorio: los navegadores marcan como "no seguro" cualquier sitio sin él, y varias APIs del navegador (cámara, ubicación) simplemente no funcionan en HTTP. Tu deploy ya lo trae; no lo desactives.
Lo que te llevas
Cuando algo falle —y va a fallar— tu primer reflejo ya no debería ser cambiar líneas al azar. Abre Network, mira el método, el código de estado y los headers, y en 30 segundos sabrás si el problema es tuyo, del servidor o del navegador. Esa lectura es la diferencia entre depurar y adivinar.
Ejemplos de Código
4 ejemplos
Anatomía de una petición y su respuesta
Esto es lo que tu fetch construye por ti.
1POST /api/productos HTTP/1.1 ← método, ruta, versión
2Host: api.mitienda.com
3Content-Type: application/json ← qué formato estoy MANDANDO
4Accept: application/json ← qué formato quiero RECIBIR
5Authorization: Bearer eyJhbGci... ← quién soy
6
7{"nombre":"Teclado","precio":450} ← el cuerpo
8
9─────────────────────────────────────
10
11HTTP/1.1 201 Created ← código de estado
12Content-Type: application/json
13Location: /api/productos/42 ← dónde quedó lo que creaste
14Cache-Control: no-store
15
16{"id":42,"nombre":"Teclado","precio":450}Los códigos que vas a usar de verdad
No memorices los 60; domina estos.
12xx — salió bien
2 200 OK respuesta normal con cuerpo
3 201 Created creaste algo (devuelve Location)
4 204 No Content salió bien y no hay nada que devolver (DELETE)
5
63xx — redirección
7 301 Moved Permanently cambió de dirección para siempre
8 304 Not Modified úsalo de tu caché, no cambió
9
104xx — el cliente se equivocó
11 400 Bad Request petición malformada
12 401 Unauthorized NO SÉ QUIÉN ERES → manda al login
13 403 Forbidden sé quién eres, NO PUEDES → el login no ayuda
14 404 Not Found no existe
15 409 Conflict choca con el estado actual (correo duplicado)
16 422 Unprocessable sintaxis ok, datos inválidos
17 429 Too Many Requests bájale al ritmo
18
195xx — el servidor se rompió
20 500 Internal Error bug del lado del servidor
21 502 Bad Gateway el proxy no obtuvo respuesta
22 503 Unavailable caído o saturado (temporal)Métodos, seguridad e idempotencia
Idempotente = repetirlo 10 veces deja el mismo resultado que hacerlo 1 vez.
1Método ¿Modifica? ¿Idempotente? Uso típico
2───────────────────────────────────────────────────────────
3GET no sí leer (cacheable)
4POST sí NO crear (repetirlo DUPLICA)
5PUT sí sí reemplazar completo
6PATCH sí depende modificar parcial
7DELETE sí sí eliminar
8
9Por qué importa: si una petición falla por red, un cliente puede
10reintentarla sin miedo SOLO si es idempotente. Por eso el doble clic
11en "Pagar" (POST) genera dos cargos y el doble clic en "Guardar" (PUT)
12no genera dos registros.CORS — por qué falla en el navegador y no en Postman
El bloqueo lo hace TU navegador, no el servidor.
1Tu app en http://localhost:3000
2llama a https://api.otrodominio.com/datos
3
4→ El navegador aplica la política del mismo origen y BLOQUEA la
5 respuesta, salvo que el servidor autorice tu origen:
6
7 Access-Control-Allow-Origin: http://localhost:3000
8
9Consecuencias prácticas:
10• Postman y curl NO tienen esta restricción → "en Postman sí funciona"
11 no significa que tu código esté mal.
12• CORS se arregla en el SERVIDOR, no en tu fetch. Ninguna opción de
13 fetch lo desactiva.
14• Antes de peticiones "no simples" el navegador manda un OPTIONS
15 (preflight): verás DOS peticiones en Network. Es normal.Recursos
4 recursos disponibles
¡Hora de Practicar!
Práctica Guiada — Autopsia de una petición
Abre la pestaña Network y mira lo que llevas semanas mandando a ciegas.
Con las DevTools abiertas en la pestaña Network, sobre tu proyecto o cualquier sitio:
- Recarga la página y observa la lista de peticiones. Identifica el documento HTML, el CSS, el JS y las imágenes.
- Haz clic en una petición y localiza: método, código de estado, Request Headers y Response Headers.
- Encuentra el
Content-Typede la respuesta. ¿Coincide con lo que esperabas? - Filtra por Fetch/XHR y dispara una petición de tu app. Copia su
curl(clic derecho → Copy as cURL) y córrelo en tu terminal. - Provoca un error a propósito: cambia la URL de tu fetch a una ruta inexistente. Compara qué ves en Network contra qué ve el usuario en pantalla.
- En Size, busca la petición más pesada de tu sitio. ¿Es una imagen? ¿Cuánto pesa?
Entrega: captura de la petición más pesada y una tabla con las 5 peticiones de tu app (método, ruta, estado, tamaño).
Desafío de Código
Ejercicios — ¿Qué código de estado responderías?
Elegir el código correcto es diseñar una API que no miente.
Para cada escenario, di qué código de estado debería devolver el servidor y por qué:
- El usuario pide
/api/productos/9999y ese producto no existe. - El usuario pide
/api/mi-perfilsin haber iniciado sesión. - El usuario sí inició sesión, pero pide
/api/admin/usuariosy no es administrador. - Se crea un producto nuevo correctamente.
- El usuario manda el formulario de registro con el correo vacío.
- La base de datos está caída.
- El usuario intenta registrarse con un correo que ya existe.
- Se elimina un producto y no hay nada que devolver.
Después responde: ¿por qué es un problema que una API responda 200 con un mensaje de error en el cuerpo?
Reto de Lectura
Reto de Lectura — La API que miente
Una API real puede ser sintácticamente correcta y semánticamente un desastre. Diagnostica seis decisiones que le harán la vida imposible a quien la consuma.
Te contrataron para consumir esta API desde el frontend. Antes de escribir una línea, audita el contrato: encuentra los 6 problemas y explica qué le rompen a quien la usa.
POST /api/obtenerProductos
→ 200 OK
{ "error": false, "data": [ ... ] }
POST /api/obtenerProducto?id=9999
→ 200 OK
{ "error": true, "mensaje": "Producto no encontrado", "data": null }
POST /api/borrarProducto
→ 200 OK
{ "error": false }
POST /api/miPerfil (sin haber iniciado sesión)
→ 404 Not Found
{ "error": true, "mensaje": "No autorizado" }
POST /api/crearProducto (nombre vacío)
→ 500 Internal Server Error
{ "error": true, "mensaje": "Error" }
Response Headers de todas:
Content-Type: text/html
Preguntas:
- Todo es
POST. ¿Qué se pierde? Piensa en caché, en el botón atrás y en compartir un link. - El "no encontrado" responde
200. ¿Cómo obliga eso a escribir el código del frontend? - El perfil sin sesión responde
404diciendo "No autorizado". ¿Qué código correspondía y por qué importa la diferencia? - La validación fallida responde
500. ¿De quién es la culpa según ese código, y de quién era realmente? Content-Type: text/htmldevolviendo JSON. ¿Qué se rompe?- Las rutas se llaman
/obtenerProductos,/borrarProducto. ¿Cómo se llamarían en REST?
Documentación Oficial
Documentación de apoyo
Referencias para consultar durante el curso.
Clase 26 de 31 en la ruta Desarrollo movil
Clase 16 de 33 en la ruta Desarrollo web