---
name: prevencion-sobreingenieria
description: >
  Previene sobre-ingeniería y código innecesario en generación asistida por IA.
  Cuatro principios: pensar antes de codificar, simplicidad primero, cambios
  quirúrgicos y ejecución orientada a metas verificables. Cargar antes de
  implementar features, refactorizar código o resolver bugs — especialmente
  cuando el agente tiende a agregar abstracciones especulativas o tocar código
  fuera del alcance solicitado.
version: "1.2.0"
evolved: true
evolved-from: "1.1.0"
evolved-at: "2026-05-22"
evolved-by: "absorcion-temp-karpathy"
evolved-note: "Recurso nuevo: recursos/EXAMPLES.md con 9 ejemplos MAL→BIEN adaptados del repo andrej-karpathy-skills (MIT). Cierra gap de regla skills-estandar.md § Convención EXAMPLES.md."
herramientasPermitidas: [Read, Grep]
toolsRequeridos: Read, Grep, Glob
exclusiones:
  - "No cargar cuando el agente omite pasos obligatorios del proceso — eso es racionalización, no sobre-ingeniería; cargar `prevencion-racionalizacion`."
  - "No cargar para tasks one-liner obvias (fix de typo, rename de variable, one-shot de una línea) — el propio skill indica que la simplicidad aplica al proceso; añadir análisis de sobre-ingeniería a cambios triviales es en sí mismo sobre-ingeniería."
  - "No cargar para auditar código ya existente en busca de complejidad acumulada — ese análisis es parte de `mapear-codebase` o `revisor-codigo-swl`; este skill guía la implementación en curso, no la auditoría de deuda técnica."
  - "No cargar cuando la abstracción propuesta tiene 3 o más casos de uso concretos ya identificados — el criterio de 3 usos es el umbral del skill para justificar una abstracción; por encima de ese umbral, diseñarla es correcto."
evolvable: true  # default para skill estandar
---
# Prevención de sobre-ingeniería

Skill derivado de los principios de Andrej Karpathy sobre errores comunes de LLMs
al generar código, adaptado al ecosistema SWL.

> **Para 9 ejemplos MAL→BIEN lado a lado con código real**, ver
> [recursos/EXAMPLES.md](recursos/EXAMPLES.md). Cada ejemplo está mapeado a un
> escenario operativo concreto de SWL (qué agente debe citarlo, en qué flujo).

## Cuándo cargar

- Antes de implementar cualquier feature nueva con `implementador-swl` o `backend-*-swl`
- Cuando un agente tiende a agregar abstracciones que nadie pidió
- Al refactorizar código existente (riesgo de "mejorar" código adyacente)
- Cuando el usuario reporta que el agente "hace demasiado" o "cambia cosas que no debía"
- En revisiones de código donde se detecta complejidad innecesaria

## Principio 1 — Pensar antes de codificar

Antes de escribir una sola línea, explicitar supuestos y resolver ambigüedades.

### Reglas

1. **Explicitar supuestos** — Si hay ambigüedad en la instrucción, preguntar. NUNCA
   asumir silenciosamente qué quiso decir el usuario.
2. **Presentar interpretaciones** — Si hay múltiples formas válidas de resolver el
   problema, presentarlas brevemente y dejar que el usuario elija.
3. **Surfacear tradeoffs** — Si la solución tiene consecuencias no obvias (performance,
   complejidad, dependencias nuevas), mencionarlas antes de implementar.

### Anti-patrón

```python
# MAL — asume scope sin preguntar
def exportar_usuarios():
    usuarios = db.query(Usuario).all()
    csv = generar_csv(usuarios, campos=TODOS_LOS_CAMPOS)
    enviar_email(admin, csv)
    registrar_log_auditoria("export_usuarios")
    return csv
# El usuario solo pidió "exportar usuarios" — ¿qué campos? ¿formato?
# ¿enviar email? ¿log de auditoría? Todo fue asumido.

# BIEN — clarifica antes de implementar
# Antes de implementar: ¿qué campos necesitas en el export?
# ¿Formato CSV o JSON? ¿Solo usuarios activos o todos?
# ¿Se requiere notificación o solo el archivo?
def exportar_usuarios(campos: list[str], solo_activos: bool = True) -> bytes:
    query = db.query(Usuario)
    if solo_activos:
        query = query.filter(Usuario.es_activo == True)
    return generar_csv(query.all(), campos=campos)
```

