@saulwade/swl-ses 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CLAUDE.md +5 -3
  2. package/README.md +3 -3
  3. package/bin/swl-mcp-server.js +187 -0
  4. package/habilidades/benchmark-memoria/SKILL.md +186 -0
  5. package/habilidades/contenedores-docker/SKILL.md +8 -1
  6. package/habilidades/datos-etl/SKILL.md +18 -1
  7. package/habilidades/eval-framework/SKILL.md +212 -0
  8. package/habilidades/memoria-busqueda/SKILL.md +24 -1
  9. package/habilidades/planear-fase/SKILL.md +299 -269
  10. package/habilidades/postgresql-experto/SKILL.md +24 -1
  11. package/habilidades/verificar-trabajo/SKILL.md +7 -1
  12. package/hooks/lib/evolution-tracker.js +65 -11
  13. package/hooks/lib/memory-search.js +44 -13
  14. package/hooks/sugerir-contribuir.js +226 -0
  15. package/manifiestos/hooks-config.json +9 -0
  16. package/manifiestos/modulos.json +36 -2
  17. package/manifiestos/perfiles.json +2 -1
  18. package/manifiestos/skills-lock.json +1 -1
  19. package/package.json +4 -3
  20. package/plugin.json +343 -343
  21. package/reglas/analisis-previo-tareas-grandes.md +172 -0
  22. package/reglas/arreglar-al-detectar.md +147 -0
  23. package/scripts/benchmark-memoria.js +167 -0
  24. package/scripts/detectar-aprendizajes-duplicados.js +151 -0
  25. package/scripts/lib/benchmark-metrics.js +160 -0
  26. package/scripts/lib/eval-metrics-store.js +218 -0
  27. package/scripts/lib/eval-quality.js +171 -0
  28. package/scripts/lib/eval-schemas.js +144 -0
  29. package/scripts/lib/eval-self-correct.js +106 -0
  30. package/scripts/lib/eval-validator.js +185 -0
  31. package/scripts/lib/jaccard-similarity.js +98 -0
  32. package/scripts/lib/longmemeval-runner.js +125 -0
  33. package/scripts/lib/rrf-fusion.js +175 -0
  34. package/scripts/lib/scoring-instintos.js +40 -3
  35. package/scripts/mcp-server/README.md +128 -0
  36. package/scripts/mcp-server/handlers.js +206 -0
  37. package/scripts/run-eval.js +141 -0
package/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- # CLAUDE.md — @saulwade/swl-ses v1.1.3
1
+ # CLAUDE.md — @saulwade/swl-ses v1.2.0
2
2
 
3
3
  ## Reglas de máxima prioridad (aplican SIEMPRE, sin excepción)
4
4
 
@@ -23,7 +23,7 @@ El Read tool sigue siendo correcto para `.pdf` (≤20 páginas), `.md`, `.txt` y
23
23
  ## Qué es este repositorio
24
24
 
25
25
  Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo).
26
- 11 lenguajes, 5 runtimes, 59 agentes, 151 skills, 42 comandos, 62 reglas, 39 hooks.
26
+ 11 lenguajes, 5 runtimes, 59 agentes, 153 skills, 42 comandos, 64 reglas, 40 hooks.
27
27
  **Idioma**: 100% español (México) para componentes SWL y skills Anthropic en inglés.
28
28
 
29
29
  ## Estructura del repositorio
@@ -87,7 +87,7 @@ scripts/ bin/ _userland/ .claude/ .planning/
87
87
  | `/swl:contribuir` | Contribuir evoluciones de _userland/ al core vía PR (filtro dominio + PluginEval ≥80) |
88
88
  | `/swl:ayuda` | Ayuda interactiva: catálogo, detalle de comando, búsqueda por keyword |
89
89
 
90
- ## Reglas obligatorias (22 base + 40 por lenguaje)
90
+ ## Reglas obligatorias (24 base + 40 por lenguaje)
91
91
 
92
92
  | Regla | Carga cuando |
