Files
hechakuaa_bot/csj_client_nuevos_metodos.py
markosbenitez 7743919695 Initial commit — Pedrito Hechakuaa v0
Bot de Telegram para monitoreo de notificaciones judiciales del PJ Paraguay.
Multi-tenant con onboarding conversacional, audit log, y polling automático.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:58:32 -03:00

257 lines
9.2 KiB
Python

"""
NUEVOS MÉTODOS para agregar a csj_client.py
Basados en reconocimiento del 22/05/2026.
Agregar:
1. La clase CasoJudicial después de NotificacionPendiente
2. La clase ActuacionCaso después de CasoJudicial
3. Los métodos buscar_expedientes() y listar_actuaciones() dentro de CSJClient
"""
# ─── NUEVOS MODELOS (agregar después de NotificacionPendiente) ────────────────
class CasoJudicial:
"""Expediente judicial devuelto por búsqueda."""
__slots__ = (
"cod_caso_judicial",
"caratula",
"origen",
"nro_expediente_numero",
"nro_expediente_anio",
"circunscripcion",
"nombre_circunscripcion",
"descripcion_despacho_judicial",
"descripcion_estado_despacho_caso",
"cod_estado_despacho_caso",
"es_corte",
"cantidad_total_elementos",
)
def __init__(self, data: dict) -> None:
self.cod_caso_judicial: int = data["codCasoJudicial"]
self.caratula: str = data.get("caratula") or "sin carátula"
self.origen: int = data.get("origen", 0)
self.nro_expediente_numero: int | None = data.get("nroExpedienteNumero")
self.nro_expediente_anio: int | None = data.get("nroExpedienteAnio")
self.circunscripcion: str | None = data.get("nombreCircunscripcion") or data.get("circunscripcion")
self.descripcion_despacho_judicial: str | None = data.get("descripcionDespachoJudicial")
self.descripcion_estado_despacho_caso: str | None = data.get("descripcionEstadoDespachoCaso")
self.cod_estado_despacho_caso: int = data.get("codEstadoDespachoCaso", 0)
self.es_corte: int = data.get("esCorte", 0)
self.cantidad_total_elementos: int = data.get("cantidadTotalElementos", 0)
@property
def expediente_str(self) -> str:
if self.nro_expediente_numero and self.nro_expediente_anio:
return f"{self.nro_expediente_numero}/{self.nro_expediente_anio}"
return "sin número"
@property
def estado(self) -> str:
return self.descripcion_estado_despacho_caso or "sin estado"
@property
def resumen(self) -> str:
"""Texto compacto para mostrar en listados."""
partes = [self.caratula]
if self.expediente_str != "sin número":
partes.append(f"Exp. {self.expediente_str}")
if self.circunscripcion:
partes.append(self.circunscripcion)
partes.append(self.estado)
return " | ".join(partes)
class ActuacionCaso:
"""Actuación/movimiento dentro de un expediente."""
__slots__ = (
"cod_actuacion_caso",
"cod_caso_judicial",
"cod_tipo_actuacion_global",
"descripcion_actuacion",
"descripcion_tipo_actuacion",
"descripcion_despacho",
"descripcion_proceso_caso",
"fecha_actuacion",
"fecha_finalizacion",
"estado",
"grupo",
"origen",
)
def __init__(self, data: dict) -> None:
self.cod_actuacion_caso: int = data["codActuacionCaso"]
self.cod_caso_judicial: int = data["codCasoJudicial"]
self.cod_tipo_actuacion_global: int | None = data.get("codTipoActuacionGlobal")
self.descripcion_actuacion: str | None = data.get("descripcionActuacion")
self.descripcion_tipo_actuacion: str | None = data.get("descripcionTipoActuacion")
self.descripcion_despacho: str | None = data.get("descripcionDespacho")
self.descripcion_proceso_caso: str | None = data.get("descripcionProcesoCaso")
self.fecha_actuacion: str | None = data.get("fechaActuacion")
self.fecha_finalizacion: str | None = data.get("fechaFinalizacion")
self.estado: str | None = data.get("estado")
self.grupo: str | None = data.get("grupo")
self.origen: int | None = data.get("origen")
@property
def fecha_formateada(self) -> str:
if not self.fecha_actuacion:
return "sin fecha"
try:
from datetime import datetime
dt = datetime.fromisoformat(self.fecha_actuacion)
return dt.strftime("%d/%m/%Y")
except ValueError:
return self.fecha_actuacion
@property
def tipo(self) -> str:
return self.descripcion_tipo_actuacion or f"Tipo {self.cod_tipo_actuacion_global}"
# ─── NUEVOS MÉTODOS para la clase CSJClient ──────────────────────────────────
async def buscar_expedientes(
self,
*,
caratula: str | None = None,
numero: int | None = None,
anio: int | None = None,
cantidad: int = 10,
offset: int = 0,
) -> tuple[list[CasoJudicial], int]:
"""
Busca expedientes por carátula y/o número+año.
Retorna (lista de casos, total de registros).
Parámetros confirmados por reconocimiento 22/05/2026:
- caratula: búsqueda parcial por texto en carátula
- NroExpedienteNumero: número del expediente (sin año)
- NroExpedienteAnho: año del expediente (con 'h', no 'ñ')
- offset/cantidad: paginación
Al menos uno de caratula, numero, o (numero+anio) debe proveerse.
"""
if not caratula and not numero:
raise ValueError("Se requiere al menos carátula o número de expediente")
session = await self._asegurar_sesion()
assert self._http is not None
params: dict = {
"offset": offset,
"cantidad": cantidad,
"caratula": caratula or "",
}
if numero is not None:
params["NroExpedienteNumero"] = numero
if anio is not None:
params["NroExpedienteAnho"] = anio
try:
r = await self._http.get(
"/api/gestion-partes/CasoJudicial",
params=params,
headers=session.auth_headers(),
)
except httpx.HTTPError as e:
raise CSJConnectionError(f"Error al buscar expedientes: {e}") from e
if r.status_code == 401:
self._session = None
session = await self._asegurar_sesion()
r = await self._http.get(
"/api/gestion-partes/CasoJudicial",
params=params,
headers=session.auth_headers(),
)
r.raise_for_status()
data = r.json()
items = data.get("resultado") or []
total = data.get("cantidadTotalRegistros") or 0
casos = []
for item in items:
try:
casos.append(CasoJudicial(item))
except (KeyError, TypeError) as e:
logger.warning("error_parseando_caso", error=str(e), item=str(item)[:200])
logger.info("expedientes_encontrados", total=total, devueltos=len(casos))
return casos, total
async def listar_actuaciones(
self,
cod_caso_judicial: int,
origen: int,
cantidad: int = 10,
offset: int = 0,
incluir_notificaciones: bool = False,
) -> tuple[list[ActuacionCaso], int]:
"""
Retorna las actuaciones (movimientos) de un expediente.
URL confirmada por reconocimiento 22/05/2026:
GET /api/gestion-partes/CasoJudicial/{codCasoJudicial}/{origen}/0/actuaciones
El parámetro {origen} es el campo 'origen' del CasoJudicial:
- origen=2: expediente del sistema viejo integrado
- origen=0: expediente del sistema nuevo
El segundo posicional (/0/) parece fijo en todos los casos observados.
"""
session = await self._asegurar_sesion()
assert self._http is not None
url = f"/api/gestion-partes/CasoJudicial/{cod_caso_judicial}/{origen}/0/actuaciones"
try:
r = await self._http.get(
url,
params={
"offset": offset,
"cantidad": cantidad,
"incluirNotificaciones": str(incluir_notificaciones).lower(),
},
headers=session.auth_headers(),
)
except httpx.HTTPError as e:
raise CSJConnectionError(f"Error al listar actuaciones: {e}") from e
if r.status_code == 401:
self._session = None
session = await self._asegurar_sesion()
r = await self._http.get(
url,
params={
"offset": offset,
"cantidad": cantidad,
"incluirNotificaciones": str(incluir_notificaciones).lower(),
},
headers=session.auth_headers(),
)
r.raise_for_status()
data = r.json()
items = data.get("resultado") or []
total = data.get("cantidadTotalRegistros") or 0
actuaciones = []
for item in items:
try:
actuaciones.append(ActuacionCaso(item))
except (KeyError, TypeError) as e:
logger.warning("error_parseando_actuacion", error=str(e), item=str(item)[:200])
logger.info(
"actuaciones_listadas",
cod_caso=cod_caso_judicial,
total=total,
devueltas=len(actuaciones),
)
return actuaciones, total