@saulwade/swl-ses 1.5.1 → 1.6.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 (155) hide show
  1. package/CLAUDE.md +225 -209
  2. package/README.md +578 -561
  3. package/agentes/arquitecto-swl.md +33 -1
  4. package/agentes/nemesis-auditor-swl.md +59 -19
  5. package/bin/swl-mcp-server.js +214 -214
  6. package/bin/swl-ses.js +49 -7
  7. package/comandos/swl/.evolved.json +22 -22
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/nemesis.md +230 -56
  10. package/gateway/lib/event-channel.js +191 -191
  11. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  12. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  13. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  14. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  15. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  16. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  17. package/habilidades/eval-framework/SKILL.md +212 -212
  18. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  19. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  20. package/habilidades/harness-claude-code/SKILL.md +299 -299
  21. package/habilidades/infra-github-actions/SKILL.md +166 -166
  22. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  23. package/habilidades/manejo-errores/.evolved.json +8 -8
  24. package/habilidades/meta-skills-estandar/SKILL.md +207 -4
  25. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  26. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  27. package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
  28. package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
  29. package/habilidades/node-experto/SKILL.md +94 -4
  30. package/habilidades/patrones-python/SKILL.md +229 -229
  31. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  32. package/habilidades/planear-fase/SKILL.md +319 -319
  33. package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
  34. package/habilidades/release-semver/.evolved.json +8 -8
  35. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  36. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  37. package/habilidades/tdd-workflow/SKILL.md +121 -4
  38. package/habilidades/testing-python/SKILL.md +340 -340
  39. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  40. package/hooks/check-update.js +31 -3
  41. package/hooks/claudemd-bloat-detector.js +161 -161
  42. package/hooks/extraccion-aprendizajes.js +11 -0
  43. package/hooks/lib/agent-routing.js +107 -107
  44. package/hooks/lib/auto-consolidator.js +335 -335
  45. package/hooks/lib/error-classifier.js +308 -308
  46. package/hooks/lib/merkle-audit.js +96 -96
  47. package/hooks/lib/provenance-tracker.js +191 -191
  48. package/hooks/lib/rate-limit-tracker.js +253 -253
  49. package/hooks/lib/resource-quota.js +122 -122
  50. package/hooks/lib/retry-jitter.js +165 -165
  51. package/hooks/lib/security-net.js +201 -201
  52. package/hooks/lib/skill-auditor.js +588 -588
  53. package/hooks/lib/sync-status.js +228 -228
  54. package/hooks/lib/taint-tracker.js +107 -107
  55. package/hooks/lib/text-similarity.js +241 -241
  56. package/hooks/lib/toon-compressor.js +245 -245
  57. package/hooks/registro-turnos.js +209 -209
  58. package/hooks/sugerir-regenerar-inventario.js +170 -170
  59. package/hooks/validar-formato-post-subagente.js +140 -140
  60. package/hooks/validar-memoria-hook.js +218 -218
  61. package/instintos/prompt-appendices.yaml +57 -57
  62. package/manifiestos/agent-output-schemas.json +57 -57
  63. package/manifiestos/modulos.json +1324 -1321
  64. package/manifiestos/skills-lock.json +1142 -1114
  65. package/package.json +5 -4
  66. package/plantillas/auditor-veto-template.md +105 -105
  67. package/plantillas/github-workflows/README.md +47 -47
  68. package/plantillas/github-workflows/release-please.yml +44 -44
  69. package/plantillas/github-workflows/swl-ci.yml +107 -107
  70. package/plantillas/github-workflows/swl-security.yml +51 -51
  71. package/plugin.json +355 -351
  72. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  73. package/reglas/arreglar-al-detectar.md +147 -147
  74. package/reglas/fragmentos-compartidos.md +152 -152
  75. package/reglas/harness-claude-code.md +213 -213
  76. package/reglas/registro-componentes-nuevos.md +192 -0
  77. package/reglas/usar-context7.md +226 -226
  78. package/schemas/diary-entry.schema.json +80 -80
  79. package/scripts/actualizar.js +110 -1
  80. package/scripts/audit-tools/audit-history.js +330 -330
  81. package/scripts/audit-tools/bundle-tracker.js +290 -290
  82. package/scripts/audit-tools/canary-monitor.js +352 -352
  83. package/scripts/audit-tools/code-profiler.js +605 -605
  84. package/scripts/audit-tools/dep-doctor.js +320 -320
  85. package/scripts/audit-tools/env-validator.js +206 -206
  86. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  87. package/scripts/audit-tools/lib/output.js +23 -23
  88. package/scripts/audit-tools/migration-checker.js +392 -392
  89. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  90. package/scripts/benchmark-memoria.js +167 -167
  91. package/scripts/configurar-branch-protection.js +418 -418
  92. package/scripts/derivar-feature-list.js +489 -489
  93. package/scripts/desinstalar.js +105 -24
  94. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  95. package/scripts/doctor.js +27 -0
  96. package/scripts/field-report.js +199 -199
  97. package/scripts/generar-checklists-consolidados.js +273 -273
  98. package/scripts/generar-inventario.js +420 -420
  99. package/scripts/generar-matriz-lenguajes.js +271 -271
  100. package/scripts/instalador.js +55 -4
  101. package/scripts/lib/artefactos-python.js +43 -43
  102. package/scripts/lib/benchmark-metrics.js +160 -160
  103. package/scripts/lib/budget-enforcer.js +252 -252
  104. package/scripts/lib/configurar-ci.js +380 -380
  105. package/scripts/lib/contadores-inventario.js +217 -217
  106. package/scripts/lib/detectar-stack-detallado.js +307 -307
  107. package/scripts/lib/diary-entry.js +234 -234
  108. package/scripts/lib/eval-metrics-store.js +218 -218
  109. package/scripts/lib/eval-quality.js +171 -171
  110. package/scripts/lib/eval-schemas.js +144 -144
  111. package/scripts/lib/eval-self-correct.js +106 -106
  112. package/scripts/lib/eval-validator.js +185 -185
  113. package/scripts/lib/expandir-targets.js +71 -71
  114. package/scripts/lib/jaccard-similarity.js +98 -98
  115. package/scripts/lib/longmemeval-runner.js +125 -125
  116. package/scripts/lib/mcp_config.py +127 -0
  117. package/scripts/lib/npm-version.js +261 -261
  118. package/scripts/lib/paquetes-conocidos.js +50 -50
  119. package/scripts/lib/parsear-opciones.js +3 -0
  120. package/scripts/lib/prompt-builder.js +264 -264
  121. package/scripts/lib/rrf-fusion.js +175 -175
  122. package/scripts/lib/scoring-instintos.js +277 -277
  123. package/scripts/lib/semantic-search.js +252 -252
  124. package/scripts/lib/toml-merge.js +204 -204
  125. package/scripts/lib/transformadores/codex.js +375 -375
  126. package/scripts/lib/transformadores/cursor.js +359 -359
  127. package/scripts/lib/ui.js +148 -22
  128. package/scripts/limpiar-artefactos-python.js +131 -131
  129. package/scripts/mcp-orchestrator.py +8 -18
  130. package/scripts/mcp-pool-manager.py +12 -23
  131. package/scripts/mcp-server/README.md +170 -170
  132. package/scripts/mcp-server/auth.js +105 -105
  133. package/scripts/mcp-server/cache.js +106 -106
  134. package/scripts/mcp-server/telemetry.js +78 -78
  135. package/scripts/migrar-csv-a-array.js +168 -168
  136. package/scripts/migrar-fase-dominio.js +201 -201
  137. package/scripts/publicar.js +511 -511
  138. package/scripts/run-eval.js +141 -141
  139. package/scripts/tui/componentes/selector-multi.js +189 -0
  140. package/scripts/tui/componentes/selector-unico.js +158 -0
  141. package/scripts/tui/ejecutores.js +375 -0
  142. package/scripts/tui/index.js +162 -0
  143. package/scripts/tui/lib/colores.js +129 -0
  144. package/scripts/tui/lib/render.js +264 -0
  145. package/scripts/tui/lib/teclas.js +113 -0
  146. package/scripts/tui/pantallas/inspect.js +173 -0
  147. package/scripts/tui/pantallas/install-wizard.js +334 -0
  148. package/scripts/tui/pantallas/menu-principal.js +52 -0
  149. package/scripts/tui/pantallas/progreso.js +274 -0
  150. package/scripts/tui/pantallas/resumen.js +132 -0
  151. package/scripts/tui/pantallas/uninstall-wizard.js +208 -0
  152. package/scripts/tui/pantallas/update-wizard.js +232 -0
  153. package/scripts/tui/pantallas/welcome.js +187 -0
  154. package/scripts/validar-userland-vacio.js +110 -110
  155. package/scripts/verificar-docs-vs-codigo.js +425 -0
