jcalbornoz commited on
Commit
4e0b97c
·
verified ·
1 Parent(s): 8e21655

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -27
app.py CHANGED
@@ -8,6 +8,7 @@ from fpdf import FPDF
8
  import gradio as gr
9
  from playwright.sync_api import sync_playwright
10
  import time
 
11
  import requests
12
  from PIL import Image
13
  import io
@@ -115,7 +116,6 @@ def generar_mapa(barrio, ciudad):
115
  folium.Marker([lat, lon], popup=f"Zona de Estudio: {barrio.title()}", icon=folium.Icon(color="red", icon="info-sign")).add_to(m)
116
  return m._repr_html_(), lat, lon
117
  except: pass
118
- # Coordenadas por defecto (Centro Barranquilla)
119
  return "<p>Mapa no disponible</p>", 10.9639, -74.7964
120
 
121
  # --- CLASE PDF ---
@@ -143,12 +143,12 @@ class PDF_SAE(FPDF):
143
  # --- MOTOR PRINCIPAL ---
144
  def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo, hab, ban, park, antiguedad, ascensor, piscina):
145
  resultados = []; log_visible = ""; urls_vistas = set()
146
- precios_inversos = [] # Para el Cap Rate
147
 
148
  try:
149
  url_fr, url_mc = construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
150
 
151
- # URL INVERSA (Para calcular rentabilidad financiera)
152
  op_inversa = "Venta" if operacion == "Arriendo" else "Arriendo"
