Spaces:
Sleeping
Sleeping
File size: 10,115 Bytes
c3b3ec1 25cd901 1cccf6d c3b3ec1 9e8c546 c3b3ec1 9e8c546 7f7bdb6 c3b3ec1 9e8c546 c3b3ec1 9e8c546 c3b3ec1 9e8c546 1cccf6d 9e8c546 c3b3ec1 1cccf6d 9e8c546 6764e62 7b4024f 9e8c546 1cccf6d 9e8c546 7b4024f 9e8c546 205a6eb 9e8c546 6764e62 9e8c546 c3b3ec1 b4c60f7 6e503cd b4c60f7 c3b3ec1 6e503cd 6764e62 6e503cd 6764e62 6e503cd 6764e62 6e503cd 6764e62 b4c60f7 c3b3ec1 2f2f188 b4c60f7 6764e62 c3b3ec1 25cd901 c3b3ec1 b4c60f7 6764e62 c3b3ec1 6764e62 c3b3ec1 205a6eb 9e8c546 6e503cd c3b3ec1 7f7bdb6 c3b3ec1 9e8c546 c3b3ec1 25cd901 c3b3ec1 6764e62 6e503cd 6764e62 6e503cd 25cd901 ffd1b0a 6e503cd 25cd901 9e8c546 c3b3ec1 205a6eb 1cccf6d 205a6eb 0e5dd2d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
import os
import json
import traceback
from flask import Flask, request, jsonify, render_template
import PyPDF2
from openai import OpenAI
import fitz # PymuPDF
from PIL import Image # Pillow
import pytesseract
import io
# 1. Configuraci贸n de Flask
app = Flask(__name__, template_folder='.')
# 2. Configuraci贸n de OpenAI
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY"),
)
def ocr_page(img_bytes):
"""Realiza OCR en una imagen (byte stream) usando Tesseract."""
try:
image = Image.open(io.BytesIO(img_bytes))
text = pytesseract.image_to_string(image, lang='spa')
return text
except Exception as e:
print(f"Error en Pytesseract/OCR: {e}")
return ""
def extract_text_from_file(file):
"""Extrae texto de un PDF/TXT, usando OCR si es necesario en todas las p谩ginas."""
file_bytes = file.read()
total_text = ""
# Intento de extracci贸n nativa
try:
if file.filename.endswith('.pdf'):
pdf_reader = PyPDF2.PdfReader(io.BytesIO(file_bytes))
for page in pdf_reader.pages:
total_text += page.extract_text() or ""
if len(total_text.strip()) > 100:
return total_text.strip()
elif file.filename.endswith('.txt'):
return file_bytes.decode('utf-8').strip()
except Exception:
pass
# Fallback a OCR
if file.filename.endswith('.pdf'):
try:
document = fitz.open(stream=file_bytes, filetype="pdf")
ocr_text = ""
for i in range(len(document)):
page = document.load_page(i)
pix = page.get_pixmap(dpi=300)
img_bytes = pix.tobytes("ppm")
ocr_text += ocr_page(img_bytes) + "\n"
if len(ocr_text.strip()) > 100:
return ocr_text.strip()
except Exception as e:
raise Exception("Fallo la extracci贸n de texto del PDF. Aseg煤rate de que el documento no sea un archivo de imagen corrupto.")
return ""
def generate_summary_openai(text):
"""
Genera un an谩lisis experto en formato JSON.
"""
try:
# Esquema JSON EXPANDIDO
json_schema = {
"type": "object",
"properties": {
"tipo_documento_detectado": {"type": "string", "description": "Si es Certificado de Tradici贸n y Libertad (CTL), Certificado JAC, u Otro."},
"analisis_extenso_narrativo": {"type": "string", "description": "Resumen narrativo detallado y cr铆tico, explicando en un p谩rrafo los hallazgos m谩s importantes, el tipo de documento, su validez y los riesgos detectados."},
"identificacion_principal": {"type": "string", "description": "N煤mero de Matr铆cula Inmobiliaria o Nombre de la Junta/Persona Principal."},
"propietario_actual": {"type": "string", "description": "Nombre completo del propietario o titular del derecho."},
# --- CAMPOS NUEVOS DE AN脕LISIS ---
"valor_o_area_registrada": {"type": "string", "description": "El valor catastral o el 谩rea (metros cuadrados o hect谩reas) de la propiedad si est谩 disponible."},
"fecha_y_entidad_expedicion": {"type": "string", "description": "Fecha de expedici贸n del documento y entidad que lo emiti贸 (ej. 07/10/2025, Superintendencia de Notariado y Registro)."},
"situacion_juridica_detallada": {"type": "string", "description": "Estado legal pormenorizado: Mencionar si hay hipotecas, embargos, o si la tradici贸n es falsa. Usar la palabra 'Limpio' si no hay grav谩menes serios."},
# --- FIN CAMPOS NUEVOS ---
"ultima_transaccion": {"type": "string", "description": "Acto de la 煤ltima adquisici贸n (ej. Compraventa, Sucesi贸n, o Acto comunal)."},
"limites_o_restricciones": {"type": "string", "description": "Presencia de Patrimonio de Familia, Afectaci贸n a Vivienda, o restricciones comunales/vecinales."},
"analisis_riesgo_legal": {"type": "string", "description": "Clasificaci贸n de riesgo: 'Bajo', 'Medio' o 'Alto'. Justificar brevemente esta clasificaci贸n."},
"conclusion_final": {"type": "string", "description": "Conclusi贸n final sobre la validez del t铆tulo o derecho y aptitud para una transacci贸n. Debe ser una recomendaci贸n clara (Apto/Apto con Reservas/No Apto)."}
},
"required": ["tipo_documento_detectado", "analisis_extenso_narrativo", "propietario_actual", "conclusion_final"]
}
schema_text = json.dumps(json_schema, indent=2)
prompt_text = (
"Eres un **abogado experto en derecho inmobiliario colombiano** y en el an谩lisis exhaustivo de Certificados de Tradici贸n y Libertad (CTL) o Matr铆culas Inmobiliarias. Tienes la capacidad avanzada de identificar, analizar e interpretar referencias a **sentencias judiciales, actos de la Fiscal铆a General de la Naci贸n, la Sociedad de Activos Especiales (SAE), decisiones de la autoridad ambiental (ej. CAR), o de otras entidades legales** del estado colombiano que est茅n contenidas o mencionadas en el historial registral del inmueble."
"Tu tarea es analizar detalladamente la historia registral de TODAS las p谩ginas del documento proporcionado y responder con un resumen de **7 puntos clave** utilizando vi帽etas. "
"Los 7 puntos deben ser cr铆ticos y exhaustivos para un estudio de t铆tulos detallado:\n\n"
"1. **Identificaci贸n Completa del Predio y Propietario Actual**: Menciona el n煤mero de la **Matr铆cula Inmobiliaria**, el (los) nombre(s) del (los) propietarios actuales, el tipo de tenencia (ej. Plena Propiedad), e incluye la extracci贸n de los **Linderos Generales** y los **Folios** de donde provienen los datos.\n"
"2. **Grav谩menes Vigentes y Montos**: Indicar **claramente** la existencia o inexistencia de **Hipoteca, Embargo, o Demanda Civil (Litis)**. Si existen, menciona la anotaci贸n y el valor del gravamen si est谩 registrado.\n"
"3. **Limitaciones de Dominio Vigentes**: Indicar la existencia o inexistencia de **Patrimonio de Familia, Afectaci贸n a Vivienda Familiar, o Servidumbres**. Si existe, mencionar el n煤mero de anotaci贸n.\n"
"4. **脷ltima Transacci贸n Registrada**: Detalla el tipo de acto (ej. compraventa, sucesi贸n, liquidaci贸n) y el n煤mero de **Escritura P煤blica** con que se adquiri贸 el inmueble.\n"
"5. **Cancelaci贸n de Grav谩menes Anteriores**: Confirma si todas las hipotecas o embargos anteriores fueron **debidamente cancelados** y menciona el n煤mero de anotaci贸n de la cancelaci贸n.\n"
"6. **Riesgos por Procesos Estatales/Judiciales (Fiscal铆a, SAE y Ambientales)**: Indica si existen anotaciones que sugieran Falsa Tradici贸n, o si el inmueble ha sido objeto de procesos de **extinci贸n de dominio (SAE)**, saneamiento, o si hay menciones de **sentencias judiciales, actos de la Fiscal铆a, o temas ambientales** (ej. reserva, afectaci贸n) que afecten el dominio.\n"
"7. **Conclusi贸n de Titulabilidad y Alerta de Entidades Estatales**: Breve conclusi贸n legal sobre la **limpieza** del folio, si existen riesgos mayores para un nuevo comprador o entidad financiera. **ALERTA:** Si NO existe registrada ninguna sentencia o acto de la Fiscal铆a, SAE, o autoridad ambiental en el CTL/VUR/Escritura, debes mencionarlo claramente en esta conclusi贸n como un dato relevante para el estudio de t铆tulos.\n\n"
f"\n\nEsquema JSON requerido:\n{schema_text}"
f"\n\nTexto del Documento:\n\n{text}"
)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": prompt_text}
],
response_format={"type": "json_object"},
temperature=0.3,
)
json_string = response.choices[0].message.content.strip()
structured_data = json.loads(json_string)
return structured_data
except Exception as e:
raise
# --- Rutas de Flask (El resto del app.py se mantiene igual) ---
@app.route('/')
def index():
return render_template('index.html')
@app.route('/summarize', methods=['POST'])
def summarize():
if 'file' not in request.files:
return jsonify({'error': 'No se ha subido ning煤n archivo.'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No se ha seleccionado ning煤n archivo.'}), 400
try:
raw_text = extract_text_from_file(file)
if not raw_text:
return jsonify({'error': 'No se pudo extraer texto. Documento ilegible, escaneado de baja calidad o sin texto.'}), 400
structured_summary = generate_summary_openai(raw_text)
# Generamos la lista de puntos clave para el panel izquierdo
summary_list = [f"**{k.replace('_', ' ').title()}:** {v}" for k, v in structured_summary.items()]
# Tambi茅n extraemos el an谩lisis narrativo para el frontend
analisis_narrativo = structured_summary.get('analisis_extenso_narrativo', 'An谩lisis narrativo no disponible.')
return jsonify({
'structured_data': structured_summary,
'summary': summary_list,
'narrative': analisis_narrativo # Enviamos el an谩lisis narrativo separado
})
except Exception as e:
# --- BLOQUE DE DIAGN脫STICO CR脥TICO ---
print("\n" + "="*50)
print("DIAGN脫STICO: ERROR 500 DURANTE EL PROCESAMIENTO")
print(f"Tipo de Error: {type(e).__name__}")
traceback.print_exc()
print("="*50 + "\n")
# ----------------------------------------
return jsonify({'error': f"Error interno del servidor. Detalle: {type(e).__name__} - {str(e)}"}), 500
if __name__ == '__main__':
app.run(debug=True) |