Reflexiones sobre cinco años de lecciones en ingeniería de software
Cinco años de ingeniería condensados: fundamentos antes que frameworks, confianza por entregas fiables, código para quien venga y sencillez.

Los fundamentos sobreviven a los frameworks
Cada año llega un framework nuevo. Los ingenieros que prosperan son los que entienden los patrones que hay debajo. Los frameworks cambian; las estructuras de datos, los algoritmos, las redes y el pensamiento de diseño de sistemas siguen siendo relevantes durante décadas.
// The framework changes. The pattern doesn't.
// 2020: Express middleware
app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
console.log(`${req.method} ${req.path} - ${Date.now() - start}ms`);
});
next();
});
// 2023: Next.js middleware
export function middleware(request: NextRequest) {
const start = Date.now();
const response = NextResponse.next();
response.headers.set("X-Response-Time", `${Date.now() - start}ms`);
return response;
}
// 2025: Whatever comes next — still request interception,
// still wrapping the handler, still measuring duration.
// The framework is syntax. The pattern is knowledge.El código se lee más de lo que se escribe
El código ingenioso no impresiona a nadie seis meses después, cuando hay que depurarlo a las 3 de la madrugada. Escribir pensando en la claridad no es una señal de debilidad: es una señal de experiencia.
// ❌ Clever — what does this do at a glance?
const r = d.filter((x) => x.s === "a" && x.t > Date.now() - 864e5).reduce(
(a, c) => ({ ...a, [c.c]: (a[c.c] || 0) + c.v }),
{} as Record<string, number>
);
// ✅ Clear — intention is obvious without comments
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
const activeTransactions = deposits.filter(
(deposit) =>
deposit.status === "active" && deposit.timestamp > oneDayAgo
);
const totalsByCategory = activeTransactions.reduce<Record<string, number>>(
(totals, transaction) => ({
...totals,
[transaction.category]:
(totals[transaction.category] ?? 0) + transaction.value,
}),
{}
);Entrega poco, pero entrega seguido
Los pull requests grandes se quedan días esperando revisión, acumulan conflictos de merge y terminan desplegándose con errores ocultos. Los cambios pequeños y enfocados se revisan en minutos y se despliegan con confianza.
// Deployment metrics that tell the story
interface DeliveryMetrics {
// Small PRs: reviewed faster, fewer bugs
avgPRSize: { lines: number; reviewTimeHours: number; bugRate: number };
// Deployment frequency: more deploys = smaller blast radius
deploysPerWeek: number;
// Lead time: commit to production
leadTimeHours: number;
// Recovery: how fast you fix things when they break
meanTimeToRecoveryMinutes: number;
}
// What I've seen across teams:
const highPerformingTeam: DeliveryMetrics = {
avgPRSize: { lines: 150, reviewTimeHours: 2, bugRate: 0.02 },
deploysPerWeek: 25,
leadTimeHours: 4,
meanTimeToRecoveryMinutes: 30,
};
const strugglingTeam: DeliveryMetrics = {
avgPRSize: { lines: 2000, reviewTimeHours: 72, bugRate: 0.15 },
deploysPerWeek: 1,
leadTimeHours: 336, // 2 weeks
meanTimeToRecoveryMinutes: 480, // 8 hours
};
// The difference is not talent. It's workflow.Las pruebas ahorran tiempo — con el tiempo
Escribir pruebas se siente lento hasta la primera vez que una prueba detecta una regresión que, de otro modo, habría llegado a producción. El retorno no es inmediato, pero se acumula.
// The test that pays for itself
describe("checkout price calculation", () => {
it("applies percentage discount before tax", () => {
const result = calculateTotal({
items: [{ price: 100, quantity: 2 }],
discount: { type: "percentage", value: 10 },
taxRate: 0.08,
});
// Without this test, a refactor swapped tax and discount order.
// That bug would have overcharged every customer by ~1%.
// This test caught it in CI, saved a production incident,
// and justified every minute spent writing it.
expect(result.subtotal).toBe(200);
expect(result.discountAmount).toBe(20);
expect(result.tax).toBe(14.4); // 8% of $180
expect(result.total).toBe(194.4);
});
});La mejor arquitectura es la más simple que funciona
La sobreingeniería es el error más común a mitad de carrera. Un monolito soporta más tráfico del que uno imagina. Los microservicios resuelven problemas de organización, no técnicos, a menos que tengas el tamaño de equipo que justifique la sobrecarga operativa.
// Year 1 thinking: "We need microservices for scalability"
// Year 3 reality: "We have 40 services, 3 engineers, and
// no one understands the full system"
// ❌ Premature microservices for a small team
// services/user-service/
// services/order-service/
// services/payment-service/
// services/notification-service/
// services/inventory-service/
// + Kubernetes, service mesh, distributed tracing, 5 databases
// ✅ Modular monolith — same boundaries, fraction of the complexity
// src/modules/users/
// src/modules/orders/
// src/modules/payments/
// src/modules/notifications/
// src/modules/inventory/
// + One database, one deployment, clear module boundaries
// Extract to services WHEN you have the team and traffic to justify itLa comunicación escala; el código no
El techo de impacto de programar sin más es más bajo de lo que parece. Los ingenieros que dan forma a los productos, influyen en la arquitectura y aceleran a sus equipos son los que se comunican bien: en documentos de diseño, revisiones de código, retrospectivas de incidentes y conversaciones del día a día.
// A pull request description that accelerates review
interface EffectivePRDescription {
what: string; // What changed
why: string; // Why this approach
how: string; // Key implementation decisions
testing: string; // How you verified it works
risks: string; // What could go wrong
rollback: string; // How to undo if needed
}
// vs. the PR description we've all seen:
// Title: "fix stuff"
// Description: ""
// Files changed: 47
// The first one gets reviewed in 20 minutes.
// The second one sits for 3 days.Invierte en la experiencia del desarrollador
Las herramientas, los scripts y los flujos de trabajo que construyes para ti mismo se acumulan día a día. Una mejora de 30 segundos en tu ciclo de desarrollo ahorra horas a lo largo de un año.
// Small investments that compound
const developerExperienceWins = [
{
investment: "Hot reload configured properly",
timePerOccurrence: "10 seconds saved",
frequency: "200 times/day",
annualSavings: "~110 hours/year",
},
{
investment: "One-command database reset with seed data",
timePerOccurrence: "5 minutes saved",
frequency: "3 times/week",
annualSavings: "~13 hours/year",
},
{
investment: "Pre-commit hooks catching lint/type errors",
timePerOccurrence: "15 minutes saved (no CI round-trip)",
frequency: "5 times/week",
annualSavings: "~65 hours/year",
},
{
investment: "Automated PR template with checklist",
timePerOccurrence: "3 minutes saved",
frequency: "10 times/week",
annualSavings: "~26 hours/year",
},
];La lección debajo de todas las lecciones
La ingeniería de software no se trata de escribir código perfecto. Se trata de resolver problemas para personas —usuarios, compañeros de equipo y tu propio yo futuro— mientras navegas restricciones de tiempo, conocimiento y complejidad. Los mejores ingenieros no son los que conocen más tecnologías. Son los que, de forma constante, toman buenas decisiones de compromiso, se comunican con claridad y dejan el código mejor de como lo encontraron.
Cinco años, cientos de pull requests, decenas de incidentes en producción, y la habilidad más importante resultó ser el criterio: saber cuándo construir, cuándo comprar, cuándo simplificar y cuándo lanzar. El código es solo el artefacto. Lo que importa es el razonamiento detrás.