93
93
  |-------|-------------|
@@ -111,6 +111,8 @@ scripts/ bin/ _userland/ .claude/ .planning/
111
111
  | `patrones.md` | Patrones de diseño y arquitectura |
112
112
  | `testing.md` | test*/, *.test.*, *.spec.* |
113
113
  | `usar-context7.md` | OBLIGATORIA al generar código que importe librerías/frameworks externos. Aplica a `backend-*-swl`, `frontend-*-swl`, `mobile-*-swl`, `implementador-swl`, `llm-apps-swl`, `pagos-swl`, `datos-swl`. Verifica versión actual y deprecaciones via Context7 MCP antes de agregar deps o generar imports. |
114
+ | `arreglar-al-detectar.md` | Siempre — al detectar cualquier error, bug, inconsistencia o anomalía durante cualquier trabajo: informar y resolver en el mismo turno, sin deuda silenciosa ni bypass |
115
+ | `analisis-previo-tareas-grandes.md` | Cuando la solicitud implica >10 archivos, >500 LOC, cross-módulo, porte de sistema o repo en `temp/`: auditar primero, presentar tabla comparativa + 3 opciones de alcance, esperar confirmación |
114
116
 
115
117
  Reglas por lenguaje (5 por lenguaje × 8 lenguajes): Java, Go, Rust, C#, Kotlin, Swift, PHP, Next.js — en `reglas/` con subdirectorios.
116
118
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # swl-ses v1.1.3
1
+ # swl-ses v1.2.0
2
2
 
3
3
  > El paquete anterior `@saulwadeleon/swl-software-engineering-system` está deprecado. Migrar a `@saulwade/swl-ses` (npmjs.org canónico) o `@saul-wade/swl-ses` (mirror en GitHub Packages) — el CLI `swl-ses` no cambia.
4
4
 
@@ -177,7 +177,7 @@ claude
177
177
  | `mobile` | Android + iOS + React Native/Flutter + UX |
178
178
  | `devops` | CI/CD + cloud + observabilidad + releases + seguridad |
179
179
  | `polyglot` | Todos los lenguajes: 11 lenguajes + revisores + build resolvers |
180
- | `completo` | Todo: 59 agentes + 151 habilidades + 42 comandos + 62 reglas + 39 hooks |
180
+ | `completo` | Todo: 59 agentes + 153 habilidades + 42 comandos + 64 reglas + 40 hooks |
181
181
 
182
182
  ### Targets soportados
183
183
 
@@ -478,7 +478,7 @@ swl-ses/
478
478
  seguridad.js # Validaciones de seguridad
479
479
  manifiestos/ # Perfiles y módulos de instalación
480
480
  agentes/ # 59 agentes especializados
481
- habilidades/ # 151 habilidades modulares
481
+ habilidades/ # 153 habilidades modulares
482
482
  comandos/swl/ # 42 comandos slash
483
483
  reglas/ # 20 reglas base + 40 por lenguaje
