---
name: extractor-de-aprendizajes
description: Convertir errores y patrones descubiertos durante la implementación en nuevas habilidades o reglas. Ciclo de mejora continua del sistema SWL.
version: "1.0.5"
herramientasPermitidas: [Read]
exclusiones:
  - "No cargar para actualizar el perfil del usuario — las correcciones explícitas del usuario van a `instintos/perfil-usuario.yaml` vía `perfilador-usuario-swl`, no a APRENDIZAJES.md."
  - "No cargar para buscar aprendizajes ya documentados; usar `memoria-busqueda` que hace búsqueda eficiente sin cargar el archivo completo."
  - "No cargar para crear un skill nuevo desde cero de forma interactiva; usar `/swl:crear-skill` que guía el proceso con validaciones."
  - "No cargar si el aprendizaje no tiene causa raíz identificada — documentar síntoma sin causa produce reglas que no previenen el error real."
evolvable: true  # default para skill estandar
evolved: true
evolved-from: "1.0.4"
evolved-at: "2026-05-12"
evolved-by: "aprender"
evolved-note: "Backport L-140 SIGM: extender gotcha sub-agentes a Modo C (revisor-codigo-swl reporta ubicaciones incorrectas y severidades subestimadas); CONFIRMADO x4 → x5"
---
# Extractor de Aprendizajes

## Propósito

Cada error cometido durante la implementación, cada bug inesperado, cada patrón que
funcionó mejor de lo esperado, es una oportunidad de mejorar el sistema. Este skill
define el proceso para convertir experiencia concreta en conocimiento estructurado
que previene errores futuros.

---

## El ciclo de mejora continua

```
Implementación
     ↓
Error / Insight descubierto
     ↓
Clasificar: ¿es un anti-patrón, un patrón nuevo, o una regla de proyecto?
     ↓
Documentar en la estructura correcta
     ↓
El verificador lo usa en la siguiente iteración
     ↓
Errores prevenidos en el futuro
```

---

## Tipos de aprendizajes

### Tipo 1: Anti-patrón (error a prevenir)

Se descubrió algo que parece correcto pero falla en producción o en testing.
Debe convertirse en una **regla negativa** ("NUNCA hagas X").

**Indicadores**:
- Un test falló por una razón no obvia.
- Un bug tardó más de 30 minutos en diagnosticarse.
- Se asumió algo sobre una librería que resultó ser incorrecto.

### Tipo 2: Patrón positivo (mejor práctica)

Se encontró una forma de hacer algo que resuelve un problema recurrente con
elegancia. Debe convertirse en una **regla positiva** ("SIEMPRE usa X para Y").

**Indicadores**:
- Se refactorizó código que inicialmente era verboso y quedó mucho más limpio.
- Una solución resolvió 3 problemas distintos a la vez.
- El code review elogió específicamente un patrón usado.

### Tipo 3: Gotcha de librería o framework

El comportamiento de una dependencia no era el esperado según la documentación o
la intuición. Debe convertirse en una **nota de advertencia** en el skill relevante.

**Indicadores**:
- "La documentación dice X pero en realidad hace Y".
- El comportamiento cambia según la versión de la dependencia.
- Hay una edge case no documentada.

### Tipo 4: Decisión de proyecto

Se tomó una decisión de arquitectura o diseño que debe respetarse en el futuro.
Debe registrarse en **DECISIONS.md** del proyecto.

---

## Protocolo de extracción

### Paso 1: Capturar el contexto del error

Cuando se descubre un error o insight, documentarlo inmediatamente con:

```markdown
## Aprendizaje: [título corto]

**Fecha**: 2026-03-25
**Contexto**: Implementando endpoint POST /facturas en FastAPI con SQLAlchemy async.
**Error observado**: MissingGreenlet al acceder a factura.usuario.nombre en el schema Pydantic.
**Causa raíz**: La relación `usuario` no tenía selectinload() en el query. SQLAlchemy async
no puede hacer lazy loading fuera de una sesión async activa.
**Solución aplicada**: Agregar `.options(selectinload(Factura.usuario))` al query.
**Clasificación**: Anti-patrón → Gotcha de SQLAlchemy async
```

