""" 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