**Checklist de aceptación (MCP “production‑ready” para ERP3)**

**0) Precondición (control de entorno)**
- Ejecutas TODO desde el venv: `backend\.venv\Scripts\python.exe` y `backend\.venv\Scripts\tpx.exe`.
- `TPX_PYTHON_EXE` seteado al python del venv.
- `TPX_MCP_STDOUT_MODE=redirect` (para no romper JSON-RPC con logs).

---

## A. Proceso único / “no doble Python”
**A1. Discover no debe lanzar 2 pythons**
- Acción: `tpx mcp` → `discover(paths=["tests/test_rut.py"], compat=true)`
- Evidencia requerida:
  - En el sistema, mientras corre discover, debe existir **a lo más 1 proceso** que matchee:
    - `python.exe -m pytest --collect-only -q tests/test_rut.py`
  - Y ese proceso debe ser el **python del venv**, no el Python global.
- Criterio de aprobación:
  - **0** procesos con `"C:\Users\...\Python310\python.exe" -m pytest ...`
  - **1** proceso (o 0 si cache) con `backend\.venv\Scripts\python.exe -m pytest ...`

**A2. Run no debe lanzar Python global**
- Acción: `run(selection=["tests/test_rut.py::test_clean_rut"], compat=true)`
- Evidencia:
  - 1 solo proceso pytest de venv.
- Aprobación: nunca aparece el Python global.

---

## B. No zombie / kill-tree (Windows)
**B1. Timeout mata el árbol**
- Setup: fuerza timeout bajo (ej: `TPX_MCP_PYTEST_RUN_TIMEOUT_S=3`)
- Acción: `run(...)` contra un test que tarde >3s (o algo que bloquee).
- Evidencia requerida:
  - El MCP responde `timeout`.
  - Luego de la respuesta, **no quedan procesos** `python.exe` con `-m pytest` activos.
- Aprobación:
  - “post-timeout” = 0 procesos pytest (padre e hijos).

**B2. Cancelación explícita**
- Si implementas cancel: iniciar `discover` largo, cancelar, verificar 0 procesos pytest.
- Aprobación: cancel es idempotente y no deja residuos.

---

## C. Determinismo (sin fallback paralelo)
**C1. Un collect por request**
- Acción: lanzar `discover` 5 veces seguidas (mismo input).
- Evidencia:
  - Nunca se observan 2 procesos pytest simultáneos para el mismo collect.
- Aprobación:
  - Máximo 1 proceso collect concurrente.
  - Si hay cola, se ve serializado (y no timeouts por contención interna).

---

## D. Performance mínima aceptable (con `conftest.py` pesado)
**D1. Discover “light” (si lo implementas)**
- Acción: `discover(light)` sobre `tests/`.
- Métrica objetivo:
  - Respuesta < 1s–2s (depende del tamaño, pero debe ser “instantánea” sin DB).
- Aprobación:
  - No se conecta a DB, no dispara alembic, no importa conftest.

**D2. Discover con pytest (compat=true)**
- Acción: `discover(compat=true, paths=["tests/test_rut.py"])`
- Evidencia:
  - Si `conftest.py` hace alembic, esto puede ser lento, pero debe:
    - No duplicar procesos
    - No colgarse indefinidamente
    - Respetar timeout y limpiar procesos al expirar
- Aprobación:
  - Con timeout alto (ej 180s), o termina OK o termina timeout, pero sin zombies.

**D3. Cache**
- Acción:
  - Primera vez `discover` (frío)
  - Segunda vez `discover` mismo input (caliente)
- Aprobación:
  - Segunda ejecución significativamente más rápida y sin levantar pytest (idealmente 0 procesos).

---

## E. Integridad de salida MCP (JSON-RPC limpio)
**E1. No stdout “basura”**
- Acción: `initialize → tools/list → discover → run`
- Evidencia:
  - stdout es sólo JSON-RPC (cualquier log va a stderr o a archivo).
- Aprobación:
  - 0 líneas no-JSON en stdout.

**E2. Reporte usable**
- Acción: `get_report`
- Aprobación:
  - Contiene `runId`, `mode`, `summary`, `error` estructurado si falla.

---

## F. Pruebas de regresión específicas ERP3 (mínimo)
- `discover(paths=["tests/test_rut.py"], compat=true)` (no debe colgarse y no duplicar procesos)
- `run(["tests/test_rut.py::test_clean_rut"], compat=true)` (debe correr o fallar por test, pero no por infraestructura)
- `discover(paths=["tests"], compat=true)` (aceptable que sea lento por conftest, pero debe ser controlable por timeout y sin zombies)

---

