Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -27,7 +27,7 @@ except ImportError:
|
|
| 27 |
subprocess.run(["pip", "install", "fake-useragent"], check=True)
|
| 28 |
from fake_useragent import UserAgent
|
| 29 |
|
| 30 |
-
# --- URL DEL GOBIERNO
|
| 31 |
URL_LOGO_GOBIERNO = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Escudo_de_Colombia.svg/250px-Escudo_de_Colombia.svg.png"
|
| 32 |
|
| 33 |
def descargar_recurso(url, nombre_archivo):
|
|
@@ -41,7 +41,6 @@ def descargar_recurso(url, nombre_archivo):
|
|
| 41 |
return None
|
| 42 |
|
| 43 |
def preparar_entorno_pdf():
|
| 44 |
-
# Descarga el escudo de Colombia si no existe
|
| 45 |
if not os.path.exists("logo_gob.png"):
|
| 46 |
descargar_recurso(URL_LOGO_GOBIERNO, "logo_gob.png")
|
| 47 |
|
|
@@ -111,21 +110,16 @@ def es_inmueble_valido(href, portal):
|
|
| 111 |
# --- CLASE PDF MANUAL SAE ACTUALIZADA ---
|
| 112 |
class PDF_SAE(FPDF):
|
| 113 |
def header(self):
|
| 114 |
-
# Escudo de Colombia a la izquierda
|
| 115 |
if os.path.exists("logo_gob.png"):
|
| 116 |
try: self.image("logo_gob.png", x=15, y=8, h=16)
|
| 117 |
except: pass
|
| 118 |
-
|
| 119 |
-
# LOGO SAE (El que tú subiste) a la derecha
|
| 120 |
-
# Busca primero en PNG, luego en JPG por si acaso
|
| 121 |
if os.path.exists("logo_sae.png"):
|
| 122 |
try: self.image("logo_sae.png", x=155, y=8, w=40)
|
| 123 |
except: pass
|
| 124 |
elif os.path.exists("logo_sae.jpg"):
|
| 125 |
try: self.image("logo_sae.jpg", x=155, y=8, w=40)
|
| 126 |
except: pass
|
| 127 |
-
|
| 128 |
-
self.ln(25) # Espacio base debajo de la cabecera
|
| 129 |
|
| 130 |
def footer(self):
|
| 131 |
self.set_y(-25)
|
|
@@ -220,15 +214,22 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
|
|
| 220 |
|
| 221 |
if df_final.empty: return f"{log_visible}\n❌ DATOS VACÍOS.", pd.DataFrame(), None, "---"
|
| 222 |
|
| 223 |
-
# CÁLCULOS TÉCNICOS
|
| 224 |
-
margen_negociacion = 0.08
|
| 225 |
mediana_m2 = df_final['Precio_M2'].median()
|
| 226 |
promedio_m2 = df_final['Precio_M2'].mean()
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
# --- GENERACIÓN DEL PDF ---
|
| 231 |
-
preparar_entorno_pdf()
|
| 232 |
|
| 233 |
pdf_path = f"Estudio_Mercado_SAE_{int(time.time())}.pdf"
|
| 234 |
pdf = PDF_SAE()
|
|
@@ -236,6 +237,8 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
|
|
| 236 |
COLOR_ROSADO = (254, 25, 120)
|
| 237 |
COLOR_GRIS = (137, 137, 137)
|
| 238 |
COLOR_NEGRO = (0, 0, 0)
|
|
|
|
|
|
|
| 239 |
|
| 240 |
# PÁGINA 1
|
| 241 |
pdf.add_page()
|
|
@@ -257,33 +260,48 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
|
|
| 257 |
pdf.ln(10)
|
| 258 |
|
| 259 |
pdf.set_font("Arial", 'B', 12); pdf.set_text_color(*COLOR_ROSADO)
|
| 260 |
-
pdf.cell(0, 10, sanear_texto("2. METODOLOGIA"), ln=True)
|
| 261 |
pdf.set_font("Arial", '', 11); pdf.set_text_color(*COLOR_NEGRO)
|
| 262 |
-
pdf.multi_cell(0, 5, sanear_texto("Estudio deducido mediante la comparacion sistematica de ofertas recientes de inmuebles similares en la zona.
|
| 263 |
|
| 264 |
-
# PÁGINA 2
|
| 265 |
pdf.add_page()
|
| 266 |
pdf.set_font("Arial", 'B', 12); pdf.set_fill_color(*COLOR_ROSADO); pdf.set_text_color(255, 255, 255)
|
| 267 |
-
pdf.cell(0, 10, sanear_texto(" 3. RESULTADOS ESTADISTICOS"), ln=True, fill=True)
|
| 268 |
pdf.set_text_color(*COLOR_NEGRO); pdf.ln(5); pdf.set_font("Arial", '', 11)
|
| 269 |
|
| 270 |
-
|
|
|
|
| 271 |
pdf.cell(50, 8, sanear_texto(f"{len(df_final)}"), border=1, ln=True, align='C')
|
| 272 |
-
|
| 273 |
-
pdf.cell(
|
| 274 |
-
pdf.cell(
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
pdf.ln(10)
|
| 277 |
|
| 278 |
-
|
| 279 |
-
pdf.
|
| 280 |
-
pdf.
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
pdf.set_text_color(*COLOR_GRIS); pdf.ln(15); pdf.set_font("Arial", '', 8)
|
| 284 |
-
pdf.multi_cell(0, 4, sanear_texto("
|
| 285 |
|
| 286 |
-
# PÁGINA 3+
|
| 287 |
pdf.add_page()
|
| 288 |
pdf.set_font("Arial", 'B', 12); pdf.set_fill_color(*COLOR_GRIS); pdf.set_text_color(255, 255, 255)
|
| 289 |
pdf.cell(0, 10, sanear_texto(" 4. ANEXO TECNICO: TESTIGOS COMPARABLES"), ln=True, fill=True)
|
|
@@ -313,17 +331,18 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
|
|
| 313 |
|
| 314 |
# --- CÁLCULOS INTERFAZ ---
|
| 315 |
resumen = (
|
| 316 |
-
f"🏢 **
|
| 317 |
-
f"
|
| 318 |
-
f"
|
| 319 |
-
f"
|
|
|
|
| 320 |
)
|
| 321 |
|
| 322 |
df_mostrar = df_final[['Portal', 'Precio', 'Precio_M2', 'Ubicacion', 'Descripcion', 'URL']].copy()
|
| 323 |
df_mostrar['Precio'] = df_mostrar['Precio'].apply(lambda x: f"${x:,.0f}")
|
| 324 |
df_mostrar['Precio_M2'] = df_mostrar['Precio_M2'].apply(lambda x: f"${x:,.0f}")
|
| 325 |
|
| 326 |
-
return f"{log_visible}\n✅ Reporte
|
| 327 |
|
| 328 |
except Exception as error_fatal:
|
| 329 |
traza = traceback.format_exc()
|
|
@@ -331,7 +350,7 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
|
|
| 331 |
|
| 332 |
# --- INTERFAZ GRÁFICA ---
|
| 333 |
with gr.Blocks() as demo:
|
| 334 |
-
gr.Markdown("## 🏢 TramitIA Pro:
|
| 335 |
|
| 336 |
with gr.Row():
|
| 337 |
with gr.Column(scale=1):
|
|
@@ -353,7 +372,7 @@ with gr.Blocks() as demo:
|
|
| 353 |
btn = gr.Button("GENERAR REPORTE NORMATIVO", variant="primary")
|
| 354 |
|
| 355 |
with gr.Column(scale=2):
|
| 356 |
-
res_fin = gr.Markdown("### 💰
|
| 357 |
with gr.Tabs():
|
| 358 |
with gr.TabItem("Descargar Estudio Técnico (PDF)"): out_pdf = gr.File()
|
| 359 |
with gr.TabItem("Matriz de Datos"): out_df = gr.Dataframe()
|
|
|
|
| 27 |
subprocess.run(["pip", "install", "fake-useragent"], check=True)
|
| 28 |
from fake_useragent import UserAgent
|
| 29 |
|
| 30 |
+
# --- URL DEL GOBIERNO ---
|
| 31 |
URL_LOGO_GOBIERNO = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Escudo_de_Colombia.svg/250px-Escudo_de_Colombia.svg.png"
|
| 32 |
|
| 33 |
def descargar_recurso(url, nombre_archivo):
|
|
|
|
| 41 |
return None
|
| 42 |
|
| 43 |
def preparar_entorno_pdf():
|
|
|
|
| 44 |
if not os.path.exists("logo_gob.png"):
|
| 45 |
descargar_recurso(URL_LOGO_GOBIERNO, "logo_gob.png")
|
| 46 |
|
|
|
|
| 110 |
# --- CLASE PDF MANUAL SAE ACTUALIZADA ---
|
| 111 |
class PDF_SAE(FPDF):
|
| 112 |
def header(self):
|
|
|
|
| 113 |
if os.path.exists("logo_gob.png"):
|
| 114 |
try: self.image("logo_gob.png", x=15, y=8, h=16)
|
| 115 |
except: pass
|
|
|
|
|
|
|
|
|
|
| 116 |
if os.path.exists("logo_sae.png"):
|
| 117 |
try: self.image("logo_sae.png", x=155, y=8, w=40)
|
| 118 |
except: pass
|
| 119 |
elif os.path.exists("logo_sae.jpg"):
|
| 120 |
try: self.image("logo_sae.jpg", x=155, y=8, w=40)
|
| 121 |
except: pass
|
| 122 |
+
self.ln(25)
|
|
|
|
| 123 |
|
| 124 |
def footer(self):
|
| 125 |
self.set_y(-25)
|
|
|
|
| 214 |
|
| 215 |
if df_final.empty: return f"{log_visible}\n❌ DATOS VACÍOS.", pd.DataFrame(), None, "---"
|
| 216 |
|
| 217 |
+
# --- CÁLCULOS TÉCNICOS Y RANGOS DE NEGOCIACIÓN ---
|
|
|
|
| 218 |
mediana_m2 = df_final['Precio_M2'].median()
|
| 219 |
promedio_m2 = df_final['Precio_M2'].mean()
|
| 220 |
+
|
| 221 |
+
minimo_zona = df_final['Precio'].min()
|
| 222 |
+
maximo_zona = df_final['Precio'].max()
|
| 223 |
+
|
| 224 |
+
mediana_total = mediana_m2 * area
|
| 225 |
+
|
| 226 |
+
# Bandas de negociación (Aplicadas sobre la Mediana del mercado)
|
| 227 |
+
valor_maximo_neg = mediana_total * 0.95 # Techo (Castigo 5%)
|
| 228 |
+
valor_optimo_neg = mediana_total * 0.92 # Centro (Castigo 8%)
|
| 229 |
+
valor_minimo_neg = mediana_total * 0.90 # Piso (Castigo 10%)
|
| 230 |
|
| 231 |
# --- GENERACIÓN DEL PDF ---
|
| 232 |
+
preparar_entorno_pdf()
|
| 233 |
|
| 234 |
pdf_path = f"Estudio_Mercado_SAE_{int(time.time())}.pdf"
|
| 235 |
pdf = PDF_SAE()
|
|
|
|
| 237 |
COLOR_ROSADO = (254, 25, 120)
|
| 238 |
COLOR_GRIS = (137, 137, 137)
|
| 239 |
COLOR_NEGRO = (0, 0, 0)
|
| 240 |
+
COLOR_VERDE = (0, 128, 0)
|
| 241 |
+
COLOR_ROJO = (200, 0, 0)
|
| 242 |
|
| 243 |
# PÁGINA 1
|
| 244 |
pdf.add_page()
|
|
|
|
| 260 |
pdf.ln(10)
|
| 261 |
|
| 262 |
pdf.set_font("Arial", 'B', 12); pdf.set_text_color(*COLOR_ROSADO)
|
| 263 |
+
pdf.cell(0, 10, sanear_texto("2. METODOLOGIA Y COMERCIALIZACION"), ln=True)
|
| 264 |
pdf.set_font("Arial", '', 11); pdf.set_text_color(*COLOR_NEGRO)
|
| 265 |
+
pdf.multi_cell(0, 5, sanear_texto("Estudio deducido mediante la comparacion sistematica de ofertas recientes de inmuebles similares en la zona. Para aislar los precios especulativos de oferta en internet y llegar al valor real de transaccion, se aplican bandas de negociacion sobre la mediana del mercado (Techo del -5%, Optimo del -8% y Piso del -10%)."))
|
| 266 |
|
| 267 |
+
# PÁGINA 2: CONCLUSIONES Y BANDAS DE NEGOCIACIÓN
|
| 268 |
pdf.add_page()
|
| 269 |
pdf.set_font("Arial", 'B', 12); pdf.set_fill_color(*COLOR_ROSADO); pdf.set_text_color(255, 255, 255)
|
| 270 |
+
pdf.cell(0, 10, sanear_texto(" 3. RESULTADOS ESTADISTICOS Y RANGOS"), ln=True, fill=True)
|
| 271 |
pdf.set_text_color(*COLOR_NEGRO); pdf.ln(5); pdf.set_font("Arial", '', 11)
|
| 272 |
|
| 273 |
+
ancho_col = 80
|
| 274 |
+
pdf.cell(ancho_col, 8, sanear_texto("Total Testigos Analizados:"), border=1)
|
| 275 |
pdf.cell(50, 8, sanear_texto(f"{len(df_final)}"), border=1, ln=True, align='C')
|
| 276 |
+
|
| 277 |
+
pdf.cell(ancho_col, 8, sanear_texto("Oferta mas economica de la zona:"), border=1)
|
| 278 |
+
pdf.cell(50, 8, sanear_texto(f"${minimo_zona:,.0f}"), border=1, ln=True, align='C')
|
| 279 |
+
|
| 280 |
+
pdf.cell(ancho_col, 8, sanear_texto("Oferta mas costosa de la zona:"), border=1)
|
| 281 |
+
pdf.cell(50, 8, sanear_texto(f"${maximo_zona:,.0f}"), border=1, ln=True, align='C')
|
| 282 |
+
|
| 283 |
+
pdf.set_font("Arial", 'B', 11)
|
| 284 |
+
pdf.cell(ancho_col, 8, sanear_texto("Mediana Bruta (Sin Descuentos):"), border=1)
|
| 285 |
+
pdf.cell(50, 8, sanear_texto(f"${mediana_total:,.0f}"), border=1, ln=True, align='C')
|
| 286 |
pdf.ln(10)
|
| 287 |
|
| 288 |
+
# BLOQUE DE RANGOS DE NEGOCIACIÓN
|
| 289 |
+
pdf.set_font("Arial", 'B', 14); pdf.set_text_color(*COLOR_ROSADO)
|
| 290 |
+
pdf.cell(0, 10, sanear_texto("RANGOS DE NEGOCIACION AUTORIZADOS (CIERRE):"), ln=True)
|
| 291 |
+
|
| 292 |
+
pdf.set_font("Arial", 'B', 12); pdf.set_text_color(*COLOR_GRIS)
|
| 293 |
+
pdf.cell(0, 8, sanear_texto(f"TECHO MAXIMO ESPERADO (Castigo 5%): ${valor_maximo_neg:,.0f} COP"), ln=True)
|
| 294 |
+
|
| 295 |
+
pdf.set_font("Arial", 'B', 16); pdf.set_text_color(*COLOR_VERDE)
|
| 296 |
+
pdf.cell(0, 12, sanear_texto(f"VALOR OPTIMO SUGERIDO (Castigo 8%): ${valor_optimo_neg:,.0f} COP"), ln=True)
|
| 297 |
+
|
| 298 |
+
pdf.set_font("Arial", 'B', 12); pdf.set_text_color(*COLOR_ROJO)
|
| 299 |
+
pdf.cell(0, 8, sanear_texto(f"PISO MINIMO ACEPTABLE (Castigo 10%): ${valor_minimo_neg:,.0f} COP"), ln=True)
|
| 300 |
|
| 301 |
pdf.set_text_color(*COLOR_GRIS); pdf.ln(15); pdf.set_font("Arial", '', 8)
|
| 302 |
+
pdf.multi_cell(0, 4, sanear_texto("Directriz Comercial: Ninguna propuesta por debajo del piso minimo aceptable debe ser tramitada sin revision especial del comite de avalúos."))
|
| 303 |
|
| 304 |
+
# PÁGINA 3+: ANEXOS
|
| 305 |
pdf.add_page()
|
| 306 |
pdf.set_font("Arial", 'B', 12); pdf.set_fill_color(*COLOR_GRIS); pdf.set_text_color(255, 255, 255)
|
| 307 |
pdf.cell(0, 10, sanear_texto(" 4. ANEXO TECNICO: TESTIGOS COMPARABLES"), ln=True, fill=True)
|
|
|
|
| 331 |
|
| 332 |
# --- CÁLCULOS INTERFAZ ---
|
| 333 |
resumen = (
|
| 334 |
+
f"🏢 **DIRECTRIZ DE NEGOCIACIÓN SAE**\n"
|
| 335 |
+
f"📈 **Techo Máximo (-5%):** ${valor_maximo_neg:,.0f}\n"
|
| 336 |
+
f"✅ **VALOR ÓPTIMO SUGERIDO (-8%):** ${valor_optimo_neg:,.0f}\n"
|
| 337 |
+
f"🛑 **Piso Mínimo Aceptable (-10%):** ${valor_minimo_neg:,.0f}\n\n"
|
| 338 |
+
f"📊 *Ofertas del Mercado: Min ${minimo_zona:,.0f} | Max ${maximo_zona:,.0f}*"
|
| 339 |
)
|
| 340 |
|
| 341 |
df_mostrar = df_final[['Portal', 'Precio', 'Precio_M2', 'Ubicacion', 'Descripcion', 'URL']].copy()
|
| 342 |
df_mostrar['Precio'] = df_mostrar['Precio'].apply(lambda x: f"${x:,.0f}")
|
| 343 |
df_mostrar['Precio_M2'] = df_mostrar['Precio_M2'].apply(lambda x: f"${x:,.0f}")
|
| 344 |
|
| 345 |
+
return f"{log_visible}\n✅ Reporte con Bandas Generado.", df_mostrar, pdf_path, resumen
|
| 346 |
|
| 347 |
except Exception as error_fatal:
|
| 348 |
traza = traceback.format_exc()
|
|
|
|
| 350 |
|
| 351 |
# --- INTERFAZ GRÁFICA ---
|
| 352 |
with gr.Blocks() as demo:
|
| 353 |
+
gr.Markdown("## 🏢 TramitIA Pro: Analizador Inmobiliario (Bandas SAE)")
|
| 354 |
|
| 355 |
with gr.Row():
|
| 356 |
with gr.Column(scale=1):
|
|
|
|
| 372 |
btn = gr.Button("GENERAR REPORTE NORMATIVO", variant="primary")
|
| 373 |
|
| 374 |
with gr.Column(scale=2):
|
| 375 |
+
res_fin = gr.Markdown("### 💰 Directriz de Negociación...")
|
| 376 |
with gr.Tabs():
|
| 377 |
with gr.TabItem("Descargar Estudio Técnico (PDF)"): out_pdf = gr.File()
|
| 378 |
with gr.TabItem("Matriz de Datos"): out_df = gr.Dataframe()
|