### Paso 2: Determinar el destino del aprendizaje

| Tipo de aprendizaje | Destino |
|--------------------|---------|
| Regla universal de Python | `habilidades/patrones-python/SKILL.md` |
| Gotcha de FastAPI | `habilidades/fastapi-experto/SKILL.md` |
| Gotcha de SQLAlchemy | `habilidades/fastapi-experto/SKILL.md` o `CLAUDE.md` del proyecto |
| Regla de diseño de API | `habilidades/api-rest-diseno/SKILL.md` |
| Regla de testing | `habilidades/testing-python/SKILL.md` |
| Decisión de proyecto específico | `DECISIONS.md` del proyecto |
| Regla que aplica a TODOS los proyectos | `CLAUDE.md` del sistema SWL |

### Paso 3: Escribir la regla en el formato correcto

#### Formato para regla negativa (anti-patrón)

```markdown
### NUNCA: [título del anti-patrón]

**Problema**: Descripción clara de qué falla y por qué.

```python
# MAL — esto causa [error específico]
query = select(Factura).where(Factura.id == factura_id)
result = await db.execute(query)
factura = result.scalar_one()
# Acceder a factura.usuario aquí causa MissingGreenlet
print(factura.usuario.nombre)
```

```python
# BIEN — con selectinload explícito
query = (
    select(Factura)
    .where(Factura.id == factura_id)
    .options(selectinload(Factura.usuario))
)
result = await db.execute(query)
factura = result.scalar_one()
print(factura.usuario.nombre)  # Funciona correctamente
```
```

#### Formato para regla positiva (mejor práctica)

```markdown
### SIEMPRE: [título de la mejor práctica]

**Cuándo aplicar**: Descripción de la situación.
**Beneficio**: Por qué esta forma es mejor.

```python
# Patrón correcto con ejemplo concreto
```
```

### Paso 4: Integrar la regla al skill correspondiente

1. Abrir el SKILL.md del skill donde debe vivir la regla.
2. Agregar la regla en la sección más relevante.
3. Si no hay sección relevante, crear una nueva sección "Gotchas y casos especiales".
4. Hacer commit del cambio al skill con mensaje:
   ```
   docs(skills): agrega regla sobre selectinload en SQLAlchemy async
   ```

---

## Plantilla de nuevo skill desde cero

Si el aprendizaje no encaja en ningún skill existente, crear uno nuevo:

```
habilidades/
└── nuevo-skill/
    └── SKILL.md
```

```yaml
---
name: nuevo-skill
description: Una línea describiendo cuándo activar este skill.
---

# Título del Skill

## Cuándo activar
- Caso 1 donde este skill es relevante
- Caso 2 donde este skill es relevante

## Reglas fundamentales

### Regla 1
...

### Regla 2
...

## Anti-patrones

### NUNCA: Anti-patrón 1
...

## Referencia rápida
...
```

---

## Indicadores de calidad de un aprendizaje bien documentado

Un aprendizaje está bien documentado si:

- [ ] Tiene un ejemplo de código concreto (MAL vs. BIEN).
- [ ] La causa raíz está explicada, no solo el síntoma.
- [ ] Es accionable: quien lo lee sabe exactamente qué hacer o no hacer.
- [ ] Está en el skill correcto (no en un lugar genérico).
- [ ] El título es buscable (contiene la tecnología y el patrón).

---

## Frecuencia de extracción

| Momento | Acción |
|---------|--------|
| Al terminar una tarea | Revisar si hubo algún insight que documentar |
| Al resolver un bug difícil | OBLIGATORIO documentar causa raíz y solución |
| Al hacer code review | Documentar patrones observados |
| Al finalizar una fase | Revisar ESTADO.md y DECISIONS.md, promover aprendizajes relevantes |
| Al finalizar el proyecto | Revisar todos los aprendizajes y consolidarlos en los skills |