484
484
  hooks/ # 39 hooks + 62 librerías en hooks/lib/
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * swl-mcp-server — Servidor MCP **EXPERIMENTAL** para exponer la memoria
6
+ * de swl-ses a clientes MCP externos (Cursor, Gemini CLI, OpenCode, etc.).
7
+ *
8
+ * **NO PRODUCCIÓN — STUB EXPERIMENTAL**.
9
+ * Ver `scripts/mcp-server/README.md` para limitaciones detalladas.
10
+ *
11
+ * Modo de transporte: stdio (JSON-RPC sobre stdin/stdout).
12
+ * No HTTP, no auth, no rate limiting.
13
+ *
14
+ * Uso (cliente MCP):
15
+ * - Configurar el cliente para ejecutar `node /path/to/swl-ses/bin/swl-mcp-server.js`
16
+ * con stdio.
17
+ * - Los handlers leen el cwd del proceso para localizar `.planning/`,
18
+ * `instintos/`, `APRENDIZAJES.md`. Por defecto usa `process.cwd()`.
19
+ * - Override con env var `SWL_MCP_BASE_DIR` si el cliente arranca el server
20
+ * desde otro directorio.
21
+ *
22
+ * Protocolo MCP soportado (subset):
23
+ * - initialize / initialized
24
+ * - tools/list
25
+ * - tools/call
26
+ *
27
+ * NO soporta:
28
+ * - resources/list, prompts/list
29
+ * - logging, sampling
30
+ * - cancellation, progress
31
+ * - HTTP transport
32
+ *
33
+ * Trigger documentado para implementación completa: "uso ≥2 runtimes
34
+ * diferentes (Cursor + Claude Code o similar) consistentemente por
35
+ * ≥1 mes". Hoy: 0 instalaciones reportadas.
36
+ */
37
+
38
+ const path = require('path');
39
+
40
+ const { HANDLERS } = require('../scripts/mcp-server/handlers');
41
+
42
+ const SERVER_NAME = 'swl-mcp-server';
43
+ const SERVER_VERSION = '0.1.0-experimental';
44
+ const PROTOCOL_VERSION = '2024-11-05';
45
+
46
+ const baseDir = process.env.SWL_MCP_BASE_DIR || process.cwd();
47
+
48
+ // ── logging ───────────────────────────────────────────────────────────────────
49
+
50
+ // Stderr para evitar contaminar stdout (que es JSON-RPC).
51
+ function log(level, msg, data) {
52
+ const linea = JSON.stringify({
53
+ timestamp: new Date().toISOString(),
54
+ level,
55
+ msg,
56
+ ...(data ? { data } : {}),
57
+ });
58
+ process.stderr.write(linea + '\n');
59
+ }
60
+
61
+ // ── JSON-RPC helpers ──────────────────────────────────────────────────────────
62
+
63
+ function respuesta(id, result) {
64
+ return JSON.stringify({ jsonrpc: '2.0', id, result });
65
+ }
66
+
67
+ function errorResp(id, code, message) {
68
+ return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
69
+ }
70
+
71
+ // ── routing ───────────────────────────────────────────────────────────────────
72
+
73
+ function manejarInitialize(request) {
74
+ return respuesta(request.id, {
75
+ protocolVersion: PROTOCOL_VERSION,
76
+ capabilities: {
77
+ tools: { listChanged: false },
78
+ },
79
+ serverInfo: {
80
+ name: SERVER_NAME,
81
+ version: SERVER_VERSION,
82
+ },
83
+ });
84
+ }
85
+
86
+ function manejarToolsList(request) {
87
+ const tools = Object.entries(HANDLERS).map(([name, def]) => ({
88
+ name,
89
+ description: def.description,
90
+ inputSchema: def.inputSchema,
91
+ }));
92
+ return respuesta(request.id, { tools });
93
+ }
94
+
95
+ function manejarToolsCall(request) {
96
+ const { name, arguments: args } = request.params || {};
97
+ const def = HANDLERS[name];
98
+ if (!def) {
99
+ return errorResp(request.id, -32601, `Tool no encontrado: ${name}`);
100
+ }
101
+ try {
102
+ const result = def.handler(baseDir, args || {});
103
+ return respuesta(request.id, {
104
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
105
+ });
106
+ } catch (err) {
107
+ log('error', `Excepción en handler ${name}`, { error: err.message });
108
+ return errorResp(request.id, -32603, `Error interno: ${err.message}`);
109
+ }
110
+ }
111
+
112
+ function rutear(request) {
113
+ switch (request.method) {
114
+ case 'initialize':
115
+ return manejarInitialize(request);
116
+ case 'initialized':
117
+ case 'notifications/initialized':
118
+ return null; // notification — sin respuesta
119
+ case 'tools/list':
120
+ return manejarToolsList(request);
121
+ case 'tools/call':
122
+ return manejarToolsCall(request);
123
+ case 'ping':
124
+ return respuesta(request.id, {});
125
+ default:
126
+ return errorResp(request.id, -32601, `Método no soportado: ${request.method}`);
127
+ }
128
+ }
129
+
130
+ // ── loop principal ────────────────────────────────────────────────────────────
131
+
132
+ function arrancar() {
133
+ log('warn', '⚠ swl-mcp-server stub experimental — NO usar en producción');
134
+ log('info', `Server iniciando`, { name: SERVER_NAME, version: SERVER_VERSION, baseDir });
135
+
136
+ let buffer = '';
137
+
138
+ process.stdin.setEncoding('utf8');
139
+ process.stdin.on('data', (chunk) => {
140
+ buffer += chunk;
141
+
142
+ // Cada mensaje JSON-RPC termina con \n
143
+ let nlIndex;
144
+ while ((nlIndex = buffer.indexOf('\n')) >= 0) {
145
+ const linea = buffer.slice(0, nlIndex).trim();
146
+ buffer = buffer.slice(nlIndex + 1);
147
+
148
+ if (!linea) continue;
149
+
150
+ let request;
151
+ try {
152
+ request = JSON.parse(linea);
153
+ } catch (err) {
154
+ log('error', 'JSON inválido recibido', { error: err.message, linea: linea.slice(0, 100) });
155
+ process.stdout.write(errorResp(null, -32700, 'Parse error') + '\n');
156
+ continue;
157
+ }
158
+
159
+ const respuestaStr = rutear(request);
160
+ if (respuestaStr) {
161
+ process.stdout.write(respuestaStr + '\n');
162
+ }
163
+ }
164
+ });
165
+
166
+ process.stdin.on('end', () => {
167
+ log('info', 'stdin cerrado, server termina');
168
+ process.exit(0);
169
+ });
170
+
171
+ // Manejo de errores no capturados — nunca crashear silenciosamente
172
+ process.on('uncaughtException', (err) => {
173
+ log('error', 'uncaughtException', { error: err.message, stack: err.stack });
174
+ });
175
+ }
176
+
177
+ if (require.main === module) {
178
+ arrancar();
179
+ }
180
+
181
+ module.exports = {
182
+ rutear,
183
+ arrancar,
184
+ SERVER_NAME,
185
+ SERVER_VERSION,
186
+ PROTOCOL_VERSION,
187
+ };
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: benchmark-memoria
3
+ description: >
4
+ Benchmark de retrieval para `hooks/lib/memory-search` que mide R@5, R@10,
5
+ MRR y nDCG@10 contra un dataset de queries con respuestas conocidas
6
+ (gold_ids). Útil para detectar regresión de calidad de búsqueda al cambiar
7
+ RRF weights, scoring o fuentes. Cargar cuando se modifique
8
+ `memory-search.js`, `rrf-fusion.js` o `session-fts.js` para verificar que
9
+ no degrada retrieval. NO cargar para uso operacional ni para evaluar
10
+ outputs de agentes (eso es `eval-framework`).
11
+ version: "1.0.0"
12
+ herramientasPermitidas: [Read, Bash]
13
+ exclusiones:
14
+ - "No cargar para evaluar outputs de agentes (aprendizajes, observaciones, resúmenes); eso es `eval-framework`."
15
+ - "No cargar como gate de release sin un dataset ≥30 entries con status='real' — las métricas con dataset placeholder no son estadísticamente significativas."
16
+ - "No cargar para benchmark de performance (latencia, throughput); eso es `performance-baseline`."
17
+ - "No cargar si el repo no tiene `.planning/APRENDIZAJES.md` ni sesiones — sin contenido no hay nada que recuperar."
18
+ evolvable: true # default para skill estandar
19
+ ---
20
+ # Benchmark de Memoria — LongMemEval-S adaptado a SWL
21
+
22
+ ## Cuándo cargar esta skill
23
+
24
+ - Tras modificar `hooks/lib/memory-search.js`, `scripts/lib/rrf-fusion.js`,
25
+ pesos de fusión o algoritmo de scoring de relevancia.
26
+ - Tras agregar fuentes de búsqueda nuevas (ej. evals, instintos globales).
27
+ - Antes de un release que toque la capa de memoria, para confirmar que no
28
+ hay regresión en R@5.
29
+ - Como parte opcional de `/swl:salud` cuando `SWL_BENCHMARK_MEMORIA=1`.
30
+
31
+ ---
32
+
33
+ ## Componentes del benchmark
34
+
35
+ | Módulo | Propósito |
36
+ |---|---|
37
+ | `scripts/lib/benchmark-metrics.js` | Funciones puras: `recallAt`, `precisionAt`, `mrr`, `ndcgAt`, `dcg`, `calcularMetricas`, `promediar`. |
38
+ | `scripts/lib/longmemeval-runner.js` | Adapter que ejecuta queries contra `memory-search` y compara con gold. |
39
+ | `scripts/benchmark-memoria.js` | CLI runner principal. |
40
+ | `.planning/benchmark/dataset.jsonl` | Dataset (placeholder por defecto, debe expandirse). |
41
+
42
+ ---
43
+
44
+ ## Estado del dataset (CRÍTICO leer antes de usar)
45
+
46
+ El dataset por defecto en `.planning/benchmark/dataset.jsonl` es **placeholder**
47
+ con 10 entries marcadas explícitamente como `"status": "placeholder"`. Las
48
+ métricas calculadas con este dataset son **indicativas, no estadísticamente
49
+ significativas**.
50
+
51
+ ### Limitaciones del dataset placeholder
52
+
53
+ 1. **IDs volátiles**: los `gold_ids` referencian `apr-N` (índice de entrada en
54
+ `APRENDIZAJES.md`). Si se agregan/borran entradas, los índices cambian y
55
+ el dataset queda desincronizado. Para dataset real considerar IDs estables
56
+ (sesiones tienen timestamp; instintos tienen `id` propio).
57
+ 2. **N=10 es ruido estadístico**: para que R@5=80% sea significativo
58
+ estadísticamente (vs 70% baseline), se requieren al menos 30 queries
59
+ con N=10 random. Por debajo, las métricas reflejan suerte.
60
+ 3. **Cobertura limitada**: el placeholder cubre solo categorías técnicas
61
+ (patrones, gotchas, decisiones). Falta cobertura de:
62
+ - Bug fixes históricos
63
+ - Workflow questions ("qué hicimos antes de X")
64
+ - Cross-session ("cuándo se decidió Y")
65
+ - Negative queries (preguntas cuya respuesta es "no aplica")
66
+
67
+ ### Cómo expandir a dataset real
68
+
69
+ ```bash
70
+ # 1. Identifica una pregunta real de uso
71
+ QUERY="qué hicimos sobre force push a main protegida"
72
+
73
+ # 2. Ejecuta búsqueda y anota top-5 IDs
74
+ node -e "console.log(require('./hooks/lib/memory-search').search('.', '$QUERY').slice(0, 5).map(r => r.id + ' / ' + r.titulo).join('\n'))"
75
+
76
+ # 3. Verifica manualmente qué IDs son CORRECTOS (revisión humana,
77
+ # no se inventa). Solo esos van en gold_ids.
78
+
79
+ # 4. Agrega la entry al dataset:
80
+ cat >> .planning/benchmark/dataset.jsonl << 'EOF'
81
+ {"question_id": "q-real-001", "question": "qué hicimos sobre force push a main protegida", "gold_ids": ["apr-313"], "category": "decision", "status": "real"}
82
+ EOF
83
+ ```
84
+
85
+ Repetir hasta tener ≥30 entries con `status: "real"`. Solo entonces el
86
+ benchmark es gate de release.
87
+
88
+ ---
89
+
90
+ ## Uso
91
+
92
+ ### CLI básico
93
+
94
+ ```bash
95
+ # Ejecutar benchmark con dataset por defecto
96
+ node scripts/benchmark-memoria.js
97
+
98
+ # Output esperado:
99
+ # Recall @ 5: 85.0%
100
+ # Recall @ 10: 92.0%
101
+ # MRR: 0.741
102
+ # nDCG @ 10: 0.812
103
+ # Precision @ 5: 41.3%
104
+ ```
105
+
106
+ ### Opciones
107
+
108
+ ```bash
109
+ # Dataset alternativo
110
+ node scripts/benchmark-memoria.js --dataset .planning/benchmark/custom.jsonl
111
+
112
+ # Top-k personalizado (default 20)
113
+ node scripts/benchmark-memoria.js --limit 30
114
+
115
+ # Output JSON (para scripts)
116
+ node scripts/benchmark-memoria.js --json
117
+
118
+ # Detalle por query (útil para debugging)
119
+ node scripts/benchmark-memoria.js --verbose
120
+ ```
121
+
122
+ ### Tracking histórico opcional
123
+
124
+ Si se setea `SWL_BENCHMARK_PERSIST=1`, el benchmark escribe el resumen
125
+ agregado a `.planning/evolucion/benchmark-memoria.jsonl` (append-only)
126
+ para detectar regresión entre releases:
127
+
128
+ ```bash
129
+ SWL_BENCHMARK_PERSIST=1 node scripts/benchmark-memoria.js
130
+ ```
131
+
132
+ Comparar entre releases:
133
+ ```bash
134
+ tail -5 .planning/evolucion/benchmark-memoria.jsonl | jq -c '{ts: .timestamp, r5: .promedio.recall_at_5}'
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Métricas explicadas
140
+
141
+ | Métrica | Significado | Rango |
142
+ |---|---|---|
143
+ | **Recall @ k** | ¿El sistema recuperó al menos un gold ID en los primeros k? | 0 o 1 por query, promediado en [0, 1] |
144
+ | **Precision @ k** | ¿Qué porcentaje de los primeros k son gold? | [0, 1] |
145
+ | **MRR** | 1 / posición del primer gold encontrado | [0, 1] — alto = gold cerca del top |
146
+ | **nDCG @ k** | DCG normalizado: penaliza gold en posiciones bajas | [0, 1] — mide ranking quality |
147
+
148
+ **Interpretación**:
149
+ - R@5 alto + MRR bajo → encuentra gold pero no en posición 1.
150
+ - R@5 bajo + R@10 alto → necesita expandir top-k para alcanzar.
151
+ - nDCG@10 alto → ranking respeta orden de relevancia.
152
+
153
+ ---
154
+
155
+ ## Anti-patrones (qué NO hacer)
156
+
157
+ - **Usar el dataset placeholder como gate de CI**: no significativo, da
158
+ falsa sensación de seguridad. Marcar el job como informativo solamente
159
+ hasta tener dataset real ≥30.
160
+ - **Inventar gold_ids "que se ven correctos"**: el dataset SOLO sirve si
161
+ los gold_ids son verificados manualmente como correctos por un humano
162
+ con conocimiento del proyecto.
163
+ - **Optimizar el algoritmo solo para que las métricas suban**: si el
164
+ dataset es placeholder, "mejorar" R@5 puede ser overfitting al
165
+ placeholder. Dataset real primero, optimización después.
166
+ - **Borrar entries que dan métricas bajas**: una query con R@5=0 puede
167
+ estar revelando un bug real del sistema de búsqueda, no un problema
168
+ del dataset. Debug antes de borrar.
169
+
170
+ ---
171
+
172
+ ## Gotchas / Errores comunes no obvios
173
+
174
+ - **Dataset JSONL acepta comentarios `//`**: las líneas que empiezan con
175
+ `//` son ignoradas. Útil para documentar inline. NO es JSON estándar
176
+ pero el parser de `longmemeval-runner` lo respeta.
177
+ - **Si la query no matchea ningún token mínimo (<= 3 chars), `search()`
178
+ devuelve `[]`**: las stop words están filtradas en
179
+ `hooks/lib/memory-search.js`. Asegurar que cada query tenga ≥2
180
+ términos significativos.
181
+ - **Los `gold_ids` deben usar el formato `apr-N`/`ses-YYYYMMDD-HHmm`/etc.
182
+ EXACTO** que devuelve `memory-search`. Si se anota `apr-14` cuando el
183
+ search devuelve `apr-014`, el match falla silenciosamente.
184
+ - **El benchmark mide `memory-search` específicamente, no toda la memoria
185
+ SWL**: instintos globales no entran si solo se buscan locales; sesiones
186
+ archivadas no entran. Documentar el scope de cada query.
@@ -1,7 +1,12 @@
1
1
  ---
