Apéndice H — H. Preguntas de entrevista: diseño, arquitectura y bases de datos

Las que separan ‘sé hacer endpoints’ de ‘sé diseñar sistemas’

NotaCómo usar esto

Repaso activo: lee la pregunta, respóndela en voz alta, después despliega la respuesta modelo. Las respuestas son de largo “hablado” — en una entrevista nadie quiere un ensayo, quieren oír cómo piensas. Si una te cuesta, la referencia te dice qué capítulo releer.

H.1 Diseño y arquitectura

R: “Empiezo por el dominio: entidades y relaciones en un diagrama ER antes de cualquier código. Luego el contrato — rutas, schemas de entrada/salida, códigos de error — porque el frontend y el backend pueden construirse en paralelo contra ese contrato. Arquitectura por capas: rutas traducen HTTP, servicios deciden, storage persiste. Primero lo que demuestra el riesgo: en Nexus fue el versionado de documentos, no el login.” El entrevistador busca: orden (dominio→contrato→capas), no “instalo React”. → cap-36

R: “Una API donde los recursos tienen URL (/tasks/42), los verbos HTTP llevan la acción (GET lee, POST crea, DELETE borra), el servidor es stateless — cada request trae todo lo necesario — y los status codes son parte del contrato (201, 422, 404). Se usa porque es simple, cacheable y universal: cualquier cliente HTTP lo habla.” → cap-03

R: “Contrato primero: recursos en plural y minúscula, verbos que respetan semántica (GET nunca muta), errores estructurados con code estable + message, paginación por cursor si la lista crece, versionado en la URL o headers cuando el contrato tenga que cambiar. El schema de entrada y de salida se definen una sola vez — y la salida filtra lo interno (password_hash jamás sale).” → cap-03, 04, 05

R: “REST cuando el dominio es recursos claros y quieres caché HTTP y simplicidad — el default. GraphQL cuando el cliente necesita seleccionar campos o componer muchas entidades en un request (apps con vistas muy distintas) — pagas un runtime de queries y caché más difícil. RPC/actions cuando el dominio no es recursos sino operaciones (/tasks/complete). En Nexus elegí REST: el dominio son documentos y miembros.” → cap-03

R: “Separación por responsabilidad: la capa HTTP traduce requests, el servicio decide reglas de negocio sin saber de HTTP, el storage habla SQL sin saber de negocio. Las dependencias apuntan hacia adentro — el servicio no importa Express. Lo compras: cada capa se puede testear y cambiar sola. Lo pagas: más archivos para un CRUD simple.” → cap-07

R: “En vez de que el componente cree sus dependencias, se las dan. En FastAPI Depends(get_current_user) declara la dependencia en la firma y el framework la resuelve. Sirve para dos cosas: el handler no sabe cómo se construye lo que usa (puedo cambiar la implementación), y en tests puedo inyectar una versión falsa — una sesión de mentira, una BD en memoria.” → cap-16

R: “El clásico de system design junior. Modelo: links(id, slug, long_url, user_id, created_at). Redirect: GET /{slug} → 301 a la URL — un index único en slug hace el lookup O(log n). Generación del slug: base62 del id o hash corto con reintento en colisión. Escala: lecturas » escritas, así que caché caliente (Redis) delante y la BD aguanta. La pregunta de seguimiento siempre es: ¿qué pasa cuando el slug colisiona y cómo lo resuelves?”

R: “Porque el código dice qué y el ADR dice por qué. En seis meses nadie recuerda por qué se eligió snapshot+diff y no CRDT — el ADR captura el contexto, las alternativas descartadas y el costo aceptado. Corta: media página por decisión. En Nexus hay tres: capas, modelo de versionado, y last-writer-wins.” → cap-36

H.2 Bases de datos

R: “Una estructura aparte (B-tree) que mantiene columnas ordenadas con punteros a las filas — como el índice de un libro: buscas sin leer todo. Puede ‘enlentecer’ porque cada INSERT/UPDATE/DELETE tiene que actualizar también el índice — los índices aceleran lecturas y cobran escrituras. Y si tu query devuelve media tabla, Postgres ignora el índice porque el seq scan es honestamente más barato.” → cap-12

R: “Las cuatro promesas de una transacción. Atomicidad: todo o nada — el INSERT y el UPDATE van juntos o ninguno. Consistencia: la BD nunca queda violando sus constraints. Aislamiento: transacciones concurrentes no se ven a medias. Durabilidad: lo confirmado sobrevive al crash — está en disco. El ejemplo universal: transferir dinero — debitar sin acreditar no puede pasar.” → cap-13

R: “Un grupo de operaciones que se confirman como una unidad: BEGIN, las queries en UNA conexión, COMMIT o ROLLBACK. La necesitas cuando varios writes son un solo hecho de negocio — crear tarea + registrar evento + descontar crédito. La trampa: la transacción vive en una conexión del pool — si usas pool.query() por query, cada una viaja por cable distinto y no hay transacción.” → cap-13