153
  url_inversa, _ = construir_urls_final(op_inversa, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
154
 
@@ -159,7 +159,7 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
159
  browser = p.chromium.launch(headless=True, args=['--disable-blink-features=AutomationControlled', '--no-sandbox'])
160
  context = browser.new_context(viewport={'width': 1366, 'height': 768}, user_agent=ua.random)
161
 
162
- # 1. EXTRACCIÓN PRINCIPAL (FINCA RAÍZ)
163
  try:
164
  page = context.new_page(); page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
165
  for _ in range(3): page.mouse.wheel(0, 1000); page.wait_for_timeout(2000)
@@ -185,7 +185,7 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
185
  page.close(); log_visible += f"✅ FR: {cont_fr} inmuebles.\n"
186
  except Exception as e: log_visible += f"⚠️ Error FR.\n"
187
 
188
- # 2. EXTRACCIÓN PRINCIPAL (METROCUADRADO)
189
  try:
190
  page = context.new_page(); page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
191
  for _ in range(3): page.mouse.wheel(0, 1000); page.wait_for_timeout(2000)
@@ -211,17 +211,37 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
211
  page.close(); log_visible += f"✅ MC: {cont_mc} inmuebles.\n"
212
  except Exception as e: log_visible += f"⚠️ Error MC.\n"
213
 
214
- # 3. EXTRACCIÓN INVERSA SILENCIOSA (MÓDULO FINANCIERO)
215
- log_visible += f"\n🔄 Ejecutando Módulo Financiero (Buscando {op_inversa})...\n"
216
  try:
217
- page = context.new_page(); page.goto(url_inversa, wait_until="domcontentloaded", timeout=30000)
218
- page.mouse.wheel(0, 1000); page.wait_for_timeout(1000)
 
 
 
 
 
219
  elementos = page.query_selector_all("a")
220
- for el in elementos[:15]:
221
- txt = el.inner_text(); precio = extraer_precio(txt, op_inversa)
222
- if precio > 0: precios_inversos.append(precio)
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  page.close()
224
- except: pass
 
 
225
 
226
  browser.close()
227
 
@@ -234,7 +254,6 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
234
  df_final = pd.concat([df_fr, df_mc]).reset_index(drop=True)
235
 
236
  # --- MÓDULO 1: HOMOGENEIZACIÓN MATEMÁTICA ---
237
- # Aplicamos factor de comercialización a todos los testigos
238
  df_final['V_Homogeneizado'] = df_final['Precio'] * 0.92
239
 
240
  mediana_m2 = df_final['Precio_M2'].median()
@@ -248,22 +267,26 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
248
  valor_minimo_neg = (mediana_m2 * area) * 0.90
249
 
250
  # --- MÓDULO 2: INTELIGENCIA FINANCIERA (CAP RATE) ---
251
- cap_rate_txt = "Datos insuficientes para calcular rentabilidad."
252
- if precios_inversos:
253
  mediana_inversa = pd.Series(precios_inversos).median()
254
- if operacion == "Arriendo":
255
- # Rentabilidad = (Arriendo Anual / Valor de Venta)
256
- rentabilidad = ((valor_total_sugerido * 12) / mediana_inversa) * 100
257
- cap_rate_txt = f"Valor Comercial Estimado (Venta): ${mediana_inversa:,.0f} COP\nRentabilidad Bruta Anual (Cap Rate): {rentabilidad:.2f}%"
258
- else:
259
- # Rentabilidad = (Arriendo Anual / Valor de Venta)
260
- rentabilidad = ((mediana_inversa * 12) / valor_total_sugerido) * 100
261
- cap_rate_txt = f"Canon de Arriendo Estimado: ${mediana_inversa:,.0f} COP\nRentabilidad Bruta Anual (Cap Rate): {rentabilidad:.2f}%"
 
 
 
 
262
 
263
  # --- MÓDULO 3: MAPA GEOESPACIAL ---
264
  mapa_html, lat_mapa, lon_mapa = generar_mapa(barrio, ciudad)
265
 
266
- # --- GENERACIÓN DEL PDF (NIVEL PERITO MÁSTER) ---
267
  preparar_entorno_pdf()
268
  pdf_path = f"Dictamen_Pericial_SAE_{int(time.time())}.pdf"
269
  pdf = PDF_SAE()
@@ -285,6 +308,7 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
285
  pdf.cell(0, 10, sanear_texto("1. DATOS GEOESPACIALES Y DEL ACTIVO"), ln=True)
286
  pdf.set_font("Arial", '', 11); pdf.set_text_color(*COLOR_NEGRO)
287
  pdf.cell(0, 6, sanear_texto(f"- Operacion: {operacion.capitalize()}"), ln=True)
 
288
  pdf.cell(0, 6, sanear_texto(f"- Ubicacion: Barrio {barrio.title()}, {ciudad.title()}"), ln=True)
289
  pdf.cell(0, 6, sanear_texto(f"- Coordenadas de Influencia: Lat {lat_mapa:.4f}, Lon {lon_mapa:.4f}"), ln=True)
290
  pdf.cell(0, 6, sanear_texto(f"- Area Construida: {area} m2"), ln=True)
@@ -333,7 +357,6 @@ def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo,
333
  try: pdf.image(img_path, x=10, y=y_start, w=45, h=30); text_x = 60
334
  except: pass
335
 
336
- # Mostrar precio Oferta y Homogeneizado
337
  pdf.set_xy(text_x, y_start); pdf.set_font("Arial", 'B', 11)
338
  pdf.cell(0, 6, f"Oferta: ${r['Precio']:,.0f} | Homogeneizado: ${r['V_Homogeneizado']:,.0f}", ln=True)
339
 
@@ -398,7 +421,7 @@ with gr.Blocks() as demo:
398
  res_fin = gr.Markdown("### 💰 Resultado y Rentabilidad (Cap Rate)...")
399
  with gr.Tabs():
400
  with gr.TabItem("Mapa Geoespacial"):
401
- mapa_ui = gr.HTML("<p style='text-align:center; color:gray;'>El mapa de calor aparecerá aquí...</p>")
402
  with gr.TabItem("Descargar Dictamen (PDF)"): out_pdf = gr.File()
403
  with gr.TabItem("Tabla de Homogeneización"): out_df = gr.Dataframe()
404
  with gr.TabItem("Log de Sistema"): msg = gr.Textbox(lines=10)
 
8
  import gradio as gr
9
  from playwright.sync_api import sync_playwright
10
  import time
11
+ import random
12
  import requests
13
  from PIL import Image
14
  import io
 
116
  folium.Marker([lat, lon], popup=f"Zona de Estudio: {barrio.title()}", icon=folium.Icon(color="red", icon="info-sign")).add_to(m)
117
  return m._repr_html_(), lat, lon
118
  except: pass
 
119
  return "<p>Mapa no disponible</p>", 10.9639, -74.7964
120
 
121
  # --- CLASE PDF ---
 
143
  # --- MOTOR PRINCIPAL ---
144
  def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo, hab, ban, park, antiguedad, ascensor, piscina):
