Apéndice I — I. Preguntas de entrevista: frontend, tiempo real, seguridad y operación
La mitad «full» del fullstack — y lo que separa funciona de publicable
Repaso activo: responde en voz alta antes de desplegar. Si una te cuesta, la referencia → cap. X te dice qué releer.
I.1 Frontend y tiempo real
R: “El clásico — respóndelo en capas: el navegador resuelve el dominio vía DNS → abre conexión TCP → negocia TLS (certificados, el candado) → manda el HTTP request → el servidor (reverse proxy → app → BD) responde HTML → el navegador parsea el DOM, pide CSS/JS, ejecuta el JS que quizá pide más datos vía fetch a la API. Si me piden profundidad, bajo a cualquier capa — el libro entero vive en esa cadena.” → nu-01, fe-01
R: “HTTP es request/response: el cliente pregunta, el servidor responde, se cierra. WebSocket abre un canal persistente y bidireccional: el servidor puede empujar datos sin que el cliente pregunte. Necesario cuando el servidor sabe algo que el cliente quiere ya — presencia, colaboración en vivo, notificaciones. Para ‘cada tanto’ alcanza polling/SSE; WS se justifica cuando la latencia y el push son el producto — en Nexus, dos cursores viéndose en <300ms.” → cap-38
R: “Conexión WebSocket autenticada (el upgrade lleva la misma cookie httpOnly — la auth aplica). El servidor mantiene un mapa documento→conexiones; cada edición hace broadcast a los demás del documento. Presencia: heartbeats para saber quién está. Conflictos: last-writer-wins es el pragmático — CRDT/OT solo cuando el merge fino por carácter es requisito, que es otro proyecto de complejidad.” → cap-38
R: “La representación del documento HTML como árbol de objetos que JavaScript puede leer y mutar — document.querySelector, crear nodos, escuchar eventos. El browser renderiza el DOM, no el HTML: por eso modificar el árbol cambia la pantalla sin recargar. React existe porque mutar el DOM a mano se desordena con estado complejo.” → fe-03
R: “useState guarda estado por componente y re-renderiza al cambiarlo: la UI es función del estado, no comandos de DOM. useEffect corre efectos tras el render — fetch, suscripciones — con un array de dependencias que dice cuándo re-ejecutarse y una función de limpieza para cancelar (AbortController, unsubscribe). El error clásico: fetch en useEffect sin cleanup → race conditions cuando el usuario navega rápido.” → fe-03, fe-05
R: “Separando dos tipos: server state (las tareas viven en la BD — el frontend solo cachea y muestra: TanStack Query/SWR manejan caché, refetch, loading) y client state (qué modal está abierto, el tema — useState/context). El error de novato es tratar el server state como si fuera tuyo: copiarlo a un store global y mantenerlo a mano. La fuente de verdad es el servidor; tu UI es una vista cacheada.” → fe-05
R: “Un evento sube por el árbol: click en un botón dispara handlers del botón, luego del div padre, hasta document. Por eso stopPropagation() existe — y por eso funciona la delegación: UN listener en la lista padre maneja clicks de mil hijos leyendo event.target. La captura es el viaje inverso (de afuera hacia adentro, antes del bubbling).” → fe-03
R: “CSR: el servidor manda un HTML casi vacío + JS que construye todo en el navegador — primera pintura lenta, interacción rica después. SSR: el servidor devuelve HTML completo ya renderizado — first paint rápido, SEO real, y luego el JS ‘hidrata’ para la interactividad. Importa para SEO y tiempo-de-primera-vista; apps detrás de login (dashboards) suelen bastar con CSR, páginas públicas piden SSR.” → fe-06
R: “El form manda POST /auth/login → el servidor verifica bcrypt → firma un JWT y lo devuelve en cookie httpOnly+Secure+SameSite → el navegador la adjunta sola en cada request → el middleware del servidor verifica firma, carga el usuario, y decide 401 o sigue → el frontend pregunta GET /me para saber si hay sesión (nunca lee el token — httpOnly lo impide a propósito). Logout = cookie borrada + sesión revocada en la tabla.” → cap-14..16
R: “Política del navegador: una página del origen A no puede leer respuestas del origen B sin permiso explícito del servidor. Para requests ‘no simples’ el navegador manda primero un OPTIONS (preflight) preguntando; el servidor responde Access-Control-Allow-Origin: <origen exacto> (+ -Methods, -Headers, -Credentials si hay cookies — que prohiben el *). Es el navegador el que enforcea: curl no pide permiso — CORS protege al usuario, no a tu API.” → cap-18
I.2 Seguridad y operación
R: “Por capas, como el libro entero: entrada — validación por schema y queries parametrizadas (cero concatenación → cero inyección SQL). Identidad — bcrypt para claves, JWT en cookie httpOnly, sesiones revocables con rotación. Permisos — ownership en la query, 404 a lo ajeno. Superficie — rate limit en /auth, CORS con orígenes explícitos, HTTPS siempre. Secretos — env vars/secret manager, nunca en el repo. Errores — el 500 genérico afuera, el stack trace solo en logs.” → caps. 4, 6, 14–18, 26
R: “No es o/es — el diseño serio es híbrido. Access JWT stateless (15 min): el servidor solo verifica firma, escala sin consultar BD — pero no se revoca. Refresh opaco persistido (30 días, tabla sessions): una consulta cada 15 min a cambio de revocación real — ‘cerrar sesión en todos los dispositivos’ existe. Rotación del refresh: cada uso emite uno nuevo; un viejo reutilizado = robo detectado → sesión entera fuera.” → cap-15, cap-18
R: “No se guardan — se guardan verificadores. bcrypt (o argon2) con salt aleatorio por usuario y costo ~10-12: lento a propósito, ~100ms por intento — imperceptible en login, una eternidad en fuerza bruta. Nunca cifrado (la clave que descifra también se filtra) y nunca MD5/SHA-256 (rápidos = regalo para GPUs). El login hashea lo que escribió el usuario y compara; el error es el mismo para ‘no existe’ y ‘clave mala’.” → cap-14
R: “Concatenar datos del usuario en la query: WHERE email = '" + email + "' — si escribe ' OR '1'='1 la condición se vuelve verdadera para toda la tabla. Se previene con consultas parametrizadas: $1/%s envían el dato por un canal separado — la BD lo trata como texto, jamás como código. Regla sin excepciones, incluidos ‘valores que yo controlo’.” → cap-09
R: “XSS: JS malicioso corriendo en tu página (un comentario que renderiza <script>) — roba lo que JS puede tocar. Defensa: sanitizar entrada, no innerHTML con datos de usuario, y cookies httpOnly (el XSS no puede leerlas). CSRF: un sitio maligno hace que el navegador logueado del usuario mande requests a tu API — porque la cookie viaja sola. Defensa: SameSite=lax/strict + origen verificado. Son ataques gemelos: uno abusa del script en tu página, el otro de la cookie automática.” → cap-15, cap-26
R: “Un techo de requests por ventana — responde 429 + Retry-After cuando se pasa. Lo pones donde cada intento te cuesta o te revela: /auth/login (fuerza bruta de claves), /auth/refresh, register, forgot-password (enumeración de emails), códigos OTP, y endpoints caros (exports, reportes). Por IP y por cuenta — el botnet tiene mil IPs pero la cuenta objetivo es una.” → cap-18
R: “La pregunta real de entrevista. Orden: (1) ¿qué cambió? — deploy reciente, migración, crecimiento de datos. (2) Medir, no adivinar: logs/APM dicen qué endpoint; EXPLAIN ANALYZE dice qué hace la query — busco Seq Scan nuevo (¿se perdió un índice?), N+1 (¿un loop nuevo?), rows escaneadas explotando. (3) CPU alto + queries iguales → carga o lock contention. (4) Fix y verificación con la misma medición. Lo que nunca hago: tirarle más máquina a un problema que no medí.” → cap-12, cap-23
R: “Configuración que vive fuera del código: DATABASE_URL, JWT_SECRET, PORT. Por dos razones: el mismo artefacto corre en dev, staging y prod (la diferencia es el entorno, no el binario), y los secretos no tocan el repo — el código se publica, las env vars no. En producción viven en un secret manager con rotación y auditoría, no en un .env del servidor.” → cap-24, nu-04
R: “La imagen es la receta inmutable (capas: SO base + runtime + deps + tu código); el contenedor es la ejecución viva de una imagen — como clase vs instancia. Sirve porque resuelve ‘en mi máquina sí funcionaba’: el mismo artefacto corre idéntico en dev, CI y prod. El Dockerfile declara la receta; docker-compose orquesta app+BD locales para que el onboarding sea un comando.” → cap-30..32
R: “Pirámide honesta: muchos tests de unidad en la capa de servicio (la regla de negocio pura — ‘no borrar tarea con subtareas’), tests de integración contra BD real en los repositorios, y pocos end-to-end por los flujos críticos (login→crear→listar). Cobertura donde importa: la frontera de errores y la autorización, no getters. El test como contrato: si rompe la API pública, el test falla antes que el cliente.” → cap-27..29
I.3 Vocabulario técnico del capítulo
Una variable es una caja con nombre donde guardas un valor: let total = 42 guarda el 42 bajo el nombre total. Puedes leerla y cambiarla después (total = 50). const = caja que no se puede reemplazar.
Un array es una colección ordenada de elementos accedidos por posición: ["a","b","c"][0] es "a" (se cuenta desde 0). Python las llama listas, Go slices — misma idea, distinto acento.
Un objeto es una colección de datos con nombre: {name: "Ana", age: 30} — cada dato es una propiedad (clave → valor). Python los llama dict, Go los arma con struct, TS con objetos/interface.
Un bucle repite una acción por cada elemento o hasta cumplir una condición: for task in tasks hace algo con cada tarea. map y filter son bucles disfrazados de funciones — transforman/filtran colecciones sin for explícito.
Una clase es el molde de un objeto: define qué datos y qué métodos tiene. class Task es el molde; new Task() es una instancia concreta. Go no tiene clases — usa struct + métodos sueltos.
return hace dos cosas a la vez: devuelve el resultado Y termina la función — lo que esté debajo nunca corre. return task = “aquí está el plato, salgo de la cocina”.
Una migración es un cambio versionado del esquema: un archivo que dice “crea esta columna” (up) y “bórrala” (down). Son el historial Git de la estructura de la BD — se aplican en orden y no se editan una vez aplicadas.
La autenticación responde “¿quién eres?” — login, contraseña, token. Se confunde con autorización (“¿qué puedes hacer?”), que es la pregunta siguiente. Primero te identificas, luego te dejan o no pasar.
La autorización responde “¿qué puedes hacer?”: eres usuario válido (autenticado), pero ¿puedes borrar esta tarea? Se decide por rol o por ownership — y se verifica en cada request, no se recuerda.
Una sesión es la conversación continuada entre tú y el servidor: HTTP no recuerda nada entre requests, así que la sesión (vía cookie o token) es el “pulso de mano” que te identifica en cada llamada.
El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.
Un entorno es una instancia completa donde corre tu app con su propia config y datos: local (tu máquina), staging (réplica de prueba), producción (la real). Cada entorno tiene sus propias llaves y su propia base de datos.
El DOM es la página como árbol de objetos vivos: HTML es el papel, el DOM es lo que JavaScript puede tocar. element.textContent = "hola" cambia el DOM y el navegador repinta — el HTML original no se entera.
Una dependencia es código de terceros que tu proyecto usa: npm install, pip install, go get las traen. Cada una es deuda — ahora funciona, pero hay que mantenerla, actualizarla y confiar en ella.
Un repositorio (repo) es la carpeta del proyecto con todo su historial Git adentro. GitHub/GitLab son servicios que hospedan repos para compartirlos y respaldarlos.
El CPU es el cerebro que ejecuta instrucciones; cada núcleo puede ejecutar una cosa a la vez (por eso importan la concurrencia y los procesos paralelos). “CPU-bound” = el límite es el cálculo, no la espera.
Una base de datos es el programa que guarda datos de forma permanente y los responde rápido. Relacional (Postgres): tablas con relaciones. No relacional: documentos, clave-valor, grafos — cada una para una forma de dato distinta.
Una query es la pregunta que le haces a la base de datos en SQL: SELECT * FROM tasks WHERE done = false. La BD traduce la pregunta a un plan de búsqueda — por eso los índices importan.
El esquema es el plano de la base de datos: qué tablas hay, qué columnas tiene cada una y de qué tipo. Es contrato: una fila que no cumple el esquema no entra. Se cambia con migraciones, no a mano.
Un índice es la tabla de contenido de una tabla: sin él, buscar un usuario es leer las 10 millones de filas (seq scan); con él, es ir directo a la página. Se crea según cómo se consulta, y cada uno cuesta escrituras.