R: “Una query para la lista + una query por cada elemento: 20 tareas = 21 round trips. Invisible en dev, asesino en prod — y los ORMs lo disfrazan de código limpio (task.tags dispara una query). La cura: una sola query con JOIN, o WHERE task_id IN (...), agrupando en código. Detector: loguea queries en dev — un request que dispara N queries es la alarma.” → cap-12

R: “Medir antes de tocar: EXPLAIN ANALYZE muestra el plan real — busco Seq Scan en tablas grandes (falta índice), Rows Removed by Filter altos (leyó millones para entregar veinte), y estimados que no coinciden con realidad (estadísticas viejas → ANALYZE). Arreglo típico: índice compuesto con las columnas de igualdad primero y la de ORDER BY al final. Luego verifico que el plan cambió — el índice que Postgres ignora no existe.” → cap-12

R: “Seq scan lee la tabla entera — correcto cuando la tabla es pequeña o la query devuelve gran parte de ella. Index scan baja por el árbol a las filas exactas — correcto cuando filtras poco de mucho. Un seq scan en EXPLAIN no es automáticamente un bug; lo es cuando la tabla creció y el plan no cambió.” → cap-12

R: “Un índice sobre varias columnas: (user_id, done, created_at). El orden importa porque el árbol está ordenado por la primera, luego la segunda dentro de cada valor — regla práctica: columnas de igualdad primero, rango/orden al final. WHERE user_id=? AND done=? ORDER BY created_at usa el índice completo; WHERE done=? solo, no — el índice no ‘empieza’ por ahí.” → cap-12

R: “Expand-contract en migraciones: agregar lo nuevo (columna, tabla) sin tocar lo viejo → el código escribe en ambos → backfill de datos → deploy que solo usa lo nuevo → última migración retira lo viejo. Cada paso es seguro solo. Reglas: ADD COLUMN es barato (Postgres 11+: instantáneo con default), ALTER TYPE/DROP bloquean → se hacen por pasos; índices en tablas grandes con CREATE INDEX CONCURRENTLY.” → cap-11

R: “Un conjunto de conexiones abiertas a la BD que se prestan por request en vez de abrirse y cerrarse — abrir una conexión cuesta handshake+auth+proceso nuevo (~50ms). Con max: 10 atiendes cientos de usuarios porque cada query dura milisegundos y devuelve su conexión. Si el pool se agota, los requests esperan en cola — por eso olvidar release()/close() cuelga la app a los minutos.” → cap-10

R: “El modelo de Nexus — como git interno: tabla revisions(document_id, rev, snapshot, created_at) y el documento guarda current_revision como puntero. Undo = mover el puntero atrás; redo = adelante; editar tras undo = truncar el ‘futuro’ — exactamente git commit. Snapshot completo por revisión (no solo diffs) porque el restore es O(1): lees un estado, no re-ejecutas la historia.” → cap-37

R: “En Nexus elegí PostgreSQL aunque los documentos son JSON: (a) la mayoría del dominio es relacional — miembros, proyectos, permisos con FKs que la BD enforza; (b) JSONB guarda el contenido del documento sin renunciar a lo relacional — es ‘lo mejor de los dos’ con índices GIN si hace falta; (c) las revisiones necesitan transacciones fuertes. NoSQL habría sido pereza disfrazada de flexibilidad.” → cap-08, cap-37

H.3 Vocabulario técnico del capítulo

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.

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.

Una transacción es un grupo de operaciones que se confirman juntas o no se confirma ninguna: transferir dinero = restar de A y sumar a B. Si falla a la mitad sin transacción, el dinero desapareció.

Un pool es el staff de conexiones a la BD: abrir una conexión por request es caro, así que el pool mantiene ~10–20 abiertas y las presta. El request la usa, la devuelve, y la siguiente la reutiliza.

Un JOIN combina tablas por su relación: “cada tarea con el nombre de su proyecto”. Es la superpotencia relacional — en una query traes lo que en NoSQL serían varios viajes.

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.

Un hash es una función de un solo sentido: contraseña →$2b\(10\)…`. No se puede revertir — por eso las contraseñas se hashean, no se cifran. bcrypt es lento a propósito: fuerza bruta cara para el atacante.

Una firma (HMAC) prueba que un mensaje es auténtico y no fue tocado: se calcula con un secreto sobre el contenido. Los webhooks la usan para que verifiques “esto realmente vino de Stripe”.

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 framework es un esqueleto de aplicación ya decidido: te da la estructura (rutas, validación, errores) y tú llenas la lógica. Diferencia con librería: la librería la llamas tú; el framework te llama a ti.

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 commit es una foto guardada del código con un mensaje que dice por qué cambió. La historia del proyecto es una cadena de commits: puedes volver a cualquiera, ver qué cambió y quién lo hizo.

Un proceso es un programa en ejecución: el sistema operativo le da memoria propia y un lugar en la fila del CPU. Tu API es un proceso; Postgres es otro; el navegador es varios. Que “se caiga el proceso” = el programa murió.

La memoria RAM es el escritorio de trabajo del programa: rápida, pero se borra al apagar. Los datos que deben sobrevivir van a disco o a la base de datos. Por eso let tasks = [] pierde todo al reiniciar.

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.