# Ejemplos MAL→BIEN — prevención de sobre-ingeniería

Catálogo de 9 ejemplos pareados (código que falla / código correcto) para los
4 principios del skill. Cada uno se mapea a un anti-patrón ya documentado en
`SKILL.md` y a un escenario operativo de SWL.

Adaptado de
[andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)
(MIT) al ecosistema SWL — español de México, escenarios concretos del sistema.

**Cómo usar este archivo**: cuando un agente SWL (`implementador-swl`,
`revisor-codigo-swl`, agentes de stack) detecta el anti-patrón en su sesión,
puede citar `recursos/EXAMPLES.md § <ejemplo>` como evidencia observable en
lugar de invocar la regla abstracta.

---

## Principio 1 — Pensar antes de codificar

### Ejemplo 1.1 — Supuestos ocultos sobre alcance

**Solicitud del usuario**: *"Agrega una feature para exportar datos de usuarios."*

#### ❌ MAL — asume sin preguntar

```python
def exportar_usuarios(formato='json'):
    """Exporta todos los usuarios a un archivo."""
    usuarios = Usuario.query.all()

    if formato == 'json':
        with open('usuarios.json', 'w') as f:
            json.dump([u.to_dict() for u in usuarios], f)
    elif formato == 'csv':
        with open('usuarios.csv', 'w') as f:
            writer = csv.DictWriter(f, fieldnames=['id', 'email', 'nombre'])
            writer.writeheader()
            writer.writerows([u.to_dict() for u in usuarios])

    return f"Exportados {len(usuarios)} usuarios"
```

**Problemas**:
- Asume que se deben exportar TODOS los usuarios (¿paginación? ¿privacidad?)
- Asume ubicación del archivo sin preguntar
- Asume campos a incluir (algunos podrían ser sensibles)
- Hardcodea fieldnames del CSV sin revisar la estructura real

#### ✅ BIEN — explicita supuestos antes de implementar

```
Antes de implementar, necesito clarificar:

1. **Alcance**: ¿exportar todos los usuarios o un subconjunto filtrado?
   (implicaciones de privacidad)
2. **Formato**: cuando dices "exportar", ¿te refieres a:
   - Descarga de archivo en el navegador?
   - Job en background con notificación por email?
   - Endpoint de API que devuelve los datos?
3. **Campos**: ¿qué campos del usuario? (algunos pueden ser sensibles —
   datos personales, hash de contraseña, tokens)
4. **Volumen**: ¿cuántos usuarios típicamente? (afecta el enfoque)

Enfoque más simple: agregar endpoint de API que devuelve JSON paginado.
Para exportación a archivo necesito más detalle.

¿Cuál prefieres?
```

**Aplicación SWL**: este patrón está documentado en el `Principio 1` del SKILL.md y
es exactamente lo que `/swl:discutir-fase` resuelve antes de que llegue a
`implementador-swl`.

---

### Ejemplo 1.2 — Múltiples interpretaciones, elegir en silencio

**Solicitud del usuario**: *"Haz la búsqueda más rápida."*

#### ❌ MAL — elige una interpretación y construye 200 LOC

```python
@lru_cache(maxsize=1000)
async def buscar(query: str) -> list[Resultado]:
    # ... 200 líneas: cache, índices, async, connection pooling ...
```

#### ✅ BIEN — presenta interpretaciones con costo estimado

```
"Más rápida" puede significar tres cosas distintas:

1. **Latencia menor por request** (de 500ms a <100ms)
   - Agregar índices de BD
   - Cachear queries frecuentes
   - Esfuerzo estimado: 2h

2. **Más throughput concurrente**
   - async/await + connection pooling
   - Esfuerzo estimado: 4h

3. **Velocidad percibida mejor** (UX)
   - Mostrar resultados parciales antes
   - Carga progresiva
   - Esfuerzo estimado: 3h

La búsqueda actual tarda ~500ms en queries típicas.
¿Cuál importa más para tu caso de uso?
```

**Aplicación SWL**: cuando el usuario invoca `/swl:planear-fase` con instrucción
vaga, el `planificador-swl` debe responder así antes de generar PLAN.md.

---

## Principio 2 — Simplicidad primero

### Ejemplo 2.1 — Abstracción prematura (Strategy pattern para un caso de uso)

**Solicitud del usuario**: *"Agrega una función para calcular descuento."*

#### ❌ MAL — Strategy + Factory + Config para una multiplicación