## Principio 2 — Simplicidad primero

El código más simple que resuelve el problema es el código correcto. Toda
complejidad adicional requiere justificación explícita.

### Reglas

1. **Código mínimo viable** — Escribir la solución más corta y clara que cumpla
   el requisito. Si 200 líneas pueden ser 50, reescribir.
2. **Sin abstracciones especulativas** — No crear interfaces, factories, strategies
   ni patrones de diseño para un solo caso de uso. Tres usos concretos justifican
   una abstracción; menos de tres, no.
3. **Sin features fantasma** — No agregar configurabilidad, flags, parámetros
   opcionales ni manejo de errores para escenarios que no existen hoy.
4. **Sin error handling para lo imposible** — No validar que un parámetro interno
   no sea null cuando el tipo lo garantiza. Validar solo en fronteras del sistema.

### Anti-patrón

```python
# MAL — Strategy pattern para un solo tipo de descuento
class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, price: Decimal) -> Decimal: ...

class PercentageDiscount(DiscountStrategy):
    def __init__(self, pct: Decimal):
        self.pct = pct
    def calculate(self, price: Decimal) -> Decimal:
        return price * self.pct / 100

class DiscountFactory:
    _strategies = {"percentage": PercentageDiscount}
    @classmethod
    def create(cls, type: str, **kwargs) -> DiscountStrategy:
        return cls._strategies[type](**kwargs)

# BIEN — función directa para un solo caso de uso
def calcular_descuento(precio: Decimal, porcentaje: Decimal) -> Decimal:
    return precio * porcentaje / 100
```

### Test de simplicidad

Antes de hacer commit, preguntar: *¿un ingeniero senior diría que esto está
sobre-complicado?* Si la respuesta es "sí" o "tal vez", simplificar.

## Principio 3 — Cambios quirúrgicos

Solo tocar el código que la tarea requiere. Cada línea cambiada debe trazar
directamente al requisito del usuario.

### Reglas

1. **No "mejorar" código adyacente** — Si el usuario pide corregir un bug en la
   función A, no renombrar variables en la función B ni agregar type hints en C.
2. **No refactorizar de oportunidad** — Si el código existente funciona y no es
   parte del cambio, dejarlo. Aunque "podría ser mejor".
3. **Respetar estilo existente** — Si el archivo usa comillas simples, usar comillas
   simples. Si usa tabs, usar tabs. Incluso si preferirías otro estilo.
4. **Solo eliminar lo que tu cambio hizo innecesario** — Si tu cambio hace que un
   import quede sin uso, elimínalo. Pero no eliminar imports que ya estaban sin uso
   antes de tu cambio.

### Anti-patrón

```python
# Tarea: "corregir el cálculo de IVA cuando la tasa es 0%"

# MAL — corrige el bug Y además "mejora" cosas que no le pidieron
def calcular_impuestos(
    precio: Decimal,  # type hint agregado (no pedido)
    tasa: float,
) -> Decimal:  # return type agregado (no pedido)
    """Calcula impuestos sobre el precio."""  # docstring agregado (no pedido)
    if tasa == 0:
        return Decimal("0")  # ← este es el fix real
    return precio * Decimal(str(tasa))  # refactorizado sin necesidad

# BIEN — solo el fix necesario
def calcular_impuestos(precio, tasa):
    if tasa == 0:
        return Decimal("0")  # fix: evitar multiplicación innecesaria con tasa 0
    return precio * Decimal(str(tasa))
```

### Test quirúrgico

Revisar el diff antes del commit: *¿cada línea cambiada es directamente necesaria
para cumplir lo que el usuario pidió?* Si hay líneas que no pasan este test,
revertirlas.

## Principio 4 — Ejecución orientada a metas

Transformar instrucciones vagas en criterios de éxito verificables antes de
implementar.

### Reglas

1. **Definir criterio de éxito** — Antes de implementar, declarar qué significa
   "terminado" en términos verificables (tests que pasan, comportamiento observable).
2. **Plan breve con verificación** — Para tareas multi-paso, declarar los pasos
   y cómo se verifica cada uno.