145
  resultados = []; log_visible = ""; urls_vistas = set()
146
+ precios_inversos = []
147
 
148
  try:
149
  url_fr, url_mc = construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
150
 
151
+ # URL INVERSA
152
  op_inversa = "Venta" if operacion == "Arriendo" else "Arriendo"
153
  url_inversa, _ = construir_urls_final(op_inversa, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
154
 
 
159
  browser = p.chromium.launch(headless=True, args=['--disable-blink-features=AutomationControlled', '--no-sandbox'])
160
  context = browser.new_context(viewport={'width': 1366, 'height': 768}, user_agent=ua.random)
161
 
162
+ # 1. EXTRACCIÓN PRINCIPAL (FR)
163
  try:
164
  page = context.new_page(); page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
165
  for _ in range(3): page.mouse.wheel(0, 1000); page.wait_for_timeout(2000)
 
185
  page.close(); log_visible += f"✅ FR: {cont_fr} inmuebles.\n"
186
  except Exception as e: log_visible += f"⚠️ Error FR.\n"
187
 
188
+ # 2. EXTRACCIÓN PRINCIPAL (MC)
189
  try:
190
  page = context.new_page(); page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
191
  for _ in range(3): page.mouse.wheel(0, 1000); page.wait_for_timeout(2000)
 
211
  page.close(); log_visible += f"✅ MC: {cont_mc} inmuebles.\n"
212
  except Exception as e: log_visible += f"⚠️ Error MC.\n"
213
 
214
+ # 3. EXTRACCIÓN INVERSA SILENCIOSA (MÓDULO FINANCIERO MEJORADO)
215
+ log_visible += f"\n🔄 Ejecutando Módulo Financiero (Buscando {op_inversa} para Cap Rate)...\n"
216
  try:
217
+ page = context.new_page(); page.goto(url_inversa, wait_until="domcontentloaded", timeout=45000)
218
+ try: page.wait_for_load_state("networkidle", timeout=5000)
219
+ except: pass
220
+
221
+ # Hacemos scroll para asegurar que carguen los elementos
222
+ for _ in range(3): page.mouse.wheel(0, 1000); page.wait_for_timeout(1500)
223
+
224
  elementos = page.query_selector_all("a")
225
+ for el in elementos:
226
+ if len(precios_inversos) >= 8: break # Con atrapar 8 datos es suficiente para una mediana robusta
227
+ try:
228
+ href = el.get_attribute("href")
229
+ if not es_inmueble_valido(href, "FR"): continue
230
+
231
+ # Usamos el mismo radar preciso de la búsqueda principal
232
+ card = el.evaluate_handle("el => el.closest('article') || el.closest('[class*=\"card\"]') || el.parentElement.parentElement")
233
+ if not card: continue
234
+
235
+ txt = card.inner_text()
236
+ precio = extraer_precio(txt, op_inversa)
237
+
238
+ if precio > 0:
239
+ precios_inversos.append(precio)
240
+ except: continue
241
  page.close()
242
+ log_visible += f"✅ Módulo Financiero: Capturados {len(precios_inversos)} valores de {op_inversa} en la zona.\n"
243
+ except Exception as ex:
244
+ log_visible += f"⚠️ Módulo Financiero falló: {ex}\n"
245
 
246
  browser.close()
247
 
 
254
  df_final = pd.concat([df_fr, df_mc]).reset_index(drop=True)
255
 
256
  # --- MÓDULO 1: HOMOGENEIZACIÓN MATEMÁTICA ---
 
257
  df_final['V_Homogeneizado'] = df_final['Precio'] * 0.92
258
 
259
  mediana_m2 = df_final['Precio_M2'].median()
 
267
  valor_minimo_neg = (mediana_m2 * area) * 0.90
268
 
269
  # --- MÓDULO 2: INTELIGENCIA FINANCIERA (CAP RATE) ---
270
+ cap_rate_txt = "Datos insuficientes en la zona para cruzar la rentabilidad."
271
+ if len(precios_inversos) > 0:
272
  mediana_inversa = pd.Series(precios_inversos).median()
273
+
274
+ # Para evitar rentabilidades rotas si el portal cruza mal los datos (ej: Venta de lote en vez de apto)
275
+ try:
276
+ if operacion == "Arriendo":
277
+ # Tasa Cap = (Arriendo Anual Sugerido / Valor de Venta Promedio de la zona)
278
+ rentabilidad = ((valor_total_sugerido * 12) / mediana_inversa) * 100
279
+ cap_rate_txt = f"Valor Comercial de Venta Promedio en la zona: ${mediana_inversa:,.0f} COP\nRentabilidad Bruta Anual Estimada (Cap Rate): {rentabilidad:.2f}%"
280
+ else: # Operación = Venta
281
+ # Tasa Cap = (Arriendo Anual Promedio de la zona / Valor de Venta Sugerido)
282
+ rentabilidad = ((mediana_inversa * 12) / valor_total_sugerido) * 100
283
+ cap_rate_txt = f"Canon de Arriendo Mensual Promedio en la zona: ${mediana_inversa:,.0f} COP\nRentabilidad Bruta Anual Estimada (Cap Rate): {rentabilidad:.2f}%"
284
+ except: pass
285
 
286
  # --- MÓDULO 3: MAPA GEOESPACIAL ---
287
  mapa_html, lat_mapa, lon_mapa = generar_mapa(barrio, ciudad)
288
 
289
+ # --- GENERACIÓN DEL PDF ---
290
  preparar_entorno_pdf()
291
  pdf_path = f"Dictamen_Pericial_SAE_{int(time.time())}.pdf"
292
  pdf = PDF_SAE()
 
308
  pdf.cell(0, 10, sanear_texto("1. DATOS GEOESPACIALES Y DEL ACTIVO"), ln=True)
309
  pdf.set_font("Arial", '', 11); pdf.set_text_color(*COLOR_NEGRO)
310
  pdf.cell(0, 6, sanear_texto(f"- Operacion: {operacion.capitalize()}"), ln=True)
311
+ pdf.cell(0, 6, sanear_texto(f"- Tipo de Inmueble: {tipo.capitalize()}"), ln=True)
312
  pdf.cell(0, 6, sanear_texto(f"- Ubicacion: Barrio {barrio.title()}, {ciudad.title()}"), ln=True)
313
  pdf.cell(0, 6, sanear_texto(f"- Coordenadas de Influencia: Lat {lat_mapa:.4f}, Lon {lon_mapa:.4f}"), ln=True)
314
  pdf.cell(0, 6, sanear_texto(f"- Area Construida: {area} m2"), ln=True)
 
357
  try: pdf.image(img_path, x=10, y=y_start, w=45, h=30); text_x = 60
358
  except: pass
359
 
 
360
  pdf.set_xy(text_x, y_start); pdf.set_font("Arial", 'B', 11)
361
  pdf.cell(0, 6, f"Oferta: ${r['Precio']:,.0f} | Homogeneizado: ${r['V_Homogeneizado']:,.0f}", ln=True)
362
 
 
421
  res_fin = gr.Markdown("### 💰 Resultado y Rentabilidad (Cap Rate)...")
422
  with gr.Tabs():
423
  with gr.TabItem("Mapa Geoespacial"):
424
+ mapa_ui = gr.HTML("<p style='text-align:center; color:gray;'>El mapa aparecerá aquí...</p>")
425
  with gr.TabItem("Descargar Dictamen (PDF)"): out_pdf = gr.File()
426
  with gr.TabItem("Tabla de Homogeneización"): out_df = gr.Dataframe()
427
  with gr.TabItem("Log de Sistema"): msg = gr.Textbox(lines=10)