2
2
  name: contenedores-docker
3
3
  description: Docker y containerización. Dockerfiles optimizados con multi-stage builds, docker-compose, volúmenes, networking, health checks, security scanning, build caching, distroless images. Anti-patrones comunes.
4
- version: "1.0.0"
4
+ version: "1.0.1"
5
+ evolved: true
6
+ evolved-from: "1.0.0"
7
+ evolved-at: "2026-05-05"
8
+ evolved-by: "aprender"
9
+ evolved-note: "Gotcha de la sesión SIGM 2026-05-05 (L6): scripts en /docker-entrypoint-initdb.d con dependencias externas (mc, aws, etc.) abortan initdb"
5
10
  herramientasPermitidas: [Read]
6
11
  exclusiones:
7
12
  - "No cargar para orquestación Kubernetes (Deployments, Services, Helm, HPA) — para Kubernetes cargar `kubernetes-orquestacion`."
@@ -135,3 +140,5 @@ Para ejemplos completos de multi-stage Node.js/Angular, .dockerignore, docker-co
135
140
  **Secrets pasados como `ARG` en el Dockerfile son visibles en `docker history` aunque se eliminen en una capa posterior**: `ARG SECRET_KEY` seguido de `RUN rm -f /app/.env` no elimina el valor del ARG del historial de la imagen — `docker history --no-trunc imagen` muestra los valores de los ARG. Causa: `docker history` registra los comandos de cada capa, incluyendo los valores de ARG usados. Fix: NUNCA pasar secretos como `ARG` o `ENV` en el Dockerfile; para secrets en build-time usar `--secret` de Docker BuildKit: `RUN --mount=type=secret,id=api_key cat /run/secrets/api_key`.
