🎯 Meta del capítulo: dejar de ser alguien que escribe código para convertirte en alguien que resuelve problemas con código. La diferencia no es cuántos frameworks conoces: es cómo decides, qué preguntas haces antes de teclear y qué eres capaz de justificar seis meses después.
🧭 Dónde estás. Hasta aquí has aprendido el cómo: cómo escribir una API, cómo modelar datos, cómo desplegar. Esta parte trata el qué y el por qué. Es la parte que separa a un programador de un ingeniero, y también la que casi ningún tutorial enseña — porque no se puede reducir a un
npm install.
23.1 · La diferencia real entre programar y hacer ingeniería
PROGRAMAR HACER INGENIERÍA
───────────────────────── ────────────────────────────────────
"¿Cómo hago que funcione?" → "¿Qué debería construir, y por qué?"
Optimiza: que compile → Optimiza: coste total a lo largo del tiempo
Horizonte: hoy → Horizonte: 3 años y 5 personas más
Éxito: pasa los tests → Éxito: el equipo puede cambiarlo sin miedo
Una solución → Varias opciones y un motivo para elegir🧠 La definición que mejor lo captura (Titus Winters, Google): "la ingeniería del software es programación integrada en el tiempo". Un programa se escribe una vez. Un sistema de software se lee, modifica, depura y despliega cientos de veces por gente que no eres tú. Casi todas las decisiones que parecen malas en un tutorial (más abstracción, más tests, más documentación) son apuestas contra el paso del tiempo.
El corolario incómodo: el código que escribes hoy será leído entre 10 y 100 veces más de lo que fue escrito. Optimizar para "escribirlo rápido" es optimizar la variable equivocada.
23.2 · Todo es un trade-off (no existe "lo mejor")
La pregunta "¿cuál es el mejor lenguaje / base de datos / arquitectura?" no tiene respuesta, igual que "¿cuál es el mejor vehículo?". Depende de si vas a mudarte, a competir o a aparcar en el centro.
Los ejes en los que siempre te mueves:
| Ganas | Pierdes | Ejemplo del libro |
|---|---|---|
| Velocidad de desarrollo | Control y rendimiento | Laravel (cap. 3) vs Go (cap. 8) |
| Rendimiento | Simplicidad | Caché (ap. B) → ahora hay invalidación |
| Consistencia | Disponibilidad | Transacción SQL vs cola asíncrona |
| Flexibilidad | Garantías | JSONB (cap. 2) vs columnas tipadas |
| Independencia de equipos | Complejidad operativa | Microservicios vs monolito (cap. 11) |
| Seguridad de tipos | Verbosidad | TypeScript (ap. A) vs JS |
| Menos código propio | Dependencia externa | Stripe (ap. P) vs pasarela propia |
⚠️ Señal de alarma en una entrevista o en una reunión: alguien afirma que una tecnología es "mejor" sin mencionar qué se pierde. O no la conoce lo suficiente, o está vendiendo algo. La respuesta de ingeniero siempre tiene la forma: "depende de X; si X entonces A, porque ganamos Y a cambio de Z".
Cómo se decide de verdad: el método de las tres columnas
Cuando tengas que elegir entre opciones, escríbelo — literalmente, en una tabla:
Decisión: ¿cómo enviamos los emails de la aplicación?
│ Síncrono en el │ Cola + worker │ Servicio externo
│ propio request │ (Redis/RabbitMQ) │ (Resend, SES)
──────────────┼──────────────────┼──────────────────┼──────────────────
Complejidad │ Ninguna │ Alta (infra +) │ Baja
Latencia user │ ❌ +2s por email │ ✅ ~0 │ ✅ ~0 (si async)
Fiabilidad │ ❌ se pierde │ ✅ reintentos │ ✅ del proveedor
Coste € │ 0 │ servidor Redis │ ~1€/1000 emails
Entregabilidad│ ❌ acabas en spam│ ❌ igual │ ✅ IPs reputadas
Reversible │ Sí │ Costoso │ Sí
──────────────┴──────────────────┴──────────────────┴──────────────────
Decisión: servicio externo AHORA (entregabilidad es el problema real,
no la latencia). Cola cuando superemos los 50k emails/mes.💡 El acto de escribir la tabla es el 80% del valor. Al forzarte a listar los criterios descubres cuál importa de verdad — en el ejemplo, la entregabilidad, que no era ni siquiera el problema que creías tener. La mayoría de las malas decisiones técnicas no son "eligieron mal": son "nunca escribieron cuáles eran las opciones".
23.3 · Complejidad esencial vs accidental
Es la distinción más útil de toda la ingeniería del software (Fred Brooks, No Silver Bullet, 1986 — y sigue igual de vigente):
- Complejidad esencial: la que viene del problema. Un sistema de facturación es complejo porque los impuestos son complejos. No se puede eliminar, solo gestionar.
- Complejidad accidental: la que te has traído tú. Cinco capas de abstracción para leer un archivo, un framework que nadie entiende, cuatro formas distintas de hacer lo mismo en el mismo repositorio. Es opcional, y casi todo tu esfuerzo debe ir a eliminarla.
┌─────────────────────────────────────────────────────────────┐
│ COMPLEJIDAD TOTAL DEL SISTEMA │
│ │
│ ┌───────────────────────┐ ┌────────────────────────────┐ │
│ │ ESENCIAL │ │ ACCIDENTAL │ │
│ │ (el dominio) │ │ (tus decisiones) │ │
│ │ │ │ │ │
│ │ · reglas de negocio │ │ · abstracciones prematuras│ │
│ │ · normativa legal │ │ · frameworks de más │ │
│ │ · concurrencia real │ │ · configuración duplicada │ │
│ │ · escala requerida │ │ · microservicios sin causa│ │
│ │ │ │ │ │
│ │ Irreducible. │ │ ⬅ AQUÍ es donde un buen │ │
│ │ Se modela bien. │ │ ingeniero marca la │ │
│ │ │ │ diferencia. │ │
│ └───────────────────────┘ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘🧠 Test rápido para distinguirlas: pregúntate "si empezáramos hoy desde cero sabiendo lo que sabemos, ¿esta parte seguiría existiendo?". Si la respuesta es sí, es esencial. Si es "bueno, es que en su día usamos X…", es accidental — y es candidata a desaparecer.
Las tres fuentes de complejidad accidental que más daño hacen (John Ousterhout, A Philosophy of Software Design):
- Dependencias: para cambiar A hay que entender y tocar B, C y D.
- Oscuridad: la información importante no es evidente; hay que preguntar a alguien.
- Duplicación de conocimiento: la misma regla de negocio escrita en tres sitios que se desincronizan.
23.4 · Antes de escribir código: entender el problema
La causa nº1 de software fallido no es el código malo. Es construir la cosa equivocada perfectamente.
Las preguntas que debes hacer siempre
🎯 SOBRE EL PROBLEMA
· ¿Qué problema del usuario resuelve esto? ¿Cómo lo hace hoy sin nosotros?
· ¿Cómo sabremos si funcionó? (métrica concreta, no "que quede bonito")
· ¿Qué pasa si NO lo construimos? ← la pregunta más infravalorada
📏 SOBRE LA ESCALA
· ¿Cuántos usuarios? ¿Cuántas peticiones por segundo, hoy y en un año?
· ¿Cuántos datos? ¿Crecen linealmente o de golpe?
· ¿Cuál es el pico? (Black Friday, inicio de mes, 9:00 del lunes)
⏱️ SOBRE LAS RESTRICCIONES
· ¿Cuándo hace falta? ¿Qué pasa si llega un mes tarde?
· ¿Con qué presupuesto y con cuánta gente?
· ¿Con qué sistemas debe convivir?
⚠️ SOBRE LOS FALLOS
· ¿Qué pasa si esto se cae 5 minutos? ¿Y 5 horas?
· ¿Qué datos NO nos podemos permitir perder jamás?
· ¿Quién se entera si falla a las 3 de la madrugada?💡 La pregunta más valiosa de todas: "¿qué pasa si no lo construimos?" A veces la respuesta honesta es "nada", y acabas de ahorrarle tres meses a tu equipo. El código que no escribes no tiene bugs, no hay que mantenerlo y no hay que migrarlo nunca.
Requisitos funcionales y no funcionales
- Funcionales: qué hace. "El usuario puede subir una foto de perfil."
- No funcionales: cómo de bien lo hace. "La subida termina en menos de 3 s para archivos de hasta 5 MB con una conexión 4G."
⚠️ Los requisitos no funcionales son los que determinan la arquitectura. "Guardar pedidos" se resuelve igual con 100 pedidos al día que con 100.000 — pero "no perder ni un pedido nunca, con picos de 10.000/segundo y respuesta bajo 200 ms" ya te obliga a colas, particionado y replicación. Cuando alguien te dé solo requisitos funcionales, los no funcionales existen igualmente: simplemente nadie los ha escrito, y los descubrirás en producción.
Escribe los criterios de aceptación antes
Funcionalidad: Recuperar contraseña
Escenario: email registrado
Dado un usuario con email "ana@ejemplo.com"
Cuando solicita recuperar su contraseña
Entonces recibe un email con un enlace válido durante 30 minutos
Y el enlace solo puede usarse UNA vez
Escenario: email NO registrado
Dado un email que no existe en el sistema
Cuando solicita recuperar su contraseña
Entonces la respuesta es idéntica a la del caso anterior
# ⚠️ deliberado: si respondiéramos distinto, cualquiera podría
# averiguar qué emails están registrados (enumeración de usuarios)
Escenario: límite de intentos
Dado 5 solicitudes desde la misma IP en 10 minutos
Cuando llega la sexta
Entonces se responde 429 Too Many RequestsEse comentario del segundo escenario es exactamente el tipo de cosa que distingue a un ingeniero: el requisito de seguridad no estaba escrito en la tarea, pero es obligatorio. Saber cuáles son esos requisitos invisibles es lo que se llama experiencia.
23.5 · El método: cómo atacar un problema que no sabes resolver
graph TD
A["1. ENTENDER<br/>Reformula el problema con tus palabras.<br/>Si no puedes, no lo entiendes."] --> B
B["2. ACOTAR<br/>Casos límite, escala, restricciones.<br/>Qué está DENTRO y qué FUERA."] --> C
C["3. BUSCAR PRIOR ART<br/>¿Quién ha resuelto esto ya?<br/>El 95% de los problemas no son nuevos."] --> D
D["4. DISEÑAR 2-3 OPCIONES<br/>Nunca UNA. Con una no estás<br/>decidiendo, estás justificando."] --> E
E["5. ELEGIR Y ESCRIBIR EL PORQUÉ<br/>Un ADR de 10 líneas (cap. 25)."] --> F
F["6. PROBAR LO MÁS ARRIESGADO<br/>Un prototipo desechable de lo<br/>que más dudas te da."] --> G
G["7. CONSTRUIR EN VERTICAL<br/>Una rebanada fina de punta a punta,<br/>no una capa entera."] --> H
H["8. MEDIR<br/>¿Resolvió el problema real?"] --> I
I{"¿Aprendiste algo<br/>que invalida el diseño?"} -->|Sí| A
I -->|No| J["9. DOCUMENTAR Y CERRAR"]Los pasos que la gente se salta (y no debería)
Paso 3 — Buscar prior art. Antes de diseñar un sistema de colas, lee cómo funciona RabbitMQ. Antes de inventar un formato de token, lee el RFC de JWT. Antes de diseñar tu esquema multi-tenant, lee cómo lo hacen Stripe o Shopify (lo publican). No inventes lo que ya está inventado y probado por millones de usuarios.
Paso 6 — Prototipo del riesgo, no de lo fácil. El instinto es empezar por lo que sabes hacer (el CRUD). El método correcto es empezar por lo que no sabes si funcionará: la integración rara, el requisito de rendimiento, la librería sin documentar. Un prototipo desechable de dos días sobre la parte incierta te ahorra dos meses de camino equivocado.
Paso 7 — Construir en vertical (walking skeleton).
❌ POR CAPAS (horizontal) ✅ EN VERTICAL (rebanadas)
Semana 1: toda la BD Semana 1: crear pedido, de punta a punta
Semana 2: todos los modelos (UI → API → BD → email)
Semana 3: todos los endpoints Semana 2: listar pedidos, ídem
Semana 4: toda la UI Semana 3: cancelar pedido, ídem
───────────────────────────── ─────────────────────────────────────
Nada funciona hasta la semana 4. Algo REAL funciona el día 5.
Todos los riesgos aparecen al final. Los riesgos aparecen el primer día.🧠 Por qué la vertical gana siempre: los problemas de integración son los caros, y solo aparecen cuando conectas las capas. Si dejas la integración para el final, descubres el día 25 que la pasarela de pagos no soporta lo que prometiste. Con rebanadas verticales lo descubres el día 3, cuando todavía puedes cambiar el diseño.
23.6 · YAGNI, y su límite
YAGNI = You Aren't Gonna Need It. No construyas lo que crees que quizá necesitarás.
// ❌ Especulativo: "algún día tendremos varios proveedores de pago"
interface ProveedorPago { … }
class FabricaProveedorPago { … }
class EstrategiaEnrutadoPago { … }
class AdaptadorStripe implements ProveedorPago { … }
// → 4 clases, 1 implementación, 0 proveedores adicionales en 3 años
// ✅ Lo que necesitas hoy
class ServicioPagos {
public function cobrar(Pedido $pedido): Recibo { /* Stripe */ }
}
// El día que llegue el segundo proveedor, extraes la interfaz en 20 minutos —
// y esta vez la diseñas conociendo DE VERDAD los dos casos, no imaginándolos.💡 La abstracción prematura es peor que la duplicación. Una abstracción diseñada a partir de un solo ejemplo casi siempre está mal: modela lo que imaginaste, no lo que resultó ser. Y una abstracción equivocada es más cara de eliminar que código duplicado, porque todo el mundo construye encima de ella. Espera al tercer caso (regla del tres): con tres ejemplos reales ves cuál es el patrón de verdad.
Dónde YAGNI NO aplica
YAGNI se malinterpreta como excusa para no pensar. No aplica a las decisiones difíciles de revertir:
| YAGNI ✅ aplica | YAGNI ❌ no aplica |
|---|---|
| Interfaces para una sola implementación | Modelo de datos y esquema |
| Sistema de plugins "por si acaso" | Autenticación y autorización |
| Caché antes de tener un problema | Cifrado de datos sensibles |
| Microservicios en el día 1 | Copias de seguridad |
| Multi-idioma sin usuarios extranjeros | Logs y trazabilidad |
| Configuración para 20 escenarios | Migraciones de esquema versionadas |
🧠 El criterio real es el coste de equivocarse, no el de construirlo. Añadir caché después es fácil. Añadir un campo
usuario_ida una tabla de 500 millones de filas que nunca lo tuvo, o descubrir que llevas dos años sin backups, no lo es. Aplica YAGNI a lo reversible; sé conservador con lo irreversible.
23.7 · Decisiones de una vía y de dos vías
Marco de Jeff Bezos, y probablemente la herramienta mental más práctica de este capítulo:
┌──────────────────────────────┬──────────────────────────────┐
│ PUERTA DE DOS VÍAS │ PUERTA DE UNA VÍA │
│ (reversible) │ (difícil o imposible volver) │
├──────────────────────────────┼──────────────────────────────┤
│ · Librería de fechas │ · El motor de base de datos │
│ · Framework de tests │ · El modelo de datos central │
│ · Estructura de carpetas │ · El esquema público de tu API│
│ · Proveedor de hosting │ · Monolito vs microservicios │
│ · Herramienta de CI │ · Cómo identificas usuarios │
│ · Nombres internos │ · Dónde viven los datos (RGPD)│
├──────────────────────────────┼──────────────────────────────┤
│ DECIDE RÁPIDO. │ DECIDE DESPACIO. │
│ Prueba, mide, cambia. │ Investiga, prototipa, │
│ Discutir 2 semanas cuesta │ consulta, escribe un ADR. │
│ más que equivocarse. │ Equivocarse cuesta AÑOS. │
└──────────────────────────────┴──────────────────────────────┘⚠️ El error más común de los equipos junior es tratar todas las decisiones como de una vía: tres reuniones para elegir librería de validación. El más caro de los senior es el contrario: elegir en 10 minutos, en una reunión de pasillo, un modelo de datos que condicionará el producto durante cinco años.
Antes de discutir, pregunta en voz alta: "¿esto es reversible? ¿Cuánto costaría cambiarlo dentro de un año?". La respuesta determina cuánto tiempo merece la conversación.
23.8 · Deuda técnica: la metáfora bien entendida
Deuda técnica = elegir una solución más rápida hoy aceptando que costará más mañana. Como la deuda financiera, paga intereses: cada cambio futuro en esa zona cuesta un poco más.
Y como la deuda financiera, no siempre es mala. Endeudarse para llegar a un lanzamiento con fecha o para validar una idea es una decisión legítima. Lo que la vuelve tóxica es no registrarla.
| Tipo | Ejemplo | ¿Aceptable? |
|---|---|---|
| Deliberada y a corto plazo | "Sin tests en este módulo para llegar a la demo; los añadimos la semana que viene" | ✅ Sí, si se escribe |
| Deliberada y a largo plazo | "Un solo servidor hasta llegar a 10k usuarios" | ✅ Sí, con un umbral definido |
| Accidental | "No sabíamos que esto se hacía así" | 🟡 Inevitable; se arregla al descubrirla |
| Por negligencia | "No hay tiempo para tests, nunca" | ❌ No es deuda: es un incendio con intereses |
💡 La técnica que cambia las cosas: haz visible la deuda. No basta con un
// TODOque nadie leerá. Escribe un issue con las tres cosas que importan:markdown## Deuda: los pedidos se procesan de forma síncrona **Atajo tomado:** el email de confirmación se envía dentro del request HTTP. **Por qué:** llegar al lanzamiento del 15 de marzo. **Interés que paga:** +2 s de latencia por pedido; si el proveedor de email se cae, el pedido falla entero aunque el pago se haya cobrado. **Umbral para pagarla:** >500 pedidos/día o la primera incidencia por esta causa. **Coste estimado de pagarla:** 3 días (cola + worker, ver apéndice B).Con eso, la deuda deja de ser una queja de los desarrolladores y pasa a ser una decisión de negocio con números — que es la única forma de conseguir tiempo para arreglarla.
23.9 · Estimar sin mentir
Estimar mal no es un problema de cálculo: es un problema de honestidad sobre la incertidumbre.
1. Estima en rangos, no en números. "Entre 3 y 8 días" es una estimación. "5 días" es una promesa que no puedes cumplir.
2. Descompón hasta que cada trozo sea de 1-2 días. Si algo se estima en "unas 3 semanas", en realidad significa "no lo he pensado". Al descomponerlo aparecen las tareas que habías olvidado — que son siempre las que se comen el plazo.
3. Cuenta el trabajo invisible. Un ejercicio útil: sobre una funcionalidad "de 2 días", suma lo que nunca se estima:
Escribir el código feliz ................. 2 días ← lo único que se suele estimar
Casos límite y validación ................ 1 día
Tests .................................... 1 día
Revisión de código + correcciones ........ 0,5 días
Migración de datos existentes ............ 1 día
Documentación y logs ..................... 0,5 días
Despliegue y verificación ................ 0,5 días
Un imprevisto (siempre hay uno) .......... 1,5 días
─────────
8 días🧠 La ley de Hofstadter: "siempre se tarda más de lo esperado, incluso teniendo en cuenta la ley de Hofstadter". Es una broma con base real: subestimamos porque imaginamos el camino feliz, y el camino feliz es la parte pequeña del trabajo.
4. Distingue estimación de compromiso. Una estimación es tu mejor predicción con la información de hoy. Un compromiso es una promesa. Convertir lo primero en lo segundo sin ajustar por incertidumbre es cómo se destruyen los equipos.
5. Registra tus estimaciones y compáralas con la realidad. Es la única forma de mejorar. Si descubres que sistemáticamente multiplicas por 2,5, has descubierto algo valiosísimo: tu factor de corrección personal.
23.10 · Optimización: los tres principios
"La optimización prematura es la raíz de todo mal" — Donald Knuth… que en realidad escribió: "deberíamos olvidarnos de las pequeñas eficiencias, digamos el 97% del tiempo. Sin embargo, no deberíamos dejar pasar nuestras oportunidades en ese 3% crítico". La segunda mitad, convenientemente, casi nunca se cita.
Principio 1 — Mide antes de tocar. Tu intuición sobre dónde está el cuello de botella es mala. La de todo el mundo lo es. Perfila (apéndice F: observabilidad); no adivines.
Principio 2 — Ataca por órdenes de magnitud, no por porcentajes. Estas son las magnitudes aproximadas que conviene tener en la cabeza (no son benchmarks: son órdenes de magnitud para razonar):
Referencia a caché L1 ................ ~1 ns
Referencia a memoria RAM ............. ~100 ns (×100)
Lectura aleatoria en SSD NVMe ........ ~10-100 µs (×1.000)
Ida y vuelta en el mismo datacenter .. ~0,5 ms (×5.000)
Búsqueda en disco duro mecánico ...... ~5-10 ms (×100.000)
Ida y vuelta Europa ↔ California ..... ~150 ms (×1.500.000)🧠 Lo que esta tabla te enseña de verdad: una consulta a base de datos por la red cuesta ~1000 veces más que leer de memoria. Por eso eliminar una consulta siempre gana a optimizar diez líneas de código. El clásico problema N+1 (1 consulta + 1 por cada resultado, cap. 3) convierte 1 ms en 300 ms — y ninguna micro-optimización del bucle lo va a arreglar. Optimizar es sobre todo quitar viajes, no acelerar cálculos.
Principio 3 — El orden correcto de ataque:
1. ¿Se puede NO hacer? → caché, o eliminar la funcionalidad
2. ¿Se puede hacer MENOS veces? → batching, arreglar el N+1, deduplicar
3. ¿Se puede hacer MÁS TARDE? → cola asíncrona, trabajo en segundo plano
4. ¿Se puede hacer EN PARALELO? → concurrencia, más workers
5. ¿Se puede hacer MÁS RÁPIDO? → índices, algoritmo mejor, otro lenguaje
─────────────────────────────────────────────────────────────────
La mayoría empieza por el 5. Los pasos 1-3 dan mejoras de ×10 a ×100;
el 5 suele dar un ×2 a cambio de mucha complejidad accidental.23.11 · Escribir para el que viene detrás
El siguiente que lea tu código no tiene tu contexto. Puede que seas tú dentro de ocho meses.
// ❌ Explica QUÉ hace (eso ya lo dice el código)
// Incrementa el contador
$contador++;
// ✅ Explica POR QUÉ (eso el código NO lo puede decir)
// El proveedor de pagos duplica los webhooks hasta 3 veces en menos de 1 s.
// Contamos los intentos para deduplicar; ver incidencia #4821.
$contador++;Las tres cosas que el código no puede contarte y que debes escribir:
- El porqué de una decisión. "Usamos polling en vez de webhooks porque su servicio no permite configurar la URL de destino."
- Lo que ya intentaste y no funcionó. Evita que el siguiente repita el experimento.
- Las trampas y suposiciones. "Este método asume que
pedidosviene ordenado por fecha; si cambias la consulta de arriba, esto se rompe en silencio."
💡 El "test del autobús inverso": en vez de "¿qué pasa si te atropella un autobús?", pregunta "¿qué necesitaría alguien nuevo para modificar esto con seguridad un martes por la tarde, sin poder preguntarte?". Todo lo que responderías en voz alta es exactamente lo que debe estar escrito — en el README, en un ADR (capítulo 25) o en un comentario.
✅ Ejercicio del capítulo
Parte 1 — Analiza una decisión con la tabla de trade-offs
Un cliente te pide: "quiero que los usuarios puedan exportar todos sus datos a Excel".
- Escribe 8 preguntas que harías antes de escribir una sola línea, agrupadas según las categorías de la sección 23.4 (problema, escala, restricciones, fallos).
- Diseña 3 opciones técnicas distintas (piensa en: síncrono, asíncrono con email, exportación programada) y compáralas en una tabla con al menos 5 criterios.
- Elige una y escribe el porqué en 3 frases, incluyendo qué estás sacrificando.
- Identifica: ¿es una decisión de una vía o de dos vías? Justifícalo.
Parte 2 — Complejidad accidental en tu propio código
- Abre un proyecto tuyo (o cualquiera de los capítulos anteriores de este libro). Encuentra tres ejemplos de complejidad accidental usando el test de la sección 23.3.
- Para cada uno, estima el coste de eliminarlo y el interés que paga si se queda.
Parte 3 — Requisitos invisibles
- Para la funcionalidad "los usuarios pueden subir una foto de perfil", escribe todos los requisitos no funcionales que nadie te va a decir pero son obligatorios. Objetivo: al menos diez. Piensa en seguridad, coste, privacidad, rendimiento y casos límite.
Parte 4 — Estimación honesta
- Estima esa funcionalidad de la foto de perfil descomponiéndola en tareas de 1-2 días máximo, con rangos, incluyendo el trabajo invisible de la sección 23.9. Compara tu total con tu instinto inicial.
Parte 5 — Deuda técnica
- Escribe una ficha de deuda técnica completa (formato de 23.8) para un atajo real que hayas tomado alguna vez, con su umbral de pago.
💡 Pistas de la solución (abre solo si te atascas)
Punto 1 — Las preguntas que separan las respuestas buenas de las mediocres: ¿cuántos datos tiene el usuario con más datos? (cambia todo el diseño: 100 filas se generan al vuelo, 5 millones no), ¿es un requisito legal del RGPD? (entonces el formato y el plazo están regulados y Excel quizá no valga), ¿con qué frecuencia lo usará cada usuario? (una vez al año → nunca optimices), ¿los datos incluyen información de otras personas? (mensajes, contactos → hay un problema de privacidad que nadie ha mencionado).
Punto 2 — El eje real de la comparación es cuánto tarda la exportación más grande. Si el peor caso son 300 ms, síncrono gana por simplicidad abrumadora. Si es de 4 minutos, síncrono está descartado sin discusión (el navegador y el proxy cortan la conexión) y la decisión pasa a ser cómo haces el asíncrono.
Punto 4 — Es de dos vías: puedes empezar síncrono y migrar a cola sin romper nada externo, porque el contrato con el usuario ("descargo mis datos") no cambia. Eso significa: decide rápido, elige lo simple, y añade complejidad cuando los datos te lo pidan.
Punto 7 — Los diez de la foto de perfil, para comparar: tamaño máximo; formatos aceptados (y validar el contenido real, no la extensión — un .jpg puede ser un ejecutable); qué pasa con imágenes enormes (redimensionar en servidor); dónde se almacena (no en el disco del servidor web, que no escala); si es pública o privada la URL; eliminación al borrar la cuenta (RGPD); moderación de contenido inapropiado; eliminación de metadatos EXIF, que incluyen la geolocalización exacta de dónde se tomó la foto; coste de almacenamiento y de transferencia; y qué se muestra mientras carga o si falla.
Si no se te ocurrió lo del EXIF, acabas de vivir en directo lo que significa "requisito invisible": nadie lo pide nunca, y filtrar la casa de tus usuarios es un incidente de privacidad real.
Punto 8 — Si tu instinto dijo "2 días" y la descomposición honesta da 8-12, no has estimado mal ahora: estimabas mal antes. Ese factor entre tu instinto y tu descomposición es la información más útil del ejercicio.
🧠 Autoevaluación
Porque omite el contexto, que es donde vive toda la respuesta. "Mejor" solo tiene sentido respecto a criterios concretos: ¿mejor para consultas relacionales complejas, para escrituras masivas de series temporales, para búsqueda de texto, para trabajar sin conexión? ¿Con qué presupuesto y qué experiencia tiene el equipo?
Toda elección técnica es un trade-off: PostgreSQL da consistencia fuerte y SQL potente a cambio de un escalado horizontal más laborioso; una NoSQL como MongoDB da flexibilidad de esquema y particionado sencillo a cambio de garantías transaccionales más débiles y de mover la validación a tu código. La pregunta bien formulada es: "dadas estas restricciones y este patrón de acceso, ¿qué opción tiene el menor coste total, y qué estamos sacrificando?".
Esencial es la que impone el dominio: calcular el IVA de un pedido internacional es complejo porque el tipo depende del país del comprador, del vendedor, de si es empresa o particular y del tipo de producto. Ninguna arquitectura elimina esa complejidad — lo mejor que puedes hacer es modelarla explícitamente y ponerla en un solo sitio.
Accidental es la que has creado tú: que ese cálculo esté repetido en el controlador, en el generador de PDF y en el job de informes, y que los tres se hayan desincronizado. El problema del IVA sigue ahí; la triplicación no tiene por qué.
El test: si empezáramos hoy desde cero sabiendo lo que sabemos, ¿esto seguiría existiendo? El IVA sí. Las tres copias no.
Cuando la decisión es difícil de revertir. YAGNI optimiza para no pagar por lo que quizá nunca uses, y eso es correcto siempre que añadirlo después sea barato: interfaces, caché, sistemas de plugins, más servidores.
No aplica a lo caro de cambiar: el modelo de datos (migrar 500 millones de filas), el modelo de autenticación y autorización (reconstruirlo obliga a tocar cada endpoint), el cifrado de datos sensibles (cifrar a posteriori exige reprocesar todo el histórico), las copias de seguridad (no se pueden añadir retroactivamente a los datos que ya perdiste) y el esquema público de tu API (una vez hay clientes externos, cambiarlo los rompe).
El criterio no es "¿lo necesitaré?", sino "¿cuánto costaría añadirlo más tarde?".
Porque los riesgos caros son los de integración, y solo se manifiestan cuando las capas se conectan. Construyendo por capas (toda la BD, luego todos los modelos, luego toda la API…) nada funciona de punta a punta hasta el final, así que todos los descubrimientos desagradables — el proveedor no soporta ese flujo, el rendimiento es inaceptable, el requisito estaba mal entendido — llegan cuando ya no queda plazo para reaccionar.
Una rebanada vertical delgada (un solo caso de uso, de la interfaz a la base de datos y vuelta) da algo demostrable en días, valida las suposiciones cuando todavía son baratas de corregir, y permite recibir feedback real del usuario sobre algo que funciona en vez de sobre un documento.
No con "es imposible" ni con "vale, lo intentamos". Con opciones y consecuencias explícitas:
- Enseña la descomposición, no el número. Seis semanas es discutible; una lista de doce tareas con sus rangos es información.
- Separa el alcance del plazo. "En dos semanas puedo entregar A y B, que cubren el 80% de los casos. C y D necesitan cuatro más. ¿Sirve A+B para lo que necesitas?" Muchas veces sí, porque el plazo responde a un evento concreto que solo necesita una parte.
- Pon precio a la deuda, no la escondas. "Puedo llegar en tres semanas sin tests de integración y con el envío de emails síncrono. Eso son unos cinco días de trabajo después del lanzamiento, y el riesgo concreto es X." Que la decisión la tome quien tiene el contexto de negocio, con la información encima de la mesa.
- Nunca aceptes un plazo que sabes falso. Comprometerte a dos semanas para evitar una conversación incómoda hoy garantiza una conversación mucho peor dentro de dos semanas, y esta vez con el proyecto a medias.
Significa que el atajo no tiene un coste único, sino recurrente: cada cambio futuro en esa zona del código cuesta más de lo que costaría sin el atajo. Sin tests, cada modificación requiere pruebas manuales. Con lógica duplicada en tres sitios, cada cambio de regla se hace tres veces (y antes o después, solo dos).
Se hace visible convirtiéndola en una ficha con cuatro datos: el atajo tomado, por qué se tomó, qué interés paga en tiempo o riesgo concreto, y el umbral que dispara pagarla (una métrica: "cuando superemos 500 pedidos/día" o "a la primera incidencia por esta causa"). Sin umbral, la deuda es eterna; con umbral, deja de ser una opinión de los desarrolladores y pasa a ser una decisión de negocio medible.
Por la diferencia de órdenes de magnitud. Un acceso a memoria son ~100 ns; una ida y vuelta a la base de datos por la red son ~0,5 ms como mínimo, es decir unas 5.000 veces más. Optimizar un bucle que ya se ejecuta en memoria puede ahorrarte microsegundos; quitar una consulta te ahorra milisegundos enteros.
El caso canónico es el problema N+1: listar 100 pedidos con una consulta por cada cliente son 101 viajes. Ninguna optimización del código Python, PHP o Go cambia eso de forma apreciable — pero un JOIN o una carga ansiosa (eager loading) lo reduce a 1 o 2 viajes y multiplica el rendimiento por cien. Por eso el orden de ataque correcto empieza por "¿se puede no hacer?" y "¿se puede hacer menos veces?", y deja "¿se puede hacer más rápido?" para el final.
Siguiente: 24-diseno-de-sistemas.md — de los principios a los sistemas reales: escalar, replicar, encolar y no perder datos.