```python
from abc import ABC, abstractmethod
from dataclasses import dataclass

class EstrategiaDescuento(ABC):
    @abstractmethod
    def calcular(self, monto: float) -> float: ...

class DescuentoPorcentual(EstrategiaDescuento):
    def __init__(self, porcentaje: float):
        self.porcentaje = porcentaje
    def calcular(self, monto: float) -> float:
        return monto * (self.porcentaje / 100)

class DescuentoFijo(EstrategiaDescuento):
    def __init__(self, monto_fijo: float):
        self.monto_fijo = monto_fijo
    def calcular(self, monto: float) -> float:
        return min(self.monto_fijo, monto)

@dataclass
class ConfigDescuento:
    estrategia: EstrategiaDescuento
    compra_minima: float = 0.0
    descuento_maximo: float = float('inf')

class CalculadoraDescuento:
    def __init__(self, config: ConfigDescuento):
        self.config = config
    def aplicar(self, monto: float) -> float:
        if monto < self.config.compra_minima:
            return 0
        descuento = self.config.estrategia.calcular(monto)
        return min(descuento, self.config.descuento_maximo)

# 30+ líneas de setup para una multiplicación
```

#### ✅ BIEN — función directa

```python
def calcular_descuento(monto: float, porcentaje: float) -> float:
    """Calcula el descuento. porcentaje en rango 0-100."""
    return monto * (porcentaje / 100)

# Uso
descuento = calcular_descuento(100.0, 10.0)  # 10.0
```

**Cuándo agregar complejidad**: solo cuando aparezca el segundo o tercer tipo
de descuento. Si la necesidad llega después, refactorizar entonces.

**Aplicación SWL**: regla SKILL.md "Tres usos concretos justifican una
abstracción; menos de tres, no". Este ejemplo es la materialización del umbral.

---

### Ejemplo 2.2 — Features especulativas no solicitadas

**Solicitud del usuario**: *"Guarda las preferencias del usuario en la base de datos."*

#### ❌ MAL — cache, validator, merge, notificación

```python
class GestorPreferencias:
    def __init__(self, db, cache=None, validator=None):
        self.db = db
        self.cache = cache or CacheEnMemoria()
        self.validator = validator or ValidadorDefault()

    def guardar(self, usuario_id: int, prefs: dict,
                merge: bool = True,
                validar: bool = True,
                notificar: bool = False) -> bool:
        if validar:
            errores = self.validator.validar(prefs)
            if errores:
                raise ValidationError(errores)

        existentes = self.obtener(usuario_id) if merge else {}
        if merge:
            existentes.update(prefs)
            prefs = existentes

        self.db.execute(
            "UPDATE usuarios SET preferencias = ? WHERE id = ?",
            (json.dumps(prefs), usuario_id)
        )

        if self.cache:
            self.cache.set(f"prefs:{usuario_id}", prefs)

        if notificar:
            self.notificar_cambio(usuario_id, prefs)

        return True
```

#### ✅ BIEN — solo lo que pidieron

```python
def guardar_preferencias(db, usuario_id: int, preferencias: dict):
    """Guarda las preferencias del usuario en la base de datos."""
    db.execute(
        "UPDATE usuarios SET preferencias = ? WHERE id = ?",
        (json.dumps(preferencias), usuario_id)
    )
```

**Agregar después si aparece la necesidad**:
- Caching → cuando aparezca problema de performance medible
- Validación → cuando lleguen datos malformados a la BD
- Merging → cuando emerja el requisito explícito

**Aplicación SWL**: este es exactamente el escenario donde `revisor-codigo-swl`
debe emitir veto item DRY/sobre-ingeniería contra commits que agregan
parámetros opcionales sin requisito documentado.

---

## Principio 3 — Cambios quirúrgicos

### Ejemplo 3.1 — Drive-by refactoring (refactor de oportunidad)

**Solicitud del usuario**: *"Corrige el bug donde los emails vacíos rompen el validador."*

#### ❌ MAL — corrige el bug Y "mejora" todo lo demás

```diff
  def validar_usuario(datos_usuario):
-     # Verificar formato de email
-     if not datos_usuario.get('email'):
+     """Valida los datos del usuario."""
+     email = datos_usuario.get('email', '').strip()
+
+     # Validar email
+     if not email:
          raise ValueError("Email requerido")
-
-     # Validación básica de email
-     if '@' not in datos_usuario['email']:
+     if '@' not in email or '.' not in email.split('@')[1]:
          raise ValueError("Email inválido")

-     # Verificar nombre de usuario
-     if not datos_usuario.get('nombre_usuario'):
+     # Validar nombre de usuario
+     nombre_usuario = datos_usuario.get('nombre_usuario', '').strip()
+     if not nombre_usuario:
          raise ValueError("Nombre de usuario requerido")
+     if len(nombre_usuario) < 3:
+         raise ValueError("Nombre de usuario muy corto")
+     if not nombre_usuario.isalnum():
+         raise ValueError("Nombre de usuario debe ser alfanumérico")

      return True
```

**Problemas**:
- "Mejoró" la validación de email más allá del fix
- Agregó validación de username que nadie pidió
- Cambió comentarios
- Agregó docstring