3. **Test primero para bugs** — Al corregir un bug, escribir primero el test que
   lo reproduce. Verificar que falla. Luego corregir. Verificar que pasa.

### Plantillas de transformación

| Instrucción vaga | Meta verificable |
|---|---|
| "Agregar validación" | Escribir tests de inputs inválidos → hacerlos pasar |
| "Corregir el bug" | Escribir test que reproduce el bug → hacerlo pasar |
| "Refactorizar X" | Tests existentes pasan antes Y después del cambio |
| "Hacerlo más rápido" | Definir métrica (p95 latencia) + baseline + objetivo |
| "Mejorar la autenticación" | Definir qué problema específico resolver primero |

### Anti-patrón

```markdown
# MAL — plan vago sin verificación
1. Revisar el sistema de autenticación
2. Identificar problemas
3. Implementar mejoras
4. Probar

# BIEN — meta concreta con verificación por paso
Meta: las sesiones se invalidan al cambiar contraseña.
1. Test: cambiar contraseña → sesión anterior rechazada (FAIL esperado)
2. Implementar invalidación de sesiones en cambio de contraseña
3. Verificar: test anterior PASA
4. Verificar: login con nueva contraseña funciona (sin regresión)
```

## Resumen de señales de alerta

| Señal | Problema | Acción |
|---|---|---|
| Se necesita "y" para describir el cambio | Múltiples cambios en uno | Dividir en commits separados |
| Clase con 1 solo uso | Abstracción prematura | Reemplazar con función |
| Parámetro `**kwargs` "por flexibilidad" | Feature fantasma | Parámetros explícitos |
| `try/except` para error que nunca ocurre | Manejo defensivo excesivo | Eliminar |
| Se modificaron archivos fuera del scope | Cambio no quirúrgico | Revertir extras |
| "Aproveché para mejorar…" | Refactor de oportunidad | Commit separado o no hacerlo |
| `if config.get("feature_x", False)` | Feature flag prematuro | Código directo |

## Calibración: cuándo NO aplicar rigor completo

Estos principios sesgan hacia cautela sobre velocidad. Para tareas triviales
(fix de typo, one-liner obvio, rename de variable), usar criterio. No toda línea
de código requiere análisis profundo. La simplicidad también aplica al proceso.

## Cuándo NO cargar

- El agente omite un paso obligatorio del proceso con una justificación — eso es racionalización, no sobre-ingeniería; cargar `prevencion-racionalizacion`.
- La tarea es un fix de typo, rename de variable u otro one-liner obvio — el análisis completo de los 4 principios no compensa; el propio skill reconoce que "la simplicidad también aplica al proceso".
- El objetivo es identificar complejidad acumulada en código heredado — ese análisis es parte de `mapear-codebase` (Paso 5: deuda técnica) o de una revisión de código; este skill previene sobre-ingeniería durante la implementación, no la audita después.
- La abstracción ya tiene 3 o más casos de uso concretos identificados — el umbral del skill para justificar una abstracción es 3 usos; si ya se cumplen, diseñar la interfaz es correcto y este skill no agrega restricciones.

## Gotchas / Errores comunes no obvios

- **Test quirúrgico fallado porque el diff incluye cambios de formato automático del linter**: el agente revisa el diff antes del commit y ve líneas reformateadas por `black` o `prettier` que no estaban en el scope. Causa: el linter se ejecuta sobre archivos tocados y formatea líneas adyacentes al cambio real. Solución: distinguir entre cambios del agente y cambios del formateador automático — si el formateador era parte del flujo pre-commit, las líneas de formato no cuentan como cambios fuera del scope; si no era parte del flujo, revertir el formato y solo incluir el cambio mínimo.
- **Principio de "no mejorar código adyacente" interpretado como prohibición de agregar type hints al código que se modifica**: el agente corrige un bug pero no actualiza el type hint del parámetro que cambió de tipo. Causa: generalizar la regla de "no tocar lo adyacente" a partes del código que sí deben actualizarse como parte del fix. Solución: la regla aplica a código que no necesita cambiar; un type hint incorrecto *después del cambio* es parte del fix, no una mejora de oportunidad.
- **Principio 1 (clarificar antes de implementar) aplicado a tareas que ya tienen spec completa**: el agente pregunta por decisiones que ya están documentadas en el PLAN.md aprobado. Causa: no leer la spec antes de preguntar. Solución: el Principio 1 aplica cuando hay ambigüedad real después de leer el PLAN.md, CLAUDE.md y los archivos relevantes — si la respuesta está en los documentos ya leídos, preguntar es sobreingeniería del proceso, no clarificación.
- **Criterio de 3 usos para abstracciones aplicado a tipos (interfaces/protocolos)**: el agente no crea una interfaz para un tipo que se usa en un solo lugar. Causa: confundir "3 usos para abstracciones de comportamiento" con "3 usos para tipos". Solución: el criterio de 3 usos aplica a factories, strategies, y patrones de comportamiento especulativos; una interfaz TypeScript o un Protocol Python que define un contrato usado una vez puede ser la solución correcta si el tipo es el boundary entre módulos.

