Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,8 +11,8 @@ import time
|
|
| 11 |
# --- CONFIGURACIÓN DE ENTORNO ---
|
| 12 |
try:
|
| 13 |
subprocess.run(["playwright", "install", "chromium"], check=True)
|
| 14 |
-
except Exception
|
| 15 |
-
|
| 16 |
|
| 17 |
from playwright.sync_api import sync_playwright
|
| 18 |
|
|
@@ -21,128 +21,127 @@ USER_AGENTS = [
|
|
| 21 |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
| 22 |
]
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
def extraer_datos(page, area_m2):
|
| 25 |
referencias = []
|
| 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 |
-
referencias.append({
|
| 51 |
-
"Portal": "Finca Raíz",
|
| 52 |
-
"Descripción": descripcion[:100] + "...",
|
| 53 |
-
"Precio": precio,
|
| 54 |
-
"Area": area_ref,
|
| 55 |
-
"Precio_M2": precio / area_ref,
|
| 56 |
-
"URL": url
|
| 57 |
-
})
|
| 58 |
-
except: continue
|
| 59 |
return referencias
|
| 60 |
|
| 61 |
-
def
|
| 62 |
with sync_playwright() as p:
|
| 63 |
browser = p.chromium.launch(headless=True)
|
| 64 |
context = browser.new_context(user_agent=random.choice(USER_AGENTS))
|
| 65 |
page = context.new_page()
|
| 66 |
-
|
| 67 |
zona_url = zona.lower().replace(" ", "-")
|
| 68 |
-
# Diccionario de URLs según modo
|
| 69 |
-
path_modo = "arriendo" if modo == "Arriendo" else "venta"
|
| 70 |
-
url = f"https://www.fincaraiz.com.co/{tipo_inmueble.lower()}/{path_modo}/{zona_url}"
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def comparativa_tramitia(zona, area_m2, tipo):
|
| 83 |
-
# Ejecutar ambas búsquedas
|
| 84 |
-
res_arriendo = analizar_mercado(zona, area_m2, tipo, "Arriendo")
|
| 85 |
-
res_venta = analizar_mercado(zona, area_m2, tipo, "Venta")
|
| 86 |
-
|
| 87 |
-
if not res_arriendo and not res_venta:
|
| 88 |
-
return "❌ No se encontraron datos suficientes para comparar.", None, None
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
if not df_a.empty:
|
| 97 |
-
prom_a = df_a['Precio'].mean()
|
| 98 |
-
html_resumen += f"<p style='font-size:1.1em;'>🏠 <b>Promedio Arriendo:</b> ${prom_a:,.0f} COP</p>"
|
| 99 |
-
|
| 100 |
-
if not df_v.empty:
|
| 101 |
-
prom_v = df_v['Precio'].mean()
|
| 102 |
-
html_resumen += f"<p style='font-size:1.1em;'>💰 <b>Promedio Venta:</b> ${prom_v:,.0f} COP</p>"
|
| 103 |
-
if not df_a.empty:
|
| 104 |
-
rentabilidad = ( (df_a['Precio'].mean() * 12) / prom_v ) * 100
|
| 105 |
-
html_resumen += f"<div style='margin-top:10px; padding:10px; background:#dcfce7; color:#166534; border-radius:5px;'>"
|
| 106 |
-
html_resumen += f"<b>Rentabilidad Estimada (PER):</b> {rentabilidad:,.2f}% anual</div>"
|
| 107 |
-
|
| 108 |
-
html_resumen += "</div>"
|
| 109 |
-
|
| 110 |
-
# Unir ambos dataframes para mostrar las referencias detalladas
|
| 111 |
-
df_a['Tipo'] = 'Arriendo'
|
| 112 |
-
df_v['Tipo'] = 'Venta'
|
| 113 |
df_final = pd.concat([df_a, df_v], ignore_index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
#
|
| 116 |
-
|
| 117 |
-
# (Lógica simplificada de PDF para el ejemplo)
|
| 118 |
|
| 119 |
-
return
|
| 120 |
|
| 121 |
-
# --- INTERFAZ
|
| 122 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 123 |
-
gr.HTML("<h1 style='text-align:center
|
| 124 |
-
gr.HTML("<p style='text-align:center;'>Análisis Comparativo de Mercado (Arriendo vs Venta)</p>")
|
| 125 |
-
|
| 126 |
with gr.Row():
|
| 127 |
with gr.Column(scale=1):
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
with gr.Column(scale=2):
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
btn_comp.click(
|
| 142 |
-
comparativa_tramitia,
|
| 143 |
-
inputs=[zona_in, area_in, tipo_in],
|
| 144 |
-
outputs=[res_html, data_view, file_out]
|
| 145 |
-
)
|
| 146 |
-
|
| 147 |
-
if __name__ == "__main__":
|
| 148 |
-
demo.launch()
|
|
|
|
| 11 |
# --- CONFIGURACIÓN DE ENTORNO ---
|
| 12 |
try:
|
| 13 |
subprocess.run(["playwright", "install", "chromium"], check=True)
|
| 14 |
+
except Exception:
|
| 15 |
+
pass
|
| 16 |
|
| 17 |
from playwright.sync_api import sync_playwright
|
| 18 |
|
|
|
|
| 21 |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
| 22 |
]
|
| 23 |
|
| 24 |
+
# --- GENERADOR DE PDF (CORREGIDO) ---
|
| 25 |
+
def generar_pdf_comparativo(zona, area, df):
|
| 26 |
+
pdf = FPDF()
|
| 27 |
+
pdf.add_page()
|
| 28 |
+
pdf.set_font("Arial", 'B', 16)
|
| 29 |
+
pdf.cell(0, 15, "TRAMITIA PRO - REPORTE COMPARATIVO", ln=True, align='C')
|
| 30 |
+
pdf.set_font("Arial", '', 10)
|
| 31 |
+
pdf.cell(0, 5, f"Fecha: {datetime.datetime.now().strftime('%d/%m/%Y')}", ln=True, align='C')
|
| 32 |
+
pdf.ln(10)
|
| 33 |
+
|
| 34 |
+
pdf.set_font("Arial", 'B', 12)
|
| 35 |
+
pdf.cell(0, 10, f"Analisis para: {zona.upper()} - Area: {area}m2", ln=True)
|
| 36 |
+
pdf.ln(5)
|
| 37 |
+
|
| 38 |
+
# Tabla de referencias
|
| 39 |
+
pdf.set_font("Arial", 'B', 9)
|
| 40 |
+
pdf.set_fill_color(200, 220, 255)
|
| 41 |
+
pdf.cell(20, 10, "Tipo", 1, 0, 'C', True)
|
| 42 |
+
pdf.cell(80, 10, "Descripcion", 1, 0, 'C', True)
|
| 43 |
+
pdf.cell(45, 10, "Precio", 1, 0, 'C', True)
|
| 44 |
+
pdf.cell(45, 10, "Valor m2", 1, 1, 'C', True)
|
| 45 |
+
|
| 46 |
+
pdf.set_font("Arial", '', 8)
|
| 47 |
+
for _, r in df.iterrows():
|
| 48 |
+
tipo = str(r['Tipo'])
|
| 49 |
+
desc = str(r['Descripción'])[:45]
|
| 50 |
+
precio = f"${r['Precio']:,.0f}"
|
| 51 |
+
val_m2 = f"${r['Precio_M2']:,.0f}"
|
| 52 |
+
|
| 53 |
+
pdf.cell(20, 10, tipo, 1)
|
| 54 |
+
pdf.cell(80, 10, desc, 1)
|
| 55 |
+
pdf.cell(45, 10, precio, 1)
|
| 56 |
+
pdf.cell(45, 10, val_m2, 1, 1)
|
| 57 |
+
|
| 58 |
+
path_pdf = "Comparativa_TramitIA.pdf"
|
| 59 |
+
pdf.output(path_pdf)
|
| 60 |
+
return path_pdf
|
| 61 |
+
|
| 62 |
+
# --- LÓGICA DE EXTRACCIÓN ---
|
| 63 |
def extraer_datos(page, area_m2):
|
| 64 |
referencias = []
|
| 65 |
+
try:
|
| 66 |
+
page.wait_for_selector("article, [class*='Card'], [class*='listing']", timeout=15000)
|
| 67 |
+
cards = page.query_selector_all("article, [class*='Card'], .listing-item")[:5]
|
| 68 |
+
for card in cards:
|
| 69 |
+
try:
|
| 70 |
+
link_elem = card.query_selector("a")
|
| 71 |
+
url = "https://www.fincaraiz.com.co" + link_elem.get_attribute("href") if link_elem else "N/A"
|
| 72 |
+
full_text = card.inner_text()
|
| 73 |
+
solo_nums = full_text.replace('.', '').replace('$', '')
|
| 74 |
+
nums = [int(s) for s in solo_nums.split() if s.isdigit() and len(s) >= 6]
|
| 75 |
+
if nums:
|
| 76 |
+
precio = max(nums)
|
| 77 |
+
area_ref = next((int(n) for n in solo_nums.split() if n.isdigit() and 20 < int(n) < 500), area_m2)
|
| 78 |
+
referencias.append({
|
| 79 |
+
"Portal": "Finca Raíz",
|
| 80 |
+
"Descripción": full_text.split('\n')[0][:50],
|
| 81 |
+
"Precio": precio,
|
| 82 |
+
"Area": area_ref,
|
| 83 |
+
"Precio_M2": precio / area_ref,
|
| 84 |
+
"URL": url
|
| 85 |
+
})
|
| 86 |
+
except: continue
|
| 87 |
+
except: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
return referencias
|
| 89 |
|
| 90 |
+
def comparar_mercado_tramitia(zona, area_m2, tipo):
|
| 91 |
with sync_playwright() as p:
|
| 92 |
browser = p.chromium.launch(headless=True)
|
| 93 |
context = browser.new_context(user_agent=random.choice(USER_AGENTS))
|
| 94 |
page = context.new_page()
|
|
|
|
| 95 |
zona_url = zona.lower().replace(" ", "-")
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
# Búsqueda Arriendo
|
| 98 |
+
page.goto(f"https://www.fincaraiz.com.co/{tipo.lower()}/arriendo/{zona_url}", wait_until="domcontentloaded")
|
| 99 |
+
data_a = extraer_datos(page, area_m2)
|
| 100 |
+
|
| 101 |
+
# Búsqueda Venta
|
| 102 |
+
page.goto(f"https://www.fincaraiz.com.co/{tipo.lower()}/venta/{zona_url}", wait_until="domcontentloaded")
|
| 103 |
+
data_v = extraer_datos(page, area_m2)
|
| 104 |
+
|
| 105 |
+
browser.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
if not data_a and not data_v:
|
| 108 |
+
return "⚠️ No se hallaron datos.", None, None
|
| 109 |
+
|
| 110 |
+
df_a = pd.DataFrame(data_a); df_a['Tipo'] = 'Arriendo'
|
| 111 |
+
df_v = pd.DataFrame(data_v); df_v['Tipo'] = 'Venta'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
df_final = pd.concat([df_a, df_v], ignore_index=True)
|
| 113 |
+
|
| 114 |
+
# CÁLCULOS
|
| 115 |
+
prom_a = df_a['Precio'].mean() if not df_a.empty else 0
|
| 116 |
+
prom_v = df_v['Precio'].mean() if not df_v.empty else 0
|
| 117 |
+
|
| 118 |
+
resumen_html = f"""
|
| 119 |
+
<div style='padding:15px; background:#f0f9ff; border-radius:10px; border:1px solid #bae6fd;'>
|
| 120 |
+
<h3 style='color:#0369a1; margin:0;'>📊 Comparativa TramitIA Pro</h3>
|
| 121 |
+
<p>Promedio Arriendo: <b>${prom_a:,.0f} COP</b></p>
|
| 122 |
+
<p>Promedio Venta: <b>${prom_v:,.0f} COP</b></p>
|
| 123 |
+
</div>
|
| 124 |
+
"""
|
| 125 |
|
| 126 |
+
# GENERACIÓN OBLIGATORIA DEL PDF
|
| 127 |
+
pdf_generado = generar_pdf_comparativo(zona, area_m2, df_final)
|
|
|
|
| 128 |
|
| 129 |
+
return resumen_html, df_final, pdf_generado
|
| 130 |
|
| 131 |
+
# --- INTERFAZ ---
|
| 132 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 133 |
+
gr.HTML("<h1 style='text-align:center'>🤖 TramitIA Pro</h1>")
|
|
|
|
|
|
|
| 134 |
with gr.Row():
|
| 135 |
with gr.Column(scale=1):
|
| 136 |
+
zona = gr.Textbox(label="Zona (Ciudad y Barrio)")
|
| 137 |
+
area = gr.Number(label="M2", value=70)
|
| 138 |
+
tipo = gr.Dropdown(["Apartamento", "Casa"], label="Tipo", value="Apartamento")
|
| 139 |
+
btn = gr.Button("EJECUTAR COMPARATIVA", variant="primary")
|
|
|
|
| 140 |
with gr.Column(scale=2):
|
| 141 |
+
res = gr.HTML()
|
| 142 |
+
table = gr.Dataframe(label="Referencias Detalladas")
|
| 143 |
+
file = gr.File(label="Descargar Reporte PDF")
|
| 144 |
+
|
| 145 |
+
btn.click(comparar_mercado_tramitia, [zona, area, tipo], [res, table, file])
|
| 146 |
+
|
| 147 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|