Cuando el 90% usa IA, el riesgo invisible es la calidad
El informe DORA 2025 dejó un dato contundente: alrededor del 90% de desarrolladores emplea IA en su día a día. Productividad, más entregas, menos tareas repetitivas. Pero hay un lado B: la degradación silenciosa de la calidad cuando aceptamos sugerencias sin criterio, medimos lo que no importa o delegamos el conocimiento del dominio a un modelo. En este artículo te muestro antipatrones reales que la IA suele introducir (o amplificar) y, sobre todo, cómo evitarlos con disciplina técnica, pruebas inteligentes y procesos que reflejan las capacidades recomendadas por DORA.
Los síntomas de que la IA está bajando la calidad
- Ofuscación por sobre-optimización prematura: cachés innecesarias, micro-optimizaciones sin datos, funciones hipergenéricas.
- Testing ciego: pruebas que confirman la implementación (o la sugerencia de IA) en vez del comportamiento, mocks por todas partes y cero propiedades invariantes.
- Pérdida de conocimiento del dominio: reglas cruciales embebidas en prompts o respuestas de IA, sin documentarse en el código ni en registros de decisión.
- Dependencias mágicas: helpers auto-generados que nadie entiende, acoplamiento a SDKs por defecto y errores silenciados.
- Documentación inexistente o desactualizada: el modelo “sabe”, el equipo no.
Ejemplo 1: Ofuscación por sobre-optimización prematura
Una IA bien intencionada puede devolver una función “rápida” pero innecesariamente compleja. Mira este ejemplo de Node.js:
Antipatrón (código):
const cache = new Map();
function getUserScore(users, id) {
const key = 'u:' + id;
if (cache.has(key)) return cache.get(key);
// Mezcla de find + reduce + coerciones crípticas
const u = users.find(u => ('' + u.id) === ('' + id));
const score = (u && Array.isArray(u.events))
? u.events.reduce((acc, e) => acc + (e.points || 0), 0)
: 0;
// Cachear todo por defecto: ¿caduca? ¿memoria?
cache.set(key, score | 0);
return score | 0;
}
Problemas:
- Complejidad innecesaria: convierte IDs a string y usa operadores bit a bit para “optimizar”.
- Caché global sin política de expiración ni métricas: deuda técnica silenciosa.
- Semántica opaca: el lector no entiende por qué esas decisiones.
Refactor simple y medible:
Refactor (código):
function getUserScore(users, id) {
const user = users.find(u => u.id === id);
if (!user || !Array.isArray(user.events)) return 0;
return user.events.reduce((total, e) => total + (e.points ?? 0), 0);
}
Si realmente hay cuello de botella, mide antes de optimizar:
- Métricas: usa clinic.js o node –prof para perf, y k6 para carga.
- Política de caché explícita: TTL, invalidación y observabilidad.
Ejemplo de caché con política explícita:
class TimedCache {
constructor(ttlMs, clock = () => Date.now()) {
this.ttl = ttlMs;
this.clock = clock;
this.map = new Map();
}
get(key) {
const hit = this.map.get(key);
if (!hit) return undefined;
if (this.clock() > hit.expiresAt) { this.map.delete(key); return undefined; }
return hit.value;
}
set(key, value) {
this.map.set(key, { value, expiresAt: this.clock() + this.ttl });
}
}
const userScoreCache = new TimedCache(30_000);
function getUserScoreCached(users, id) {
const key = `user-score:${id}`;
const cached = userScoreCache.get(key);
if (cached !== undefined) return cached;
const value = getUserScore(users, id);
userScoreCache.set(key, value);
return value;
}
Principio: primero claridad, luego optimización guiada por datos. DORA insiste en ciclos cortos y feedback rápido; mide, cambia, repite.
Ejemplo 2: Testing ciego (confirma la implementación, no el comportamiento)
Muchas sugerencias de IA generan tests que “siguen el código” en vez de definir contratos o propiedades. Así se ve un test frágil:
Antipatrón (test):
import { getUserScore } from './score';
test('getUserScore calls reduce once', () => {
const users = [{ id: 1, events: [{ points: 2 }, { points: 3 }] }];
const spy = jest.spyOn(Array.prototype, 'reduce');
const result = getUserScore(users, 1);
expect(spy).toHaveBeenCalledTimes(1); // Acopla al detalle interno
expect(result).toBe(5);
spy.mockRestore();
});
Mejor enfoque: pruebas basadas en propiedades (property-based) y contramedidas como mutation testing para evitar falsos positivos.
Ejemplo (property-based con fast-check):
import { test, expect } from '@jest/globals';
import fc from 'fast-check';
import { getUserScore } from './score';
test('score es la suma de points válidos o 0', () => {
const eventArb = fc.record({ points: fc.oneof(fc.integer(), fc.constantFrom(undefined, null)) });
const userArb = fc.record({ id: fc.integer(), events: fc.array(eventArb, { maxLength: 50 }) });
return fc.assert(
fc.property(fc.array(userArb, { maxLength: 10 }), fc.integer(), (users, id) => {
const score = getUserScore(users, id);
const expected = users
.filter(u => u.id === id && Array.isArray(u.events))
.flatMap(u => u.events)
.reduce((acc, e) => acc + (e?.points ?? 0), 0);
expect(score).toBe(expected);
})
);
});
Añade mutation testing para validar que tus pruebas detectan defectos reales:
- Stryker (JS/TS, Java, .NET): mide la calidad de tus tests con un Mutation Score significativo.
- Contratos con Pact para microservicios; E2E con Playwright.
Ejemplo 3: Pérdida de conocimiento del dominio
Cuando el “por qué” se queda en el prompt, el equipo pierde contexto. Veamos un cálculo de elegibilidad escrito por IA:
Antipatrón (código):
function isEligible(user) {
if (!user) return false;
if (user.age < 21) return false; // ¿por qué 21? if (user.country !== 'AR' && user.country !== 'CL') return false; // ¿por qué estas excepciones? const score = (user.events || []).reduce((a, e) => a + (e.points || 0), 0);
return score > 1000; // ¿de dónde sale este umbral?
}
Refactor con lenguaje ubicuo, constantes con semántica y decisión documentada:
Refactor (código):
// Dominio explícito + ADR: decisión #42 "Elegibilidad LATAM 2025-Q1"
const MIN_AGE = 21; // Basado en política comercial LATAM 2025-Q1
const SUPPORTED_COUNTRIES = new Set(['AR', 'CL']);
const MIN_SCORE_FOR_ELIGIBILITY = 1000; // Umbral acordado con Riesgos
function isEligible(user) {
if (!user) return false;
if (user.age < MIN_AGE) return false; if (!SUPPORTED_COUNTRIES.has(user.country)) return false; const score = (user.events ?? []).reduce((a, e) => a + (e.points ?? 0), 0);
return score > MIN_SCORE_FOR_ELIGIBILITY;
}
Complementa con ADRs (Architecture Decision Records), runbooks y documentación docs-as-code para que la regla no viva en la cabeza de la IA. DORA subraya la gestión explícita del conocimiento como capacidad organizativa.
Prácticas concretas para mantener la calidad con IA en el loop
- Guardrails técnicos: tipos (TypeScript), formateo (Prettier), linters (ESLint/Biome), análisis estático (SonarQube). Política: ningún commit de IA sin pasar por estos checks.
- Plantilla de PR con trazabilidad: “¿Qué parte fue sugerida por IA?”, “¿métricas previas/post?”, “riesgos/rollback?”. Añade coautoría: Co-authored-by: AI.
- Métricas que importan: Lead Time, Change Failure Rate, MTTR (DORA). Complementa con Mutation Score, cobertura con sentido y alertas de regresión.
- Testing balanceado: unit + property-based (fast-check) + contratos (Pact) + E2E (Playwright). Evita tests que espían detalles internos.
- Observabilidad desde el diseño: OpenTelemetry, trazas por caso de uso, budgets de latencia y errores por feature.
- Gestión de conocimiento: ADRs (adr-tools), glosario de dominio, code tours y runbooks. No dependas del historial del chat.
- Cadena de suministro: SBOM (Syft), firmas (Sigstore), políticas SLSA, actualizaciones automatizadas (Renovate).
- Prompts con políticas: biblioteca de prompts curados, ejemplos aprobados y lista de patrones prohibidos (p. ej., “optimiza con bitwise por defecto”).
Checklist express para tu equipo
- Activa TypeScript o tipos estrictos.
- Agrega fast-check y Stryker al pipeline.
- Define ADRs para reglas de negocio clave.
- Integra OpenTelemetry y grafana/jaeger para trazas.
- Revisa en cada PR: claridad > cleverness y métrica previa/post.
Herramientas recomendadas (probadas en proyectos reales)
- Asistentes IA: GitHub Copilot, Gemini Code Assist (configurados a nivel enterprise, con políticas de privacidad y contextos mínimos necesarios).
- Calidad: ESLint/Biome, Prettier, SonarQube, Stryker, fast-check.
- Observabilidad: OpenTelemetry, Jaeger, Grafana, k6 para pruebas de carga.
- Docs y plataforma interna: adr-tools, Docusaurus/MkDocs, Backstage para golden paths.
- Seguridad y supply chain: Syft/Grype, Sigstore, OPA/Rego para políticas.
Cultura y flujo: pequeños lotes, feedback rápido, sin héroes
La evidencia que recoge DORA es consistente: los equipos que mejor capitalizan la IA trabajan en lotes pequeños, con ciclos de feedback cortos y arquitecturas desacopladas. La IA es un amplificador: hace más rápido lo que ya haces. Si tus prácticas son sólidas, verás mejoras; si no, multiplicará la fragilidad. Diseña tu flujo para detectar y aprender temprano: feature flags, canary deploys, métricas por feature y review humano en cambios sensibles.
Reglas de oro para prompts de código
- Di qué negocio resuelves: objetivos, invariantes y constraints antes del “escribe la función”.
- Pide claridad y justificación: “explica decisiones y riesgos”.
- Prohíbe optimizaciones prematuras: “no uses bitwise ni caches salvo que lo solicite”.
- Exige pruebas: “incluye tests basados en propiedades y casos límite”.
- Solicita telemetría: “instrumenta con OpenTelemetry este endpoint”.
Conclusión: IA sí, pero con ingeniería
No se trata de frenar la IA; se trata de encauzarla. Si alineas tus prácticas con las capacidades organizativas que destaca DORA —ciclos cortos, observabilidad, automatización con criterio y gestión de conocimiento— la IA es un multiplicador extraordinario. Si no, terminarás con código rápido de escribir y caro de mantener. Que la adopción del 90% no te haga olvidar lo esencial: calidad primero.
¿Qué otros antipatrones te ha colado la IA? Cuéntamelo en comentarios, comparte este artículo con tu equipo y suscríbete para más guías prácticas orientadas a resultados.