### “Red flags” (si aparecen, MCP no está listo)
- Aparece **Python global** en cualquier ejecución MCP cuando se corre desde venv.
- Se ven **2 pytest collect** simultáneos.
- Después de timeout/cancel quedan procesos `pytest` vivos.
- stdout se contamina con logs no JSON.




**Recomendaciones (ruta crítica) para que MCP sea realmente útil en ERP3**

### 1) Forzar “un solo Python” (esto está rompiendo todo)
**Síntoma observado:** durante `discover` aparecen **2 procesos** `pytest --collect-only` corriendo a la vez: uno con el python del **venv** y otro con el python **global** (Python310). Eso es receta para timeouts/locks.

**Cambio recomendado (TurboPlex/MCP server):**
- Resolver el ejecutable así (en este orden):
  1) `TPX_PYTHON_EXE` (si existe)
  2) `sys.executable`
  3) fallback a `python` del PATH

**Evitar** priorizar `sys._base_executable` porque en venv suele apuntar al Python global y te desincroniza entorno/deps.

**Trade-off:** si alguien dependía explícitamente de correr tests fuera del venv, tendrá que setearlo vía `TPX_PYTHON_EXE` (que es exactamente lo correcto).

---

### 2) Garantizar “un solo collect” por request (sin fallback paralelo)
Ahora mismo, por cómo se manifiesta, hay alta probabilidad de que existan dos caminos lanzando collect (o un fallback que se dispara mientras el otro sigue vivo).

**Cambio recomendado (TurboPlex/MCP server):**
- `discover` debe ejecutar **una** estrategia por request, no dos.
- Si quieres fallback, que sea **secuencial** y con cancelación clara:
  - `pytest_collect` falla/timeout → *termina el proceso* → recién ahí intentas `turboplex_collect` u otra vía.
- Agrega un “lock” por proceso servidor:
  - mientras haya un `pytest_collect` vivo, no inicies otro (o cola/deny).

**Trade-off:** menos “magia”, más determinismo. En suites con DDL en import-time esto es imprescindible.

---

### 3) Kill-tree real en Windows (si no, se te quedan pytest zombies)
En Windows, matar sólo el proceso padre no siempre mata hijos/descendientes, y eso te deja `pytest` colgado afectando el siguiente run.

**Cambio recomendado (TurboPlex/MCP server):**
- Implementar “kill tree” robusto:
  - Ideal: **Job Object** (Windows) para asegurar que al cerrar el job muere todo el árbol.
  - Alternativa mínima: crear un process group + `taskkill /T /F` al expirar timeout (menos elegante, pero efectivo).

**Trade-off:** Job Object requiere ctypes (sin deps externas) y un poco de código Win32, pero es la solución más sólida para runners.

---

### 4) `discover` no puede depender 100% de pytest en ERP3 (por tu `conftest.py`)
En ERP3, `tests/conftest.py` ejecuta **alembic upgrade** al importarse. Eso convierte `pytest --collect-only` en “bootstrapping DB + DDL”. Si `discover` hace eso, va a ser lento y frágil.

**Cambio recomendado (estrategia MCP):**
- Tener 2 modos:
  - `discover=light` (default): escaneo de archivos `tests/**/test_*.py` + parse básico (AST o regex de `def test_`) sin importar nada.
  - `discover=pytest` (opt-in): usa `pytest --collect-only` sólo cuando el usuario lo pide o cuando el cache detecta que vale la pena.

**Trade-off:** `discover=light` no ve tests generados dinámicamente por fixtures/plugins, pero te da UX rápida y estable.

---

### 5) Cache fuerte + invalidación (para que “ponerle cariño” se note)
- Cachear resultado de `discover` (nodeids) por:
  - hash de archivos de tests
  - hash de `conftest.py` / `pytest.ini`
  - versión de python + versión turboplex
- Si nada cambió, `discover` responde inmediato y `run` usa selección.

**Trade-off:** complejidad de invalidación, pero es “core” si tu suite tiene setup pesado.

---

## Recomendación extra (del lado ERP3, si quieres que pytest/MCP vuelen)
Esto es la raíz de muchos dolores: **migraciones en import-time** en `conftest.py`. Lo ideal es mover ese bootstrap a una fixture `session` (y/o gatearlo con una env var), para que:
- `pytest --collect-only` sea rápido y no toque DB
- `discover` deje de ser “ejecución parcial del sistema”

---

## Prioridad sugerida (en orden)
1) Python único (override → sys.executable)
2) No doble collect / no fallback paralelo
3) Kill-tree Windows (Job Object o taskkill /T)
4) discover “light” + cache
5) progreso/telemetría útil (no sólo heartbeat)