136
141
 
137
142
  **`.dockerignore` mal configurado incluye archivos `.env` o `node_modules` en el contexto de build, ralentizando el daemon y exponiendo secretos**: si `.dockerignore` no existe o no excluye `node_modules`, el contexto de build puede ser de cientos de MB — todo se transfiere al daemon antes del build. Causa: el contexto de build incluye todo el directorio por defecto. Fix: crear `.dockerignore` con al menos `.git`, `node_modules`, `__pycache__`, `.env*`, `*.pyc`, `dist`, `build`, y verificar el tamaño del contexto con `docker build` mirando la línea "Sending build context to Docker daemon X.XX MB".
143
+
144
+ **Scripts en `database/init/` (mounted a `/docker-entrypoint-initdb.d`) no pueden depender de binarios fuera de la imagen base**: un script con `set -e` que invoca `mc` (cliente MinIO), `aws`, `gh`, `kubectl` o cualquier binario no presente en la imagen `postgres`/`postgis` aborta el initdb con `command not found` (exit 127). El contenedor sale con error y los scripts SQL que iban después (incluido schemas/seeds completos) NUNCA se ejecutan. Caso real (SIGM 2026-05-04): `02-crear-buckets-minio.sh` con `mc alias set` aborta `postgis/postgis:17-3.5` antes de cargar el schema; ningún schema se aplicaba aunque el archivo init.sh estaba bien. Causa: el directorio `/docker-entrypoint-initdb.d` ejecuta todos los archivos `.sh` y `.sql` en orden alfabético; cualquier fallo aborta toda la cadena. Fix: scripts que invocan binarios externos van en `scripts/setup/` o `scripts/init-deps/` y se ejecutan manualmente o via `docker compose run --rm --entrypoint /scripts/setup/X.sh <servicio_que_tiene_el_binario>`. Solo SQL puro o bash que use `psql` pueden vivir en `database/init/`.
@@ -4,7 +4,12 @@ description: >
4
4
  Ingeniería de datos y ETL: diseño de pipelines, calidad de datos, evolución de esquemas,