@@ -1,229 +1,229 @@
1
- ---
2
- name: patrones-python
3
- description: Idiomas pythonicos, PEP 8, type hints modernos, dataclasses, async/await, context managers, decorators y generators. Patrones de código limpio en Python.
4
- version: "1.3.1"
5
- evolved: true
6
- evolved-from: "1.3.0"
7
- evolved-at: "2026-05-04"
8
- evolved-by: "aprender"
9
- evolved-note: "+1 gotcha: assert se elimina con PYTHONOPTIMIZE=1 — usar if/raise para invariantes (sync desde global tras sesión SIGM Fase 5b)"
10
- herramientasPermitidas: [Read, Glob, Grep]
11
- exclusiones:
12
- - "No cargar para patrones de un framework específico (FastAPI, Django, Celery) — los idiomas generales de este skill aplican, pero los patrones de framework tienen restricciones adicionales; cargar el skill del framework correspondiente."
13
- - "No cargar para errores de instalación o dependencias Python — si el problema es pip, virtualenv o incompatibilidad de versiones, cargar `build-errors-python`."
14
- - "No cargar para patrones de concurrencia avanzados (TaskGroup, semáforos, event loops) — para esos cargar `async-python` que cubre asyncio en profundidad."
15
- - "No cargar para testing pytest (fixtures, mocking, cobertura) — ese dominio está en `testing-python` que cubre la toolchain completa de tests."
16
- evolvable: true # default para skill estandar
17
- ---
18
- # Patrones Python
19
-
20
- ## Cuándo NO cargar
21
-
22
- - La tarea es específica de FastAPI o Django — cargar el skill del framework que tiene restricciones adicionales a los patrones generales.
23
- - El problema es de instalación de dependencias Python — cargar `build-errors-python`.
24
- - La pregunta es sobre concurrencia avanzada con asyncio — cargar `async-python`.
25
-
26
- ## PEP 8 — Convenciones de estilo
27
-
28
- ### Nomenclatura
29
-
30
- ```python
31
- # Módulos y paquetes: snake_case minúsculas
32
- import mi_modulo
33
-
34
- # Clases: PascalCase
35
- class FacturaElectronica:
36
- pass
37
-
38
- # Funciones y variables: snake_case
39
- def calcular_iva(monto: float) -> float:
40
- tasa_iva = 0.16
41
- return monto * tasa_iva
42
-
43
- # Constantes: UPPER_SNAKE_CASE
44
- MAX_REINTENTOS = 3
45
- URL_BASE_API = "https://api.ejemplo.com/v1"
46
-
47
- # Variables privadas de instancia: prefijo _
48
- class Factura:
49
- def __init__(self):
50
- self._numero_interno = None # privado por convención
51
- ```
52
-
53
- ### Longitud de línea y formato
54
-
55
- ```python
56
- # Máximo 88 caracteres (configuración Black estándar)
57
-
58
- # Importaciones agrupadas:
59
- # 1. Stdlib
60
- import os
61
- from pathlib import Path
62
-
63
- # 2. Terceros
64
- from fastapi import FastAPI, Depends
65
- from sqlalchemy.orm import Session
66
-
67
- # 3. Módulos locales
68
- from app.models.usuario import Usuario
69
- ```
70
-
71
- ---
72
-
73
- ## Type Hints modernos (Python 3.10+)
74
-
75
- ```python
76
- # Union con | en vez de Union[X, Y]
77
- def procesar(valor: int | str | None) -> str:
78
- return str(valor or "")
79
-
80
- # list, dict, tuple nativos (no de typing)
81
- def filtrar(items: list[str], lookup: dict[str, int]) -> list[str]:
82
- return [i for i in items if i in lookup]
83
-
84
- # TypeAlias explícito
85
- type JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
86
-
87
- # ParamSpec para decoradores que preservan firma
88
- from typing import ParamSpec, Callable
89
- P = ParamSpec("P")
90
-
91
- def con_log(fn: Callable[P, T]) -> Callable[P, T]:
92
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
93
- print(f"Llamando {fn.__name__}")
94
- return fn(*args, **kwargs)
95
- return wrapper
96
- ```
97
-
98
- ---
99
-
100
- ## Dataclasses
101
-
102
- ```python
103
- from dataclasses import dataclass, field
104
- from datetime import datetime
105
-
106
- @dataclass
107
- class Producto:
108
- id: str
109
- nombre: str
110
- precio: float
111
- activo: bool = True
112
- etiquetas: list[str] = field(default_factory=list)
113
-
114
- def __post_init__(self) -> None:
115
- if self.precio < 0:
116
- raise ValueError(f"Precio no puede ser negativo: {self.precio}")
117
- self.nombre = self.nombre.strip()
118
-
119
- # Dataclass inmutable
120
- @dataclass(frozen=True)
121
- class Coordenada:
122
- latitud: float
123
- longitud: float
124
-
125
- # Dataclass con slots (Python 3.10+) — ahorra memoria
126
- @dataclass(slots=True)
127
- class EventoAuditoria:
128
- usuario_id: str
129
- accion: str
130
- timestamp: datetime
131
- ```
132
-
133
- ---
134
-
135
- ## Context Managers
136
-
137
- ```python
138
- from contextlib import contextmanager, asynccontextmanager
139
-
140
- # Context manager con generador
141
- @contextmanager
142
- def medir_tiempo(nombre: str):
143
- inicio = time.perf_counter()
144
- try:
145
- yield
146
- finally:
147
- transcurrido = time.perf_counter() - inicio
148
- print(f"{nombre}: {transcurrido:.4f}s")
149
-
150
- # Context manager async
151
- @asynccontextmanager
152
- async def sesion_db(engine):
153
- async with engine.begin() as conn:
154
- try:
155
- yield conn
156
- await conn.commit()
157
- except Exception:
158
- await conn.rollback()
159
- raise
160
- ```
161
-
162
- ---
163
-
164
- ## Anti-patrones Python Críticos
165
-
166
- ```python
167
- # MAL: argumento mutable como default
168
- def agregar(item, lista=[]): # La lista persiste entre llamadas
169
- lista.append(item)
170
-
171
- # BIEN: usar None como centinela
172
- def agregar(item, lista=None):
173
- if lista is None:
174
- lista = []
175
- lista.append(item)
176
-
177
- # MAL: bare except
178
- try:
179
- operacion_riesgosa()
180
- except:
181
- pass # silencia KeyboardInterrupt y SystemExit
182
-
183
- # BIEN: excepción específica
184
- try:
185
- operacion_riesgosa()
186
- except ValueError as e:
187
- logger.error(f"Valor inválido: {e}")
188
-
189
- # MAL: concatenación en loop (O(n²))
190
- resultado = ""
191
- for parte in partes:
192
- resultado += parte
193
-
194
- # BIEN: join (O(n))
195
- resultado = "".join(partes)
196
- ```
197
-
198
- Para generators, decoradores avanzados, async/await y manejo de errores idiomático,
199
- ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
200
-
201
- ## Gotchas / Errores comunes no obvios
202
-
203
- - **`dataclass` con campo mutable como default (`field: list = []`) produce un objeto compartido entre todas las instancias**: Python reutiliza el objeto por defecto en vez de crear uno nuevo. Causa: los defaults de dataclass se evalúan en tiempo de definición de la clase, no en cada instanciación — una lista mutable como default es el mismo objeto en memoria para todas las instancias. Solución: usar `field(default_factory=list)` para campos mutables: `from dataclasses import field; tags: list = field(default_factory=list)`.
204
- - **Decorator que usa `functools.wraps` pero no preserva type hints si la función decorada tiene anotaciones genéricas**: el `@wraps` copia `__wrapped__`, `__doc__` y `__name__`, pero el tipo de retorno inferido por `mypy` es el del wrapper, no el del wrapped. Causa: `functools.wraps` no puede preservar el tipado estático del wrapped — mypy ve el tipo del wrapper `Callable[..., Any]`. Solución: usar `TypeVar` y tipado genérico en el decorator con `ParamSpec` (Python 3.10+): `P = ParamSpec('P'); T = TypeVar('T')` y tipar el wrapper como `Callable[P, T]`.
205
- - **`__slots__` en clase Python produce `TypeError: multiple bases have instance lay-out conflict`** al heredar de otra clase con `__slots__`: las subclases con `__slots__` requieren que todos los ancestros también tengan `__slots__`, o que el ancestro directo sea `object`. Causa: si `ClaseBase` no tiene `__slots__`, tiene un `__dict__` implícito; si `ClaseHija` tiene `__slots__`, hay conflicto de layout de memoria. Solución: o agregar `__slots__ = ()` vacío a la clase base, o eliminar `__slots__` de la subclase — no mezclar clases con y sin `__slots__` en la misma jerarquía.
206
- - **`property` setter que modifica un campo privado no refleja el cambio en `__repr__` generado por dataclass**: el `@property` en un dataclass crea un campo de clase que conflictúa con el campo de instancia del dataclass. Causa: `@dataclass` genera `__repr__` basado en los campos declarados en `__init__` — si el setter modifica un atributo con nombre diferente (ej: `_valor`), `__repr__` muestra el campo original sin la modificación. Solución: usar `field(init=False, repr=False)` para el campo interno y exponer solo la `property` en la interfaz pública.
207
- - **`assert` no es guard de invariantes en producción con `PYTHONOPTIMIZE=1` o `python -O`**: el bytecode optimizado **elimina** todos los `assert` del módulo, por lo que `assert x is not None; return x` puede retornar `None` violando el contrato `-> dict` en producción aunque pase tests en desarrollo. El test runner por defecto NO usa `-O`, por lo que el bug es invisible hasta que alguien despliega con `PYTHONOPTIMIZE=1` (configuración común para reducir memoria en imágenes Docker production). Causa: `assert` está documentado en Python como herramienta de **debugging**, no de validación. Solución: para invariantes que DEBEN cumplirse en producción, usar guard explícito con raise: `if x is None: raise HTTPException(500, "Invariante violado")` o `if x is None: raise RuntimeError(...)`. Reservar `assert` solo para tests, scripts, o pre-condiciones triviales en código de desarrollo. Regla rápida: si el assert protege un caso que activa una respuesta del usuario o un side-effect, NO es assert — es validación y debe ser `if/raise`.
208
-
209
- ---
210
-
211
- ## Patrones avanzados (carga bajo demanda)
212
-
213
- Los siguientes patrones aparecen al integrar Python con pipelines reales
214
- (conversores de documentos, clientes heterogéneos, cachés determinísticos,
215
- detectores de texto, duplicación deliberada de lógica). El detalle completo
216
- está en [recursos/patrones-avanzados.md](recursos/patrones-avanzados.md).
217
-
218
- | # | Patrón | Cargar cuando |
219
- |---|--------|---------------|
220
- | 1 | Normalizadores: colapsar al formato canónico del PROYECTO | escribir `_normalizar(texto)` después de un conversor externo (MarkItDown, Pandoc, mammoth) |
221
- | 2 | kwargs opcionales entre clientes hermanos: try/except TypeError | propagar un kwarg nuevo a clientes con signature heterogénea sin romper la interfaz |
222
- | 3 | Caché content-addressable por SHA256 | función pura costosa (>1 s) que se invoca repetidamente con los mismos inputs |
223
- | 4 | F401 en archivos con soft imports es intencional | módulo importa una dependencia opcional dentro de `try/except ImportError` |
224
- | 5 | Detectores regex multi-pattern: extender scope sin refinar genera FP | un detector con OR de patterns se va a aplicar a un texto más ruidoso que el original |
225
- | 6 | Tracer/replicador paralelo del motor: marca SYNC obligatoria | duplicar lógica del motor para telemetría, explainability o shadow runs |
226
- | 7 | Fixtures con datos pre-procesados NO ejercen el path crudo | el motor prefiere leer un campo crudo opcional sobre uno condensado, y los fixtures son condensados |
227
-
228
- Cargar la sección específica del recurso cuando aplique al problema en mano —
229
- no cargar el archivo completo si solo se necesita un patrón.
1
+ ---
2
+ name: patrones-python
3
+ description: Idiomas pythonicos, PEP 8, type hints modernos, dataclasses, async/await, context managers, decorators y generators. Patrones de código limpio en Python.
4
+ version: "1.3.1"
5
+ evolved: true
6
+ evolved-from: "1.3.0"
7
+ evolved-at: "2026-05-04"
8
+ evolved-by: "aprender"
9
+ evolved-note: "+1 gotcha: assert se elimina con PYTHONOPTIMIZE=1 — usar if/raise para invariantes (sync desde global tras sesión SIGM Fase 5b)"
10
+ herramientasPermitidas: [Read, Glob, Grep]
11
+ exclusiones:
12
+ - "No cargar para patrones de un framework específico (FastAPI, Django, Celery) — los idiomas generales de este skill aplican, pero los patrones de framework tienen restricciones adicionales; cargar el skill del framework correspondiente."
13
+ - "No cargar para errores de instalación o dependencias Python — si el problema es pip, virtualenv o incompatibilidad de versiones, cargar `build-errors-python`."
14
+ - "No cargar para patrones de concurrencia avanzados (TaskGroup, semáforos, event loops) — para esos cargar `async-python` que cubre asyncio en profundidad."
15
+ - "No cargar para testing pytest (fixtures, mocking, cobertura) — ese dominio está en `testing-python` que cubre la toolchain completa de tests."
16
+ evolvable: true # default para skill estandar
17
+ ---
18
+ # Patrones Python
19
+
20
+ ## Cuándo NO cargar
21
+
22
+ - La tarea es específica de FastAPI o Django — cargar el skill del framework que tiene restricciones adicionales a los patrones generales.
23
+ - El problema es de instalación de dependencias Python — cargar `build-errors-python`.
24
+ - La pregunta es sobre concurrencia avanzada con asyncio — cargar `async-python`.
25
+
26
+ ## PEP 8 — Convenciones de estilo
27
+
28
+ ### Nomenclatura
29
+
30
+ ```python
31
+ # Módulos y paquetes: snake_case minúsculas
32
+ import mi_modulo
33
+
34
+ # Clases: PascalCase
35
+ class FacturaElectronica:
36
+ pass
37
+
38
+ # Funciones y variables: snake_case
39
+ def calcular_iva(monto: float) -> float:
40
+ tasa_iva = 0.16
41
+ return monto * tasa_iva
42
+
43
+ # Constantes: UPPER_SNAKE_CASE
44
+ MAX_REINTENTOS = 3
45
+ URL_BASE_API = "https://api.ejemplo.com/v1"
46
+
47
+ # Variables privadas de instancia: prefijo _
48
+ class Factura:
49
+ def __init__(self):
50
+ self._numero_interno = None # privado por convención
51
+ ```
52
+
53
+ ### Longitud de línea y formato
54
+
55
+ ```python
56
+ # Máximo 88 caracteres (configuración Black estándar)
57
+
58
+ # Importaciones agrupadas:
59
+ # 1. Stdlib
60
+ import os
61
+ from pathlib import Path
62
+
63
+ # 2. Terceros
64
+ from fastapi import FastAPI, Depends
65
+ from sqlalchemy.orm import Session
66
+
67
+ # 3. Módulos locales
68
+ from app.models.usuario import Usuario
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Type Hints modernos (Python 3.10+)
74
+
75
+ ```python
76
+ # Union con | en vez de Union[X, Y]
77
+ def procesar(valor: int | str | None) -> str:
78
+ return str(valor or "")
79
+
80
+ # list, dict, tuple nativos (no de typing)
81
+ def filtrar(items: list[str], lookup: dict[str, int]) -> list[str]:
82
+ return [i for i in items if i in lookup]
83
+
84
+ # TypeAlias explícito
85
+ type JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
86
+
87
+ # ParamSpec para decoradores que preservan firma
88
+ from typing import ParamSpec, Callable
89
+ P = ParamSpec("P")
90
+
91
+ def con_log(fn: Callable[P, T]) -> Callable[P, T]:
92
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
93
+ print(f"Llamando {fn.__name__}")
94
+ return fn(*args, **kwargs)
95
+ return wrapper
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Dataclasses
101
+
102
+ ```python
103
+ from dataclasses import dataclass, field
104
+ from datetime import datetime
105
+
106
+ @dataclass
107
+ class Producto:
108
+ id: str
109
+ nombre: str
110
+ precio: float
111
+ activo: bool = True
112
+ etiquetas: list[str] = field(default_factory=list)
113
+
114
+ def __post_init__(self) -> None:
115
+ if self.precio < 0:
116
+ raise ValueError(f"Precio no puede ser negativo: {self.precio}")
117
+ self.nombre = self.nombre.strip()
118
+
119
+ # Dataclass inmutable
120
+ @dataclass(frozen=True)
121
+ class Coordenada:
122
+ latitud: float
123
+ longitud: float
124
+
125
+ # Dataclass con slots (Python 3.10+) — ahorra memoria
126
+ @dataclass(slots=True)
127
+ class EventoAuditoria:
128
+ usuario_id: str
129
+ accion: str
130
+ timestamp: datetime
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Context Managers
136
+
137
+ ```python
138
+ from contextlib import contextmanager, asynccontextmanager
139
+
140
+ # Context manager con generador
141
+ @contextmanager
142
+ def medir_tiempo(nombre: str):
143
+ inicio = time.perf_counter()
144
+ try:
145
+ yield
146
+ finally:
147
+ transcurrido = time.perf_counter() - inicio
148
+ print(f"{nombre}: {transcurrido:.4f}s")
149
+
150
+ # Context manager async
151
+ @asynccontextmanager
152
+ async def sesion_db(engine):
153
+ async with engine.begin() as conn:
154
+ try:
155
+ yield conn
156
+ await conn.commit()
157
+ except Exception:
158
+ await conn.rollback()
159
+ raise
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Anti-patrones Python Críticos
165
+
166
+ ```python
167
+ # MAL: argumento mutable como default
168
+ def agregar(item, lista=[]): # La lista persiste entre llamadas
169
+ lista.append(item)
170
+
171
+ # BIEN: usar None como centinela
172
+ def agregar(item, lista=None):
173
+ if lista is None:
174
+ lista = []
175
+ lista.append(item)
176
+
177
+ # MAL: bare except
178
+ try:
179
+ operacion_riesgosa()
180
+ except:
181
+ pass # silencia KeyboardInterrupt y SystemExit
182
+
183
+ # BIEN: excepción específica
184
+ try:
185
+ operacion_riesgosa()
186
+ except ValueError as e:
187
+ logger.error(f"Valor inválido: {e}")
188
+
189
+ # MAL: concatenación en loop (O(n²))
190
+ resultado = ""
191
+ for parte in partes:
192
+ resultado += parte
193
+
194
+ # BIEN: join (O(n))
195
+ resultado = "".join(partes)
196
+ ```
197
+
198
+ Para generators, decoradores avanzados, async/await y manejo de errores idiomático,
199
+ ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
200
+
201
+ ## Gotchas / Errores comunes no obvios
202
+
203
+ - **`dataclass` con campo mutable como default (`field: list = []`) produce un objeto compartido entre todas las instancias**: Python reutiliza el objeto por defecto en vez de crear uno nuevo. Causa: los defaults de dataclass se evalúan en tiempo de definición de la clase, no en cada instanciación — una lista mutable como default es el mismo objeto en memoria para todas las instancias. Solución: usar `field(default_factory=list)` para campos mutables: `from dataclasses import field; tags: list = field(default_factory=list)`.
204
+ - **Decorator que usa `functools.wraps` pero no preserva type hints si la función decorada tiene anotaciones genéricas**: el `@wraps` copia `__wrapped__`, `__doc__` y `__name__`, pero el tipo de retorno inferido por `mypy` es el del wrapper, no el del wrapped. Causa: `functools.wraps` no puede preservar el tipado estático del wrapped — mypy ve el tipo del wrapper `Callable[..., Any]`. Solución: usar `TypeVar` y tipado genérico en el decorator con `ParamSpec` (Python 3.10+): `P = ParamSpec('P'); T = TypeVar('T')` y tipar el wrapper como `Callable[P, T]`.
205
+ - **`__slots__` en clase Python produce `TypeError: multiple bases have instance lay-out conflict`** al heredar de otra clase con `__slots__`: las subclases con `__slots__` requieren que todos los ancestros también tengan `__slots__`, o que el ancestro directo sea `object`. Causa: si `ClaseBase` no tiene `__slots__`, tiene un `__dict__` implícito; si `ClaseHija` tiene `__slots__`, hay conflicto de layout de memoria. Solución: o agregar `__slots__ = ()` vacío a la clase base, o eliminar `__slots__` de la subclase — no mezclar clases con y sin `__slots__` en la misma jerarquía.
206
+ - **`property` setter que modifica un campo privado no refleja el cambio en `__repr__` generado por dataclass**: el `@property` en un dataclass crea un campo de clase que conflictúa con el campo de instancia del dataclass. Causa: `@dataclass` genera `__repr__` basado en los campos declarados en `__init__` — si el setter modifica un atributo con nombre diferente (ej: `_valor`), `__repr__` muestra el campo original sin la modificación. Solución: usar `field(init=False, repr=False)` para el campo interno y exponer solo la `property` en la interfaz pública.
207
+ - **`assert` no es guard de invariantes en producción con `PYTHONOPTIMIZE=1` o `python -O`**: el bytecode optimizado **elimina** todos los `assert` del módulo, por lo que `assert x is not None; return x` puede retornar `None` violando el contrato `-> dict` en producción aunque pase tests en desarrollo. El test runner por defecto NO usa `-O`, por lo que el bug es invisible hasta que alguien despliega con `PYTHONOPTIMIZE=1` (configuración común para reducir memoria en imágenes Docker production). Causa: `assert` está documentado en Python como herramienta de **debugging**, no de validación. Solución: para invariantes que DEBEN cumplirse en producción, usar guard explícito con raise: `if x is None: raise HTTPException(500, "Invariante violado")` o `if x is None: raise RuntimeError(...)`. Reservar `assert` solo para tests, scripts, o pre-condiciones triviales en código de desarrollo. Regla rápida: si el assert protege un caso que activa una respuesta del usuario o un side-effect, NO es assert — es validación y debe ser `if/raise`.
208
+
209
+ ---
210
+
211
+ ## Patrones avanzados (carga bajo demanda)
212
+
213
+ Los siguientes patrones aparecen al integrar Python con pipelines reales
214
+ (conversores de documentos, clientes heterogéneos, cachés determinísticos,
215
+ detectores de texto, duplicación deliberada de lógica). El detalle completo
216
+ está en [recursos/patrones-avanzados.md](recursos/patrones-avanzados.md).
217
+
218
+ | # | Patrón | Cargar cuando |
219
+ |---|--------|---------------|
220
+ | 1 | Normalizadores: colapsar al formato canónico del PROYECTO | escribir `_normalizar(texto)` después de un conversor externo (MarkItDown, Pandoc, mammoth) |
221
+ | 2 | kwargs opcionales entre clientes hermanos: try/except TypeError | propagar un kwarg nuevo a clientes con signature heterogénea sin romper la interfaz |
222
+ | 3 | Caché content-addressable por SHA256 | función pura costosa (>1 s) que se invoca repetidamente con los mismos inputs |
223
+ | 4 | F401 en archivos con soft imports es intencional | módulo importa una dependencia opcional dentro de `try/except ImportError` |
224
+ | 5 | Detectores regex multi-pattern: extender scope sin refinar genera FP | un detector con OR de patterns se va a aplicar a un texto más ruidoso que el original |
225
+ | 6 | Tracer/replicador paralelo del motor: marca SYNC obligatoria | duplicar lógica del motor para telemetría, explainability o shadow runs |
226
+ | 7 | Fixtures con datos pre-procesados NO ejercen el path crudo | el motor prefiere leer un campo crudo opcional sobre uno condensado, y los fixtures son condensados |
227
+
228
+ Cargar la sección específica del recurso cuando aplique al problema en mano —
229
+ no cargar el archivo completo si solo se necesita un patrón.