---

## Vigencia temporal de aprendizajes (patrón Zep)

Cada aprendizaje debe llevar timestamps de vigencia para evitar acumular
conocimiento obsoleto:

```markdown
## Aprendizaje: [título]

**Fecha**: 2026-04-15
**valid_at**: 2026-04-15          <!-- Cuándo empezó a ser válido -->
**invalid_at**: null              <!-- null = vigente actualmente -->
**rating**: HIGH | MEDIUM | LOW   <!-- Impacto del aprendizaje -->
**Contexto**: ...
**Solución**: ...
```

### Reglas de vigencia

- `valid_at` es obligatorio — siempre poner la fecha de descubrimiento
- `invalid_at = null` → el aprendizaje sigue vigente
- Cuando un aprendizaje queda obsoleto (ej: se actualizó la librería), marcar `invalid_at`
- Al consultar aprendizajes, por defecto solo mostrar los vigentes (`invalid_at = null`)
- Nunca borrar aprendizajes obsoletos — solo marcarlos para preservar el historial

### Clasificación automática por impacto (fact rating)

| Rating | Criterio | Acción |
|--------|----------|--------|
| **HIGH** | Decisión irreversible, bug crítico, cambio de patrón mayor | Promover a CLAUDE.md o regla del sistema |
| **MEDIUM** | Gotcha documentado, patrón confirmado x2, anti-patrón operativo | Integrar en skill correspondiente |
| **LOW** | Observación contextual, preferencia menor, dato informativo | Mantener en APRENDIZAJES.md, no promover |

**Mapeo a modelo 4-tier:**
- HIGH → Tipo A (verdad estructural, permanente)
- MEDIUM → Tipo B (gotcha operativo, potencialmente temporal)
- LOW → Tipo C (preferencia, configuración, efímera)

### Consolidación con vigencia

Durante `/swl:aprender`, aplicar estas reglas:

1. Si un aprendizaje existente tiene `invalid_at` fecha pasada → archivar (mover a sección "Historial")
2. Si dos aprendizajes se contradicen → el más reciente marca al anterior como `invalid_at = hoy`
3. Si un aprendizaje MEDIUM se confirma x3 → promover a HIGH
4. Si un aprendizaje LOW no se referencia en 30 días → candidato a archivo

---

## Cuándo NO cargar

- La señal es una corrección del usuario sobre comportamiento del agente (no un error técnico); eso va al perfil de usuario, no a APRENDIZAJES.md.
- Se busca recuperar aprendizajes anteriores para contextualizarlos; usar `memoria-busqueda` — es más eficiente que leer APRENDIZAJES.md directamente.
- Se quiere crear un skill nuevo desde cero; usar `/swl:crear-skill` que tiene validaciones de frontmatter, nombre de slot y criterios SAP incorporados.

## Gotchas / Errores comunes no obvios

