jcalbornoz commited on
Commit
f3bce43
·
verified ·
1 Parent(s): ecbcd82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -150
app.py CHANGED
@@ -2,175 +2,103 @@ import os
2
  import subprocess
3
  import pandas as pd
4
  import datetime
5
- import folium
6
  from fpdf import FPDF
7
  import gradio as gr
8
- import plotly.express as px
9
  from playwright.sync_api import sync_playwright
10
  import time
 
11
 
12
- # --- CONFIGURACIÓN DE ENTORNO ---
13
  try:
14
  subprocess.run(["playwright", "install", "chromium"], check=True)
15
- except:
16
- pass
17
 
18
- # --- GENERADOR DE REPORTE TÉCNICO ---
19
- def generar_pdf_pro(zona, area_m2, df):
20
- pdf = FPDF()
21
- pdf.add_page()
22
- pdf.set_font("Arial", 'B', 16)
23
- pdf.set_text_color(26, 54, 93)
24
- pdf.cell(0, 15, "TRAMITIA PRO - INFORME TÉCNICO DE ESTIMACIÓN", ln=True, align='C')
25
-
26
- pdf.set_font("Arial", '', 10)
27
- pdf.set_text_color(0, 0, 0)
28
- pdf.cell(0, 5, f"Zona Analizada: {zona.upper()}", ln=True)
29
- pdf.cell(0, 5, f"Área de Referencia: {area_m2} m2", ln=True)
30
- pdf.cell(0, 5, f"Fecha: {datetime.datetime.now().strftime('%d/%m/%Y')}", ln=True)
31
- pdf.ln(10)
32
-
33
- for i, r in df.iterrows():
34
- pdf.set_font("Arial", 'B', 11)
35
- pdf.set_fill_color(230, 235, 245)
36
- pdf.cell(0, 8, f"REFERENCIA #{i+1} - {r['Portal']}", ln=True, fill=True)
37
-
38
- pdf.set_font("Arial", '', 9)
39
- # Detalle técnico solicitado
40
- detalle = f"Precio: ${r['Precio']:,.0f} | Hab: {r['Habitaciones']} | Baños: {r['Banos']} | Garajes: {r['Garajes']} | Edad: {r['Antiguedad']}"
41
- pdf.cell(0, 7, detalle, ln=True)
42
-
43
- pdf.set_font("Arial", 'I', 8)
44
- pdf.multi_cell(0, 5, f"Descripción: {r['Descripcion']}")
45
-
46
- pdf.set_font("Arial", 'U', 8)
47
- pdf.set_text_color(0, 0, 255)
48
- pdf.cell(0, 7, f"Link de la publicación: {r['URL']}", ln=True)
49
- pdf.set_text_color(0, 0, 0)
50
- pdf.ln(5)
51
-
52
- if pdf.get_y() > 250: pdf.add_page()
53
-
54
- path_pdf = "Estimacion_Renta_TramitIA.pdf"
55
- pdf.output(path_pdf)
56
- return path_pdf
57
-
58
- # --- MOTOR DE BÚSQUEDA HÍBRIDO ---
59
- def motor_tramitia_pro(zona, area, tipo, hab, ban, park, edad):
60
- # Forzar la ciudad para evitar saltos de localización
61
- busqueda_url = zona.lower().replace(" ", "-")
62
- if "bogota" not in busqueda_url:
63
- busqueda_url += "-bogota"
64
-
65
- all_data = []
66
 
67
  with sync_playwright() as p:
 
68
  browser = p.chromium.launch(headless=True)