---

## Variables residuales post-refactor son code smell

### Regla

Variables asignadas pero no usadas (F841 en ruff/flake8) **post-refactor** son
casi siempre residuos de una versión anterior del código que el refactor no
limpió. Se parecen a "variables temporales del desarrollador" pero delatan un
refactor incompleto — la lógica que las usaba se movió o eliminó, pero las
asignaciones quedaron.

### Por qué son peligrosas

- **Confunden al próximo lector**: asumirá que la variable se usa más abajo.
- **Inflan el diff cognitivo** del refactor real.
- **Ocultan bugs sutiles**: el valor asignado puede tener side effects (llamada
  a función costosa o con efecto en estado) que ya no aporta nada al flujo.
- **Invalidan la métrica de cobertura**: líneas "cubiertas" por tests que en
  realidad no afectan el resultado.

### Patrón operativo

Después de **cualquier refactor** que modifique el flujo de una función (extraer
método, inlinear función, cambiar el shape de un return), correr:

```bash
ruff check --select=F841 ruta/al/modulo.py
# o para todo el proyecto
ruff check --select=F841 .
```

Cada F841 detectado post-refactor es candidato 1 de 2:

1. **Variable muerta** (caso común post-refactor): eliminarla.
2. **Variable viva pero mal nombrada** (caso raro): si el valor se usa como
   side effect intencional (ej: evaluar una propiedad para cachear), renombrar
   con prefijo `_` y comentar el propósito.

### Anti-patrón típico

```python
# Antes del refactor: había un if que usaba exito_key
def procesar(data):
    exito, exito_key = intentar(data)
    if exito_key == "via_rapida":
        return procesar_rapido(data)
    return procesar_normal(data, exito_key)


# Después de refactor que eliminó la rama "via_rapida"
def procesar(data):
    exito, exito_key = intentar(data)   # ← F841: exito_key asignado, no usado
    return procesar_normal(data)         # ← también exito residual
```

### Patrón canónico

```python
# Post-refactor limpio
def procesar(data):
    _ = intentar(data)   # si el side effect de intentar() importa
    return procesar_normal(data)

# o mejor: si ni siquiera el side effect importa
def procesar(data):
    return procesar_normal(data)
```

### Reglas

- **Gate pre-commit**: agregar `ruff check --select=F841` al pre-commit hook del proyecto. No bloquea en un repo grande donde F841 preexiste, pero sí bloquea en los archivos modificados por el commit actual.
- **Revisión explícita post-refactor grande**: tras extraer 5+ métodos o mover 5+ bloques, correr `ruff --select=F401,F841` sobre los archivos tocados.
- **NO silenciar con `# noqa: F841` sin justificación** — la excepción legítima (side effect intencional con nombre `_`) es tan rara que vale la pena documentarla; silenciar sin comentario es ocultar deuda.
- **Incluir en checklist de PR**: "F841 limpio en archivos tocados" como item explícito.

### Aplicabilidad

Detectable en:

- Python: `ruff --select=F841`, `flake8 --select=F841`.
- TypeScript/JavaScript: `eslint --rule 'no-unused-vars: error'`.
- Go: el compilador lo hace obligatorio (`declared but not used`).
- Rust: warning `unused_variables`, promovido a error con `#![deny(unused_variables)]`.