- **Aprendizaje documentado sin `valid_at`**: el agente crea la entrada en APRENDIZAJES.md sin el campo de vigencia temporal, imposibilitando la consolidación automática futura. Causa: el campo se considera opcional. Solución: `valid_at` es obligatorio — poner siempre la fecha de descubrimiento; sin él el aprendizaje no participa en la lógica de vigencia del patrón Zep.
- **Canal incorrecto para correcciones del usuario**: el usuario dice "no uses emojis en los commits" y el agente lo registra en APRENDIZAJES.md como anti-patrón. Causa: se confundió una corrección comportamental (va al perfil) con un anti-patrón técnico (va a APRENDIZAJES.md). Solución: aplicar el árbol de decisión de `reglas/memoria-consolidada.md` antes de escribir — si el dato cambia cuando el usuario cambia, es del perfil; si el dato es verdad técnica independiente del usuario, es de APRENDIZAJES.md.
- **Aprendizaje en DECISIONS.md cuando debería estar en el skill**: una gotcha de SQLAlchemy async se documenta en DECISIONS.md del proyecto en lugar de en `fastapi-experto/SKILL.md`. Causa: se escogió el canal de menor fricción en lugar del canal correcto. Solución: verificar la tabla de destinos del Paso 2 — gotchas de librería van al skill del framework correspondiente, no a DECISIONS.md del proyecto.
- **`rating: HIGH` asignado sin verificar criterio de irreversibilidad**: el agente promueve a CLAUDE.md un aprendizaje "MEDIUM" por el entusiasmo del momento. Causa: no se aplicó el criterio de "decisión irreversible o bug crítico". Solución: antes de asignar HIGH, verificar: ¿cambiar esto en el futuro requeriría refactorizar múltiples archivos o migrar datos? Si no, mantener MEDIUM.
- **Regla incompleta sobre registro de hooks**: una entrada en memoria dice "registrar en modulos.json" pero omite `hooks-config.json`, y en la siguiente iteración se repite el fallo en CI porque ambos manifiestos son obligatorios. Causa: la regla de la lección anterior no cubrió todos los manifiestos afectados. Solución: al documentar una regla sobre registro en manifiestos, listar EXPLÍCITAMENTE cada manifiesto con su responsabilidad distinta (`modulos.json` = qué copiar; `hooks-config.json` = cómo registrar evento). Evidencia: tres incidentes históricos del mismo patrón incompleto (v5.7.1, v5.7.2/3, v5.11.0).
- **Inventario estimado a mano en vez de regenerar**: el agente cuenta "28 hooks" visualmente y propaga la cifra a 5 archivos (CLAUDE/README/package/plugin/SALUD); al regenerar con `scripts/generar-inventario.js` el número real es 30. Causa: confiar en la observación directa en vez de la fuente de verdad determinista. Solución: antes de modificar cualquier contador en documentación oficial, ejecutar el script de inventario y usar su salida como ground truth.
- **Sub-agentes (Explore + revisores) reportan afirmaciones factuales no verificadas sobre el código** [CONFIRMADO x5]: aplica a `Explore`, `revisor-codigo-swl`, `revisor-seguridad-swl`, `investigador-swl`, `planificador-swl`, y en general a cualquier sub-agente que reporte hallazgos sobre código del proyecto destino. **Tres modos de falla observados**:

  **Modo A — papers académicos** (sesión 2026-04-25): sub-agente Explore propuso 50h+ para implementar SPRT + Lyapunov + compositionality theorem del paper Bhardwaj 2026; costo real validado fue ~5h (solo Drift Score + Recovery Catalog). Causa: el Explore evalúa portabilidad técnica sin aplicar filtro de "datos disponibles" ni "infraestructura zero-deps". Evidencia: 3 papers analizados (evolver, Bhardwaj 2026, Zhang et al. 2026) con descarte sistemático ~70-90% del contenido propuesto.

  **Modo B — repositorios externos** (sesión 2026-05-02 cosecha-temp/ EMAIA): sub-agente Explore analizando `temp/docetl-main` (UC Berkeley, arXiv:2410.12189) afirmó dos cosas FALSAS verificables contra el código del proyecto destino: (1) "EMAIA ya usa LiteLLM" — verificación contra `pyproject.toml` + `grep -r litellm core/` mostró que EMAIA tiene clientes propios (`OllamaClient`/`NIMClient`/`vLLMClient`) y NO usa LiteLLM en absoluto; (2) recomendó portar Gleaning como patrón nuevo, cuando `core/learning/evaluator_optimizer.py` y `core/llm/quality_judge.py` YA implementan Anthropic Evaluator-Optimizer + LLM-as-judge. El fix real era WIRING (~30 LOC), no porting. Costo "MEDIO" del agente quedó como BAJO real. Causa: el Explore evalúa el repo externo en aislamiento, sin leer paths del proyecto destino para verificar qué mecanismos ya existen.

  **Modo C — revisores reportan ubicaciones y severidades incorrectas en código del proyecto** (sesión 2026-05-12 SIGM): `revisor-codigo-swl` ejecutado en `/swl:verificar --until-converge` reportó dos errores factuales: (1) **ubicación incorrecta**: "deferred import en `caja/router.py:545`" cuando la línea real estaba en `caja/service.py:545` — el archivo `caja/router.py` solo tiene 157 líneas (`wc -l caja/router.py = 157`); (2) **severidad subestimada**: reportó "mismatch `CobroConsolidadoResponse` en pantalla de éxito" como MAYOR, mi auditoría manual descubrió que `CobroConsolidadoRequest` también estaba completamente desalineado (más grave, debería ser CRÍTICO bloqueante) — el revisor solo miró el Response. Causa: el revisor analiza código sin re-verificar el path/línea/extensión reportada cuando produce el reporte; sub-agentes basados en LLM hallucinan números de línea con frecuencia ≥15% en archivos grandes.

  **Solución unificada — protocolo de verificación de afirmaciones factuales antes de aceptar propuesta de CUALQUIER sub-agente**:

  *Patrón obligatorio aplicable a TODOS los modos*: extraer 2-3 afirmaciones factuales del reporte del sub-agente y verificar cada una con primitivas baratas (`Grep`/`Read`/`wc -l`/`ls`/`git log`) ANTES de aceptar el plan o acción. Tipos de afirmaciones a verificar:

  - **Ubicaciones de código** (`archivo.py:123`): verificar `wc -l archivo.py` muestra ≥123, y `sed -n '120,125p' archivo.py` muestra el contenido reportado.
  - **Claims tipo "X usa Y" o "Z no existe"**: `grep -l Y archivo` o `find . -name "Z*"` para confirmar.
  - **Severidad y alcance**: si el revisor reporta "MAYOR" pero el bug afecta el happy path, re-clasificar a CRÍTICO manualmente.

  *Para papers académicos (Modo A)*: 3 filtros adicionales: (1) ¿requiere SMT solver / SPRT / Lyapunov / DTMC / formal verification? → descartar (rompe zero-deps); (2) ¿requiere N>100 sesiones de campo para validación estadística? → descartar; (3) ¿genera valor accionable HOY o solo elegancia matemática? → solo implementar si HOY.

  *Para repos externos (Modo B)*: 4 filtros adicionales: (1) ¿qué % es teoría/código no-portable vs portable? — si >70% no-portable, recortar; (2) **¿la propuesta reescribe mecanismos existentes en el proyecto destino?** — VERIFICAR con `Grep`/`Read` 2-3 afirmaciones concretas del agente contra el código real ANTES de aceptar; (3) ¿LOC nuevas estimadas vs reutilizar lo existente? — si la propuesta supera 500 LOC para un solo patrón, hay sobre-ingeniería; (4) ¿el alcance reducido cubre 80% del valor? — Pareto.

  *Para revisores de código (Modo C)*: además del patrón obligatorio, **re-clasificar severidad cuando el revisor solo miró parte del flujo**. Si el revisor marca MAYOR un bug del Response, verificar si el Request también está afectado — los happy paths típicamente involucran ambos lados.