5
5
  CDC (change data capture), validación de datos, linaje de datos, procesamiento batch vs
6
6
  streaming, patrones dbt, patrones de data lake.
7
- version: "1.0.0"
7
+ version: "1.0.1"
8
+ evolved: true
9
+ evolved-from: "1.0.0"
10
+ evolved-at: "2026-05-05"
11
+ evolved-by: "aprender"
12
+ evolved-note: "Gotcha de la sesión SIGM 2026-05-05 (L9): seeds con check constraint fecha_vencimiento >= fecha_emision violan al usar emision única para todas las tuplas"
8
13
  herramientasPermitidas: [Read, Grep]
9
14
  evolvable: true # default para skill estandar
10
15
  exclusiones:
@@ -127,3 +132,15 @@ esquemas (Avro), patrones dbt (modelos incrementales), y linaje de datos, ver
127
132
  **Validación con Great Expectations pasa en staging pero falla en producción con el mismo schema**: los expectations se definen sobre una muestra de datos de staging que puede no tener todos los valores posibles del dominio. Causa: un expectation de tipo `expect_column_values_to_be_in_set(["A","B","C"])` falla en producción cuando aparece un valor "D" válido que no existía en el sample de staging. Fix: para columnas con dominio abierto, usar `expect_column_values_to_not_be_null` en lugar de un set cerrado. Reservar sets cerrados solo para columnas con dominio verdaderamente finito y controlado (estados de máquina de estado, flags booleanos).
