Spaces:
Sleeping
Sleeping
""" | |
Este m贸dulo contiene toda la l贸gica de negocio para la aplicaci贸n de contestaci贸n de tutelas. | |
Se encarga de interactuar con la API de Gemini, procesar los archivos PDF y DOCX, | |
y orquestar el flujo de trabajo completo. | |
""" | |
import os | |
import json | |
import re | |
import tempfile | |
import pathlib | |
from docx import Document | |
from docx.shared import Inches | |
import google.generativeai as genai | |
import gradio as gr | |
# --- CONFIGURACI脫N GLOBAL --- | |
API_KEY = os.environ.get("GEMINI_API_KEY") | |
if API_KEY: | |
genai.configure(api_key=API_KEY) | |
EXAMPLE_PDF_PATH = "ejemplo_tutela.pdf" | |
# --- PLANTILLAS DE PROMPTS --- | |
EXTRACT_PROMPT = ''' | |
Del siguiente documento de acci贸n de tutela, extrae de forma precisa y concisa la siguiente informaci贸n: | |
1. **juzgado**: Nombre completo del juzgado al que se dirige la tutela. | |
2. **ciudad**: La ciudad donde se presenta la tutela. | |
3. **radicado**: El n煤mero de radicado o referencia del caso. | |
4. **accionante**: El nombre completo del demandante o accionante. | |
5. **direccion_accionante**: La direcci贸n f铆sica del accionante. | |
6. **email**: La lista de correos electr贸nicos de notificaci贸n. | |
7. **datos**: Una lista de las partes involucradas (demandante, demandado). | |
8. **hechos**: Un resumen claro y numerado de los hechos que motivan la tutela. | |
9. **argumentos**: Un resumen de los argumentos legales del accionante. | |
10. **peticiones**: Un resumen de lo que el accionante solicita al juez. | |
Devuelve la respuesta como un objeto JSON v谩lido con las claves especificadas. | |
''' | |
ENCABEZADO_PROMPT = ''' | |
Con base en el documento de ejemplo, redacta el encabezado para una contestaci贸n de tutela. | |
Usa el mismo estilo, tono y formato del ejemplo. | |
Informaci贸n de la tutela a contestar: | |
- Juzgado: {juzgado} | |
- Ciudad: {ciudad} | |
- Radicado: {radicado} | |
- Accionante: {accionante} | |
- Accionado: (El accionado es la entidad que representas, no lo inventes, usa un placeholder como [Nombre de la Entidad Accionada]) | |
Devuelve un JSON con la clave "encabezado" y el texto correspondiente. | |
''' | |
HECHOS_PROMPT = ''' | |
Actuando como un abogado experto, redacta la secci贸n de \'HECHOS\' para una contestaci贸n de tutela, bas谩ndote estrictamente en el estilo y formato del documento de ejemplo. | |
**Instrucci贸n clave: Debes responder a CADA UNO de los hechos numerados presentados por el accionante.** La respuesta debe seguir el formato "Al hecho PRIMERO, es cierto.", "Al hecho SEGUNDO, no es cierto por...", o una variaci贸n similar que se alinee con el ejemplo. | |
**Hechos del Accionante a Responder:** | |
{hechos} | |
**Contexto General de las Partes:** | |
{datos} | |
Devuelve un JSON v谩lido con una 煤nica clave "hechos" que contenga el texto completo de la secci贸n. | |
''' | |
FUNDAMENTOS_PROMPT = ''' | |
Actuando como un abogado experto, redacta la secci贸n de \'FUNDAMENTOS DE DERECHO\' para una contestaci贸n de tutela, bas谩ndote estrictamente en el estilo y formato del documento de ejemplo. | |
**Instrucci贸n clave: Debes rebatir, uno por uno, los argumentos legales del accionante.** La estructura de tu respuesta debe reflejar una contra-argumentaci贸n directa a cada punto presentado. | |
**Argumentos del Accionante a Rebatir:** | |
{argumentos} | |
**Contexto General de las Partes:** | |
{datos} | |
Devuelve un JSON v谩lido con una 煤nica clave "fundamentos" que contenga el texto completo de la secci贸n. | |
''' | |
PETICIONES_PROMPT = ''' | |
Actuando como un abogado experto, redacta la secci贸n de \'PETICIONES\' para una contestaci贸n de tutela, bas谩ndote estrictamente en el estilo y formato del documento de ejemplo. | |
**Instrucci贸n clave: Debes pronunciarte sobre CADA UNA de las peticiones del accionante.** La respuesta debe ser una oposici贸n directa y formal a sus pretensiones, siguiendo el tono del ejemplo. | |
**Peticiones del Accionante a Responder:** | |
{peticiones} | |
**Contexto General de las Partes:** | |
{datos} | |
Devuelve un JSON v谩lido con una 煤nica clave "peticiones" que contenga el texto completo de la secci贸n. | |
''' | |
# --- FUNCIONES AUXILIARES --- | |
def _safe_json_loads(text: str) -> dict: | |
""" | |
Analiza de forma robusta un string que deber铆a ser JSON. | |
Intenta limpiar el texto y encontrar un objeto JSON v谩lido. | |
""" | |
if not isinstance(text, str): | |
text = str(text) | |
# Elimina las vallas de formato de c贸digo (```json ... ```) si existen. | |
text = re.sub(r"^```(?:json)?\s*|\s*```$", "", text, flags=re.S).strip() | |
try: | |
return json.loads(text) | |
except json.JSONDecodeError as e: | |
# Si falla, intenta encontrar el primer y m谩s grande bloque JSON (entre { y }). | |
match = re.search(r"\{.*\}", text, re.DOTALL) | |
if match: | |
json_like_string = match.group(0) | |
try: | |
return json.loads(json_like_string) | |
except json.JSONDecodeError: | |
raise ValueError(f"Error al decodificar el JSON extra铆do: {json_like_string}") from e | |
raise ValueError(f"No se pudo encontrar un objeto JSON v谩lido en la respuesta: {text}") from e | |
def _safe_json_field(raw, field): | |
"""Extrae de forma segura un campo de una respuesta JSON.""" | |
if raw is None: | |
return "" | |
if isinstance(raw, list): | |
return str(raw) | |
if isinstance(raw, dict): | |
return str(raw.get(field, "")) | |
try: | |
obj = _safe_json_loads(str(raw)) | |
if isinstance(obj, dict): | |
return str(obj.get(field, "")) | |
return str(obj) # Devuelve el objeto parseado si no es un diccionario | |
except ValueError: | |
return str(raw) # Si todo falla, devuelve el texto original | |
def _add_section(doc: Document, title: str, body: str): | |
"""A帽ade una secci贸n con t铆tulo y cuerpo a un documento de Word.""" | |
doc.add_heading(title, level=1) | |
body = str(body) if body is not None else "" | |
blocks = [b.strip() for b in body.strip().split("\n\n") if b.strip()] | |
if not blocks: | |
doc.add_paragraph("(sin contenido)") | |
return | |
for b in blocks: | |
doc.add_paragraph(b) | |
# --- L脫GICA PRINCIPAL CON LA API DE GEMINI --- | |
def extract_tutela_parts(uploaded_file_path: str) -> dict: | |
"""Usa Gemini para extraer las partes clave de una tutela.""" | |
model = genai.GenerativeModel("gemini-1.5-flash") | |
uploaded_file = genai.upload_file(path=uploaded_file_path, display_name="Tutela PDF") | |
try: | |
response = model.generate_content( | |
[EXTRACT_PROMPT, uploaded_file], | |
generation_config=genai.types.GenerationConfig( | |
response_mime_type="application/json", | |
response_schema={ | |
"type": "object", | |
"properties": { | |
"datos": {"type": "array", "items": {"type": "string"}}, | |
"hechos": {"type": "array", "items": {"type": "string"}}, | |
"argumentos": {"type": "array", "items": {"type": "string"}}, | |
"peticiones": {"type": "array", "items": {"type": "string"}}, | |
"juzgado": {"type": "string"}, | |
"direccion_juzgado": {"type": "string"}, | |
"email": {"type": "array", "items": {"type": "string"}}, | |
"radicado": {"type": "string"}, | |
"accionante": {"type": "string"}, | |
"direccion_accionante": {"type": "string"}, | |
"ciudad": {"type": "string"} | |
}, | |
"required": ["juzgado", "ciudad", "radicado", "accionante", "hechos", "argumentos", "peticiones"] | |
} | |
) | |
) | |
return _safe_json_loads(response.text) | |
finally: | |
genai.delete_file(uploaded_file.name) | |
def generate_response_section(section_name: str, prompt_template: str, context_data: dict) -> str: | |
"""Funci贸n gen茅rica para generar una secci贸n de la contestaci贸n.""" | |
model = genai.GenerativeModel("gemini-1.5-flash") | |
example_filepath = pathlib.Path(EXAMPLE_PDF_PATH) | |
if not example_filepath.is_file(): | |
raise FileNotFoundError(f"El archivo de ejemplo '{EXAMPLE_PDF_PATH}' no se encontr贸.") | |
system_instruction = prompt_template.format(**context_data) | |
example_file = genai.upload_file(path=str(example_filepath), display_name="Ejemplo Tutela") | |
try: | |
response = model.generate_content( | |
[system_instruction, example_file], | |
generation_config=genai.types.GenerationConfig( | |
response_mime_type="application/json", | |
response_schema={"type": "object", "properties": {section_name: {"type": "string"}}} | |
) | |
) | |
return _safe_json_field(response.text, section_name) | |
finally: | |
try: | |
genai.delete_file(example_file.name) | |
except Exception: | |
pass | |
def write_tutela_docx(encabezado: str, hechos: str, peticiones: str, fundamentos: str) -> str: | |
"""Crea un archivo .docx con el encabezado y las secciones generadas.""" | |
doc = Document() | |
for s in doc.sections: | |
s.top_margin, s.bottom_margin, s.left_margin, s.right_margin = [Inches(1)] * 4 | |
# A帽adir encabezado | |
if encabezado: | |
doc.add_paragraph(encabezado) | |
doc.add_paragraph("\n") # Espacio | |
_add_section(doc, "HECHOS", hechos) | |
_add_section(doc, "FUNDAMENTOS DE DERECHO", fundamentos) | |
_add_section(doc, "PETICIONES", peticiones) | |
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp: | |
doc.save(tmp.name) | |
return tmp.name | |
# --- FUNCI脫N PRINCIPAL DEL FLUJO DE TRABAJO --- | |
def full_workflow(uploaded_file_path: str): | |
"""Orquesta todo el proceso: desde la subida del PDF hasta la generaci贸n del DOCX.""" | |
if not uploaded_file_path: | |
raise gr.Error("Por favor, sube un archivo de tutela en formato PDF.") | |
try: | |
# 1. Extraer las partes de la tutela del usuario. | |
extracted_data = extract_tutela_parts(uploaded_file_path) | |
# 2. Generar cada secci贸n de la contestaci贸n. | |
encabezado_generado = generate_response_section("encabezado", ENCABEZADO_PROMPT, extracted_data) | |
hechos_generados = generate_response_section("hechos", HECHOS_PROMPT, extracted_data) | |
fundamentos_generados = generate_response_section("fundamentos", FUNDAMENTOS_PROMPT, extracted_data) | |
peticiones_generadas = generate_response_section("peticiones", PETICIONES_PROMPT, extracted_data) | |
# 3. Crear el archivo .docx con los textos generados. | |
docx_path = write_tutela_docx(encabezado_generado, hechos_generados, peticiones_generadas, fundamentos_generados) | |
# 4. Devolver todos los resultados a la interfaz de Gradio. | |
return hechos_generados, fundamentos_generados, peticiones_generadas, docx_path | |
except FileNotFoundError as e: | |
print(f"Error: {e}") | |
raise gr.Error(f"Error de configuraci贸n del servidor: {e}") | |
except Exception as e: | |
print(f"An unexpected error occurred: {e}") | |
raise gr.Error(f"Ha ocurrido un error inesperado: {e}") | |