- **Hooks de calidad pre-commit bloquean fixtures de tests como falsos positivos**: el hook `calidad-pre-commit.js` aplica regex `\b(api_key|password|token|secret)\s*[=:]\s*["'][^"'\s]{4,}["']` que matchea fixtures legítimos en archivos de test. Caso real: test que valida que la función `sanitizar()` redacta `api_key="abc12345xyz"` se bloquea. Causa: el hook no distingue contexto de test vs producción. Solución: en archivos de test, construir fixtures con concatenación de strings (`'api' + '_key'`, `'pass' + 'word'`) o agregar marcador placeholder reconocido por el hook (`fake_`, `dummy_`, `placeholder`, `example`, `os.environ`). NUNCA bypassear el hook con `--no-verify` — el detector cumple su función; ajustar el fixture es lo correcto.
- **Regex de path-matching `/[\\/]<dir>[\\/]/` falla con paths relativos sin slash inicial** [CONFIRMADO x4]: hooks que filtran archivos por directorio (ej: `hooks/extraccion-aprendizajes.js` con `PATRONES_ARCHIVO_SWL_EXCLUIDO`) usan patrones que requieren un separator ANTES del nombre. Caso real: path `scripts/lib/foo.js` (sin slash inicial) eludía el filtro de exclusión y generaba placeholders espurios en APRENDIZAJES.md cada vez que se hacía `Edit` o `Write` sobre archivos del sistema SWL. Causa: el regex `[\\/]` requiere un caracter slash/backslash previo; cuando el path empieza con el nombre del directorio directamente, no matchea. Solución: usar `/(?:^|[\\/])<dir>[\\/]/` para aceptar tanto el inicio del string como un separator previo. Evidencia: 4 placeholders eliminados manualmente entre v1.3.3-v1.3.5 antes de identificar la causa raíz; fix endurecido aplicado en v1.3.5 (Fix I).
- **`git ls-files` es preferible a `fs.readdir` recursivo para tests anti-regresión que escanean el repo**: usar `execSync('git ls-files')` limita el escaneo a archivos versionados — evita `node_modules/`, `.git/`, `_userland/`, archivos temporales y backups sin enumerar exclusiones. Caso real: `tests/scripts/no-legacy-npx-pattern.test.js` v1.3.6 escanea 1000+ archivos versionados en <200ms; el equivalente con `fs.readdir` recursivo más exclusiones manuales sería más lento y propenso a olvidar paths. Cuando el test es de "calidad del repo" (no de comportamiento), `git ls-files` es la primitiva correcta.
- **Whitelist enumerado versión-por-versión en tests anti-regresión genera deuda perpetua**: un whitelist tipo `RELEASE-NOTES-v1.3.[0-5].md` requiere agregar una entrada con cada release nueva — el test fallará al publicar v1.3.6, v1.3.7, etc. Caso real: v1.3.6 publicó a GitHub Packages pero falló en npmjs porque el whitelist v1.3.5 no cubría el nuevo `RELEASE-NOTES-v1.3.6.md`. Causa: enumeración cuando la categoría es semánticamente catch-all. Solución: si el archivo es histórico inmutable por definición (release notes, ADRs, changelog entries cerrados), usar patrón catch-all `^RELEASE-NOTES-.*\.md$` desde el inicio. Aplicar regla: *si cada release nueva agregaría entry al whitelist, el whitelist está mal diseñado.*
- **Fix combinado en dos capas (específico A + general B) es más robusto que cada uno aislado** [PATRÓN VALIDADO]: cuando un bug tiene múltiples vectores de entrada, aplicar dos defensas con cobertura distinta. Caso real: `hooks/extraccion-aprendizajes.js` v1.3.4 — Capa A: lista de archivos meta excluidos (`RELEASE-NOTES-*.md`, `RESUMEN.md`, etc.) que filtra por path; Capa B: detector estructural de "contexto solo headings" (>60% líneas son `##`/`###`) que filtra por contenido. Si A falla (archivo con nombre nuevo no en lista), B protege. Si B falsea (heading legítimo en bug real), A asegura. Patrón aplicable a cualquier filtro de seguridad/calidad donde el espacio de inputs hostiles es abierto.

## Anti-patrones del proceso de extracción

- **Documentar en el momento incorrecto**: Hacerlo DURANTE o inmediatamente DESPUÉS del
  error, no días después cuando el contexto se pierde.
- **Ser demasiado genérico**: "No cometer errores en SQLAlchemy" no es útil.
  "NUNCA acceder a relaciones lazy en async fuera de session" sí lo es.
- **Duplicar reglas**: Antes de agregar una regla, buscar si ya existe en el skill.
- **No incluir ejemplo de código**: Las reglas sin código concreto se olvidan.
- **Documentar en DECISIONS.md lo que debería estar en el skill**: Las decisiones de
  proyecto van en DECISIONS.md; los patrones reutilizables van en skills.