128
133
 
129
134
  **El job de Spark/pandas falla en producción por OOM aunque funcionó bien en staging con datos de muestra**: el procesamiento en memoria de un DataFrame completo escala linealmente con el volumen de datos. En staging con 10K registros todo cabe en RAM; en producción con 10M registros el proceso muere. Causa: operaciones como `groupby + apply` con funciones Python puras no se pueden paralelizar ni distribuir automáticamente. Fix: para transformaciones sobre datasets grandes, usar procesamiento por chunks (`chunksize` en pandas) o migrar a Spark/DuckDB para operaciones distribuidas. Medir el uso de memoria en staging con el volumen máximo esperado en producción, no con la muestra de desarrollo.
135
+
136
+ **Seeds que simulan data histórica con check constraints de fechas relativas violan el constraint si se usa una sola fecha de emisión**: cuando un seed crea facturas/pagos demo con una mezcla de estatus (vencidas, vigentes, pagadas) y la tabla tiene check constraint del tipo `fecha_vencimiento >= fecha_emision`, generar `fecha_emision = '2025-12-10'` y luego asignar `fecha_vencimiento = '2025-10-31'` para vencidas viola el constraint. Caso real (SIGM 2026-05-04): seed `009-datos-operativos-demo.sql` con 500 facturas demo donde `n%10=0` representaba "vencidas" pero la fecha_emision común era posterior. Causa: la lógica genera dimensiones derivadas (vencimiento) sin re-derivar las fuente (emisión). Fix: separar fechas POR ESTATUS antes de generar tuplas:
137
+ ```sql
138
+ -- Vencidas: emision en pasado profundo, vencimiento ya pasó
139
+ CASE WHEN n % 10 = 0 THEN DATE '2025-09-30'
140
+ ELSE DATE '2025-12-10' + (n % 3)
141
+ END AS fecha_emision,
142
+ CASE WHEN n % 10 = 0 THEN DATE '2025-10-31'
143
+ ELSE DATE '2026-01-31'
144
+ END AS fecha_vencimiento,
145
+ ```
146
+ Generalización: cualquier check constraint que relacione columnas debe respetarse al SEEDEAR, no solo en INSERT productivo.