69
- # Cambiamos el contexto para parecer un navegador real en Colombia
70
- context = browser.new_context(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
71
-
72
- # FUENTES
73
- portales = [
74
- {
75
- "id": "Finca Raiz",
76
- "url": f"https://www.fincaraiz.com.co/{tipo.lower()}/arriendo/{busqueda_url}?habitaciones={int(hab)}&banos={int(ban)}"
77
- },
78
- {
79
- "id": "Metrocuadrado",
80
- "url": f"https://www.metrocuadrado.com/{tipo.lower()}/arriendo/bogota/{busqueda_url.replace('-bogota','')}/{int(hab)}-habitaciones/{int(ban)}-banos/"
81
- }
82
- ]
83
 
84
- for p_info in portales:
85
- page = context.new_page()
86
- try:
87
- # Navegar y esperar carga real
88
- page.goto(p_info["url"], wait_until="load", timeout=60000)
89
- time.sleep(5)
90
- page.mouse.wheel(0, 2000) # Scroll para cargar contenido dinámico
 
91
 
92
- # Buscamos anuncios que coincidan con la zona para evitar falsos positivos
93
- cards = page.query_selector_all("article, [class*='Card'], .listing-item")
94
 
95
- for card in cards:
96
- if len(all_data) >= 6: break # Límite de referencias
97
-
98
- try:
99
- full_text = card.inner_text()
100
- # Validación de seguridad: Si el texto del anuncio no menciona la zona o ciudad, lo ignoramos
101
- if "medellin" in full_text.lower() and "bogota" in busqueda_url:
102
- continue
103
-
104
- link_elem = card.query_selector("a")
105
- href = link_elem.get_attribute("href") if link_elem else ""
106
- url_final = href if "http" in href else f"https://www.{p_info['id'].lower().replace(' ', '')}.com.co{href}"
107
-
108
- precios = [int(s) for s in full_text.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
109
-
110
- if precios:
111
- # Extraer descripción real (buscando líneas con texto largo)
112
- desc_lineas = [l.strip() for l in full_text.split('\n') if len(l) > 20]
113
- descripcion = desc_lineas[0] if desc_lineas else "Ver más detalles en el portal."
114
-
115
- all_data.append({
116
- "Portal": p_info["id"],
117
- "Precio": max(precios),
118
- "Precio_M2": max(precios) / area,
119
- "Habitaciones": hab,
120
- "Banos": ban,
121
- "Garajes": park,
122
- "Antiguedad": edad,
123
- "Descripcion": descripcion,
124
- "URL": url_final
125
- })
126
- except: continue
127
- except: pass
128
- finally: page.close()
129
- browser.close()
130
 
131
- if not all_data:
132
- return "⚠️ No se hallaron datos exactos. Intenta con una zona más amplia o verifica la ortografía.", None, None, None, None
133
 
134
- df = pd.DataFrame(all_data)
135
- pdf_path = generar_pdf_pro(zona, area, df)
136
 
137
- # Gráfica comparativa
138
- fig = px.bar(df, x="Portal", y="Precio_M2", color="Portal", barmode="group",
139
- title="Comparativa Valor M2 por Fuente")
140
-
141
- # Mapa centrado en Bogotá
142
- m = folium.Map(location=[4.66, -74.11], zoom_start=14)
143
- folium.Marker([4.66, -74.11], popup=f"Análisis {zona}").add_to(m)
 
 
 
144
 
145
- return f"✅ Análisis Exitoso para {zona}. Se encontraron {len(df)} referencias justificadas.", df, pdf_path, fig, m._repr_html_()
146
 
147
- # --- INTERFAZ PANEL DE CONTROL ---
148
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
149
- gr.HTML("<h1 style='text-align:center'>🤖 TramitIA Pro</h1><p style='text-align:center;'>Sistema de Estimación de Renta Justificada</p>")
150
-
151
  with gr.Row():
152
- with gr.Column(scale=1):
153
- zona_in = gr.Textbox(label="Ubicación (Barrio y Ciudad)", value="Salitre Bogota")
154
- area_in = gr.Number(label="Área M2", value=150)
155
- tipo_in = gr.Dropdown(["Apartamento", "Casa"], label="Tipo de Inmueble", value="Apartamento")
156
- with gr.Row():
157
- hab_in = gr.Number(label="Habitaciones", value=3)
158
- ban_in = gr.Number(label="Baños", value=2)
159
- with gr.Row():
160
- gar_in = gr.Number(label="Parqueaderos", value=1)
161
- ant_in = gr.Dropdown(["Nuevo", "1-5 años", "5-10 años", "Más de 10"], label="Antigüedad", value="5-10 años")
162
- btn = gr.Button("EJECUTAR ANÁLISIS PROFUNDO", variant="primary")
163
-
164
- with gr.Column(scale=2):
165
- msg = gr.Markdown("Esperando consulta...")
166
- with gr.Tabs():
167
- with gr.TabItem("📋 Ficha Técnica y PDF"):
168
- table = gr.Dataframe(interactive=False)
169
- file = gr.File(label="Descargar Reporte de Consultoría")
170
- with gr.TabItem("📊 Mapa y Gráficas"):
171
- plot = gr.Plot()
172
- mapa = gr.HTML()
173
-
174
- btn.click(motor_tramitia_pro, [zona_in, area_in, tipo_in, hab_in, ban_in, gar_in, ant_in], [msg, table, file, plot, mapa])
175
 
176
  demo.launch()
 
2
  import subprocess
3
  import pandas as pd
4
  import datetime
 
5
  from fpdf import FPDF
6
  import gradio as gr
 
7
  from playwright.sync_api import sync_playwright
8
  import time
9
+ import random
10
 
11
+ # --- REINICIO DE NAVEGADOR ---
12
  try:
13
  subprocess.run(["playwright", "install", "chromium"], check=True)
14
+ except: pass
 
15
 
16
+ def motor_tramitia_limpio(zona, area, tipo, hab, ban, park, edad):
17
+ # LIMPIEZA INICIAL: Si la zona cambia, forzamos resultados nuevos
18
+ resultados = []
19
+ busqueda_clean = zona.lower().strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  with sync_playwright() as p:
22
+ # Usamos un nuevo contexto de incógnito total cada vez
23
  browser = p.chromium.launch(headless=True)
24
+ # Randomizamos el User-Agent para que el portal no nos reconozca
25
+ context = browser.new_context(user_agent=f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) {random.randint(1,100)}")
26
+ page = context.new_page()
27
+
28
+ try:
29
+ # URL de búsqueda estricta
30
+ query_url = busqueda_clean.replace(" ", "-")
31
+ url = f"https://www.fincaraiz.com.co/{tipo.lower()}/arriendo/{query_url}?habitaciones={int(hab)}&banos={int(ban)}"
32
+
33
+ page.goto(url, wait_until="networkidle", timeout=60000)
34
+ # Scroll agresivo para saltar la publicidad de Lagos de Torca
35
+ page.mouse.wheel(0, 3000)
36
+ time.sleep(4)
 
37
 
38
+ # Buscamos artículos, pero filtramos que el texto contenga la zona buscada
39
+ cards = page.query_selector_all("article")
40
+
41
+ for card in cards:
42
+ content = card.inner_text()
43
+ # VALIDACIÓN CRUCIAL: Solo tomar si el anuncio NO es el de siempre
44
+ if "Lagos de Torca" in content and "torca" not in busqueda_clean:
45
+ continue
46
 
47
+ # Extraer Precios
48
+ precios = [int(s) for s in content.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
49
 
50
+ if precios and len(resultados) < 5:
51
+ p_val = max(precios)
52
+ resultados.append({
53
+ "Portal": "Finca Raiz",
54
+ "Precio": p_val,
55
+ "Precio_M2": p_val / area,
56
+ "Habitaciones": hab,
57
+ "Banos": ban,
58
+ "Garajes": park,
59
+ "Antiguedad": edad,
60
+ "Descripcion": content.split('\n')[0][:100],
61
+ "URL": "https://fincaraiz.com.co" + (card.query_selector("a").get_attribute("href") if card.query_selector("a") else "")
62
+ })
63
+
64
+ browser.close()
65
+ except:
66
+ browser.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ if not resultados:
69
+ return " No se encontraron datos nuevos. Intenta cambiar un poco el nombre del barrio.", None
70
 
71
+ df_final = pd.DataFrame(resultados).drop_duplicates()
 
72
 
73
+ # Crear el PDF cada vez con nombre único para evitar que el navegador muestre el anterior
74
+ pdf_path = f"Reporte_{random.randint(1,999)}.pdf"
75
+ pdf = FPDF()
76
+ pdf.add_page()
77
+ pdf.set_font("Arial", 'B', 12)
78
+ pdf.cell(0, 10, f"Analisis: {zona}", ln=True)
79
+ pdf.set_font("Arial", '', 10)
80
+ for _, r in df_final.iterrows():
81
+ pdf.cell(0, 8, f"Precio: {r['Precio']} - {r['Descripcion']}", ln=True)
82
+ pdf.output(pdf_path)
83
 
84
+ return f"✅ Se encontraron {len(df_final)} registros nuevos para {zona}.", df_final, pdf_path
85
 
86
+ # --- INTERFAZ ---
87
+ with gr.Blocks() as demo:
88
+ gr.Markdown("# 🤖 TramitIA Pro: Hard Reset de Datos")
 
89
  with gr.Row():
90
+ with gr.Column():
91
+ z = gr.Textbox(label="Barrio y Ciudad (Escribe algo diferente, ej: Salitre Central Bogota)")
92
+ a = gr.Number(label="M2", value=80)
93
+ t = gr.Dropdown(["Apartamento", "Casa"], label="Tipo", value="Apartamento")
94
+ h = gr.Number(label="Hab", value=3); b = gr.Number(label="Banos", value=2)
95
+ p = gr.Number(label="Parqueaderos", value=1); e = gr.Textbox(label="Antiguedad", value="5-10")
96
+ btn = gr.Button("LIMPIAR CACHÉ Y BUSCAR")
97
+ with gr.Column():
98
+ msg = gr.Markdown()
99
+ out_df = gr.Dataframe()
100
+ out_pdf = gr.File()
101
+
102
+ btn.click(motor_tramitia_limpio, [z, a, t, h, b, p, e], [msg, out_df, out_pdf])
 
 
 
 
 
 
 
 
 
 
103
 
104
  demo.launch()