#### ✅ BIEN — solo el fix necesario

```diff
  def validar_usuario(datos_usuario):
      # Verificar formato de email
-     if not datos_usuario.get('email'):
+     email = datos_usuario.get('email', '')
+     if not email or not email.strip():
          raise ValueError("Email requerido")

      # Validación básica de email
-     if '@' not in datos_usuario['email']:
+     if '@' not in email:
          raise ValueError("Email inválido")

      # Verificar nombre de usuario
      if not datos_usuario.get('nombre_usuario'):
          raise ValueError("Nombre de usuario requerido")

      return True
```

**Solo cambió**: las líneas específicas que corrigen el manejo del email vacío.

**Aplicación SWL**: regla del SKILL.md "Cada línea cambiada debe trazar
directamente al requisito del usuario". `revisor-codigo-swl` revisa cada línea
del diff contra esta regla.

---

### Ejemplo 3.2 — Style drift (cambiar estilo no solicitado)

**Solicitud del usuario**: *"Agrega logging a la función `subir_archivo`."*

#### ❌ MAL — reformatea todo: comillas, type hints, espaciado

```diff
- def subir_archivo(ruta, destino):
+ def subir_archivo(ruta: str, destino: str) -> bool:
+     """Sube archivo al destino con logging."""
+     logger.info(f"Subiendo {ruta} a {destino}")
+
      try:
-         with open(ruta, 'rb') as f:
-             datos = f.read()
-
-         response = requests.post(destino, files={'file': datos})
-
-         if response.status_code == 200:
-             return True
-         else:
-             return False
+         with open(ruta, "rb") as f:
+             datos = f.read()
+         response = requests.post(destino, files={"file": datos})
+         exito = response.status_code == 200
+
+         if exito:
+             logger.info(f"Subida exitosa: {ruta}")
+         else:
+             logger.error(f"Subida falló: {ruta}, status={response.status_code}")
+
+         return exito
      except Exception as e:
-         print(f"Error: {e}")
+         logger.exception(f"Error subiendo {ruta}: {e}")
          return False
```

**Problemas**:
- Cambió comillas de simples a dobles
- Agregó type hints que nadie pidió
- Agregó docstring
- Reformateó espaciado
- Cambió la lógica del return booleano

#### ✅ BIEN — respeta estilo existente

```diff
+ import logging
+
+ logger = logging.getLogger(__name__)
+
  def subir_archivo(ruta, destino):
+     logger.info(f'Iniciando subida: {ruta}')
      try:
          with open(ruta, 'rb') as f:
              datos = f.read()

          response = requests.post(destino, files={'file': datos})

          if response.status_code == 200:
+             logger.info(f'Subida exitosa: {ruta}')
              return True
          else:
+             logger.error(f'Subida falló: {ruta}, status={response.status_code}')
              return False
      except Exception as e:
-         print(f"Error: {e}")
+         logger.exception(f'Error subiendo: {ruta}')
          return False
```

**Respetó**: comillas simples, ausencia de type hints, patrón booleano existente,
estilo de espaciado.

**Aplicación SWL**: este caso es señal de alerta en el "Resumen de señales de
alerta" del SKILL.md ("Se modificaron archivos fuera del scope"). El hook
pre-commit y `revisor-codigo-swl` deben marcarlo.

---

## Principio 4 — Ejecución orientada a metas

### Ejemplo 4.1 — Plan vago vs criterios verificables

**Solicitud del usuario**: *"Arregla el sistema de autenticación."*

#### ❌ MAL — plan genérico sin criterios de éxito

```
Voy a arreglar el sistema de autenticación así:
1. Revisar el código
2. Identificar problemas
3. Hacer mejoras
4. Probar los cambios

[procede a hacer cambios sin criterio de éxito claro]
```

#### ✅ BIEN — pide la meta concreta, luego plan verificable

```
Para "arreglar autenticación" necesito definir el criterio de éxito.
¿Qué problema específico estamos resolviendo?

Si el problema es "los usuarios quedan logueados después de cambiar contraseña":

Plan:
1. Test: cambiar contraseña → verificar que la sesión vieja queda invalidada
   Verificación: el test FALLA (reproduce el bug)

2. Implementación: invalidar sesiones al cambiar contraseña
   Verificación: el test PASA

3. Casos edge: múltiples sesiones activas, cambios concurrentes
   Verificación: tests adicionales PASAN

4. Sin regresión: tests de auth existentes siguen pasando
   Verificación: suite completa en verde

Cobertura actual de auth: [N%]
¿Cuál es el problema específico que ves?
```

**Aplicación SWL**: `Skill("verificar-trabajo")` aplica este patrón con 4
niveles goal-backward (EXISTE → SUSTANTIVO → CONECTADO → DATOS_FLUYEN). El
ejemplo materializa el nivel "DATOS_FLUYEN" con tests verificables.

