Este proyecto consulta causas judiciales reales. Lo que devuelve determina el cómputo de plazos procesales, y un dato mal leído puede costar un plazo. Las reglas de abajo no son preferencias de estilo.
1. Nada que escriba en los sistemas del Poder Judicial. Ni ingreso de escritos, ni modificación, ni eliminación. No debe existir el código, ni siquiera desactivado ni detrás de una bandera. Hay un job de CI que busca referencias a endpoints de ingreso y falla si aparecen.
En julio de 2026, un ingreso automatizado masivo hizo colapsar la Oficina Judicial Virtual y terminó con una IP bloqueada y una solicitud de informe sobre responsabilidades disciplinarias y penales. La distinción entre leer e ingresar es la razón por la que este proyecto puede existir.
2. El ritmo de las consultas no se relaja. Régimen sostenido de una petición cada 5
segundos, con una ráfaga máxima de 4. INTERVALO_MINIMO y RAFAGA_MAXIMA en
src/mcp_pjud/client.py implementan juntos la cláusula CUARTA de las condiciones de uso, que
prohíbe sobrecargar la plataforma. No son constantes de rendimiento. El constructor rechaza
intervalos menores y hay un job de CI que verifica las dos, porque subir la ráfaga vacía la
garantía sin tocar el número que todos miran.
La ráfaga existe porque la sobrecarga es una propiedad del régimen: al portal le importa cuántas peticiones recibe, no cómo se reparten dentro de un minuto. Una consulta de actuaciones son cinco peticiones encadenadas para responder una sola pregunta, y con un intervalo plano tardaba veinticinco segundos. El tope de 4 la acota a esa cadena: alcanza para responder de una vez, no para barrer.
3. Ante 403, 429 o captcha: detención total. Sin reintento, sin rotación de IP, sin evasión, sin impersonación de fingerprint TLS. Si el sistema bloquea, la respuesta correcta es parar y avisar.
Total significa del proceso, no del host que rechazó. Se evaluó acotarla por host cuando entró
el buscador de fallos y se descartó al medir: los dos responden con la cookie TS<hex> de F5
BIG-IP, o sea comparten cortafuegos y el 403 llega antes de la aplicación.
4. Fallo ruidoso, nunca lista vacía. Si el parser no encuentra lo que espera, levanta
EstructuraInesperada. Una lista vacía se lee como "no hubo actuaciones", y así se pierden
plazos. Este es el error que el proyecto entero existe para evitar.
5. Sin persistencia de datos de terceros. Se consulta y se devuelve.
6. Las fixtures van anonimizadas. Las respuestas reales traen RUT y nombres de personas
naturales que son parte en juicios. tests/test_fixtures.py lo verifica. El mapeo de
anonimización no se versiona: publicarlo desharía la anonimización.
La columna Fec. Trámite de la plataforma trae dos fechas en una celda:
31/03/2026 (27/03/2026)
registro diligencia
fecha_registro es cuándo el tribunal registró el trámite. fecha_diligencia es cuándo el
ministro de fe la practicó, y es la que corre los plazos. Nunca las mezcles, nunca las
presentes como una sola, nunca elijas una en silencio cuando las fuentes se contradicen: para
eso existe discrepancia_fechas.
El detalle de causa muestra un cuaderno a la vez. Leer sólo el que viene por defecto produce una respuesta que parece completa y omite el cuaderno de apremio, donde viven el requerimiento de pago y el embargo.
src/mcp_pjud/
server.py Herramientas MCP, anotaciones, directiva operativa
client.py `Transporte` (ritmo, detención, bitácora) y la consulta de causas
juris.py Buscador de fallos. Comparte el transporte, no la sesión
parser.py Extracción de tablas. Sin red: se prueba offline
tests/
fixtures/ Respuestas reales anonimizadas. Ningún test consulta al Poder Judicial
contrato.json Lo que el servidor promete por el protocolo. Un cambio acá se aprueba a mano
docs/ Documentación publicada en Read the Docs
uv sync --all-groups
uv run pytest # sin red
uv run ruff check . && uv run ruff format --check .
uv run ty check # sin chequeador de tipos pasaban firmas que reventaban
uv run cog --check README.md docs/instalacion.md # bloques generados, sin editar a mano
uv run sphinx-build -W -b html docs docs/_build/html # -W: un aviso es un error
uv run zizmor .github/workflows/ .github/dependabot.yml
uv run mutmut run # testing de mutación, lento
APROBAR_CONTRATO=1 uv run pytest tests/test_contrato.py # aprobar un cambio de contratoTodo cambio de lógica deja un test que puede fallar, y hay que verlo en rojo. Rompe a propósito la línea que arreglaste, corre la suite, confirma que se cae, restaura.
El testing de mutación tiene tres trampas medidas, y las tres hacen que un mutante vivo sea mentira. La línea base del 24 de agosto de 2026 son 4.197 mutantes con 3.110 muertos: una corrida que dé mucho menos es sospechosa del corredor antes que del código.
mutmut runcon nombres de mutantes explícitos no vuelve a copiarsrc/nitests/amutants/, así que mide el código de ahora contra los tests de la última corrida completa. Para volver a medir hay que borrarmutants/. Sin eso aparecieron 570 falsos💥.- Al verificar una mutación a mano, escribir el archivo y correr
pytestdentro del mismo segundo puede leer el.pycanterior. El rojo es confiable; el verde no: si un mutante sobrevive, vuelve a medirlo con un segundo de separación antes de concluir nada. mutmutcorre cada mutante en un hijo porfork(), yhttpx.Client()pregunta por el proxy del sistema, que en macOS entra a CoreFoundation. CoreFoundation después de un fork revienta: el 24 de agosto de 2026 eso convirtió 2.071 mutantes en💥, o sea la mitad de aquella medición era del corredor.tests/conftest.pypone una variable*_proxypara quegetproxies()responda desde el entorno y no le pregunte al sistema. Si vuelven los💥en masa, mirar ahí antes que al código.
De los mutantes vivos, la mayoría son literales de mensaje que mutmut pasa a mayúsculas o envuelve en marcadores: no cambian comportamiento y no vale la pena perseguirlos. Lo que sí vale son los que cambian qué se pide o qué se lee.
No es ritual. Durante el desarrollo esto detectó dos veces guardias que no podían fallar: un
test central que seguía verde con el bug puesto porque otro camino del código tapaba la
regresión, y un chequeo en grep cuyo comando erraba y cuya salida vacía se leía como "sin
hallazgos".
Idioma: español de Chile en código, comentarios, commits, issues y documentación. Sin
voseo (tienes, no tenés). Los nombres de campo del modelo también van en español: quien lee
la salida es un abogado chileno.
Comentarios: explican por qué, no qué. Un comentario que repite lo que dice la línea siguiente sobra. Los que valen son los que registran una decisión o una trampa.
Prosa: sin guiones largos. Usa comas, paréntesis o dos puntos.
Documentación: docs/ sigue Diátaxis, que separa cuatro cosas que
se suelen mezclar. Antes de escribir una página nueva, decide cuál es y ponla donde va:
| Tipo | Sirve para | Acá |
|---|---|---|
| Cómo se hace | resolver una tarea concreta | instalacion, ejemplos |
| Referencia | consultar un dato exacto | herramientas |
| Explicación | entender por qué | uso, cumplimiento, licencia, roadmap |
| Tutorial | aprender haciendo | no hay, y no hace falta |
Mezclarlas es lo que produce una página que no sirve para nada: una referencia con opiniones no se puede consultar rápido, y una explicación con tablas de parámetros no se puede leer.
Un dato repetido es un dato que va a quedar viejo. Las cifras medidas, el intervalo mínimo
y los topes viven en el código y se interpolan donde se pueda. Donde no se puede, porque la
prosa se escribe a mano, tests/test_documentacion.py compara cada dato repetido contra su
única fuente. Si agregas una afirmación verificable a la documentación, agrégale el test.
Commits: Conventional Commits en español.
El registro de cambios no es el lugar para contar la historia. Cada entrada de
CHANGELOG.md dice QUÉ cambió y, si hace falta, qué tiene que hacer distinto quien actualiza.
Una o dos frases, no una o dos líneas ajustadas. Cómo se encontró el problema, qué
hipótesis fallaron y por qué se eligió una versión y no otra van en el PR y en el mensaje del
commit, que es donde alguien los busca.
Se degradó solo una vez: las entradas pasaron a ser párrafos, una versión llegó a 333 líneas
con 67 viñetas, y encima repetían lo que la sección de al lado ya decía. test_documentacion.py
acota el largo de cada viñeta.
Ojo con lo que ese guardia puede y no puede hacer: cuenta líneas, así que atrapa el desborde y no el contenido. Una viñeta que justo cabe en el límite y enumera cómo funciona algo lo pasa sin problema, y aun así sobra. Eso ya se coló dos veces.
El proyecto usa PolyForm Strict 1.0.0, que no otorga derecho a modificar el software. El permiso para preparar contribuciones viene del acuerdo de contribución, sección 5.
Si actúas por encargo de una persona, esa persona debe aceptar el acuerdo en el pull request. No lo aceptes en su nombre.
- Proponer Playwright, Selenium o impersonación TLS. Está medido: el filtro de la plataforma actúa sobre el string del User-Agent, no sobre la huella TLS. HTTP plano con user agent identificable pasa.
- Agregar competencias sin verificarlas contra el sistema real. Hay seis verificadas y son las
que
MODULOSnombra; lo que no está ahí se rechaza a propósito en vez de adivinar sus parámetros. El detalle se lee en cinco: en penal se busca y no se abre, por decisión. - Levantar un segundo servidor MCP para jurisprudencia. Serían dos procesos con dos
semáforos, o sea el doble de peticiones contra la misma institución, y
ACCEPTABLE_USE.mdprohíbe correr instancias en paralelo. Por esojuris.pycomparte el transporte. - Agregar buscadores de
juris.pjud.clsin verificarlos. Cada uno declara sus propios campos Solr, y diez de los diez están medidos. Se exponen siete, y los tres que quedan fuera es por decisión y no por falta de medición: penales y el compendio de extranjería publican datos de una persona (el nombre del imputado, la nacionalidad del recurrente), y líneas jurisprudenciales no es un buscador de fallos, sino de temas que reusan los mismos campos Solr con otro significado. - Presentar la salida como información oficial del Poder Judicial.