---

### Ejemplo 4.2 — Multi-paso con verificación incremental

**Solicitud del usuario**: *"Agrega rate limiting a la API."*

#### ❌ MAL — implementación completa en un solo commit de 300 LOC

```python
# Implementa rate limiting completo con Redis, múltiples estrategias,
# sistema de configuración y monitoreo en un solo commit de 300 líneas.
# Sin pasos de verificación claros.
```

#### ✅ BIEN — incremental con verificación por paso

```
Plan para rate limiting:

1. Rate limiting básico en memoria (un solo endpoint)
   Verificación:
   - Test: 100 requests → primeros 10 pasan, resto recibe 429
   - Manual: curl al endpoint 11 veces, ver error de rate limit

2. Extraer a middleware (aplicar a todos los endpoints)
   Verificación:
   - Test: rate limits aplican a /usuarios y /posts
   - Tests existentes de endpoints siguen pasando

3. Backend Redis (para multi-instancia)
   Verificación:
   - Test: rate limit persiste entre restarts de la app
   - Test: dos instancias comparten el contador

4. Configuración (rates por endpoint)
   Verificación:
   - Test: /search permite 10/min, /usuarios permite 100/min
   - Archivo de config parseado correctamente

Cada paso es verificable y desplegable independientemente.
¿Empezamos con el paso 1?
```

**Aplicación SWL**: este patrón es el core de `/swl:ejecutar-fase` con commits
atómicos por tarea. Cada paso del plan corresponde a un commit verificable.

---

### Ejemplo 4.3 — Test-first: reproducir antes de corregir

**Solicitud del usuario**: *"El ordenamiento se rompe cuando hay scores duplicados."*

#### ❌ MAL — corrige sin reproducir primero

```python
# Cambia inmediatamente la lógica de sort sin confirmar el bug
def ordenar_scores(scores):
    return sorted(scores, key=lambda x: (-x['score'], x['nombre']))
```

#### ✅ BIEN — reproduce con test, luego corrige

```python
# 1. Primero: test que reproduce el problema
def test_ordenar_con_scores_duplicados():
    """Verifica el ordenamiento cuando varios items tienen el mismo score."""
    scores = [
        {'nombre': 'Ana', 'score': 100},
        {'nombre': 'Bruno', 'score': 100},
        {'nombre': 'Carlos', 'score': 90},
    ]

    resultado = ordenar_scores(scores)

    # Bug: el orden es no determinista para duplicados
    # Corriendo este test varias veces debería ser consistente
    assert resultado[0]['score'] == 100
    assert resultado[1]['score'] == 100
    assert resultado[2]['score'] == 90

# Verificación: corre el test 10 veces → falla con orden inconsistente

# 2. Ahora sí, fix con sort estable
def ordenar_scores(scores):
    """Ordena por score descendente, luego nombre ascendente para empates."""
    return sorted(scores, key=lambda x: (-x['score'], x['nombre']))

# Verificación: test pasa consistentemente
```

**Aplicación SWL**: regla del SKILL.md "Test primero para bugs". `tdd-qa-swl`
con `Skill("tdd-workflow")` aplica este ciclo RED→GREEN→REFACTOR.

---

## Resumen de anti-patrones

| Principio | Anti-patrón | Fix |
|---|---|---|
| Pensar antes | Asume en silencio formato, campos, alcance | Listar supuestos explícitamente, preguntar |
| Simplicidad | Strategy pattern para un solo cálculo | Una función hasta que la complejidad sea real |
| Quirúrgico | Reformatea comillas, agrega type hints mientras corrige bug | Solo las líneas que arreglan el bug reportado |
| Orientado a metas | "Voy a revisar y mejorar el código" | "Test que reproduce X → hacerlo pasar → sin regresiones" |

---

## Insight clave

Los ejemplos "sobre-complicados" no son obviamente incorrectos: siguen design
patterns y buenas prácticas. El problema es el **timing**: agregan complejidad
antes de que se necesite, lo cual:

- Hace el código más difícil de entender
- Introduce más bugs
- Toma más tiempo implementar
- Es más difícil de probar

Las versiones "simples" son:
- Más fáciles de entender
- Más rápidas de implementar
- Más fáciles de probar
- Refactorizables después cuando la complejidad realmente se necesite

> **Buen código es código que resuelve el problema de hoy de forma simple,
> no el problema de mañana de forma prematura.**

---

## Origen y licencia

Adaptado de [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)
(MIT License, Andrej Karpathy + Forrest Chang). El skill SWL
`prevencion-sobreingenieria` consolida los 4 principios + sección propia
"Variables residuales post-refactor" + calibración para tasks triviales.
