jcalbornoz commited on
Commit
d0bee36
·
verified ·
1 Parent(s): b757190

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -208
app.py CHANGED
@@ -1,11 +1,3 @@
1
- import sys
2
-
3
- # Silenciador del error fantasma de Linux (No congela, solo oculta el texto rojo)
4
- def silenciador_errores_basura(unraisable):
5
- if unraisable.exc_type == ValueError and "Invalid file descriptor: -1" in str(unraisable.exc_value): pass
6
- else: sys.__unraisablehook__(unraisable)
7
- sys.unraisablehook = silenciador_errores_basura
8
-
9
  import os
10
  import subprocess
11
  import pandas as pd
@@ -16,10 +8,6 @@ import gradio as gr
16
  from playwright.sync_api import sync_playwright
17
  import time
18
  import random
19
- import requests
20
- from PIL import Image
21
- import io
22
- import traceback
23
 
24
  # --- INSTALACIÓN DE DEPENDENCIAS ---
25
  try:
@@ -32,254 +20,224 @@ except ImportError:
32
  subprocess.run(["pip", "install", "fake-useragent"], check=True)
33
  from fake_useragent import UserAgent
34
 
35
- # --- DESCARGA SEGURA DE FOTOS ---
36
- def descargar_imagen(url, idx):
37
- if not url or len(url) < 10 or url.startswith("data:"):
38
- return None
39
- try:
40
- r = requests.get(url, timeout=5, headers={"User-Agent": "Mozilla/5.0"})
41
- if r.status_code == 200:
42
- img = Image.open(io.BytesIO(r.content))
43
- if img.mode != 'RGB':
44
- img = img.convert('RGB')
45
- path = f"temp_img_{idx}.jpg"
46
- img.save(path, format="JPEG")
47
- return path
48
- except:
49
- return None # Si falla, devuelve None silenciosamente
50
- return None
51
-
52
- # --- 1. GENERADOR DE URLS ---
53
  def construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina):
54
  mapa_ant = {
55
- "Menos de 1 año": "de-0-a-1-anos", "1 a 8 años": "de-1-a-8-anos",
56
- "9 a 15 años": "de-9-a-15-anos", "16 a 30 años": "de-16-a-30-anos", "Más de 30 años": "mas-de-30-anos"
 
 
 
57
  }
58
  slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anos")
59
  slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
60
 
61
  b_slug = barrio.lower().strip().replace(" ", "-")
62
  c_slug = ciudad.lower().strip().replace(" ", "-")
63
- op_slug = operacion.lower().strip()
64
 
65
  tipo_slug = tipo.lower().strip()
66
- tipo_fr = "casas-y-apartamentos-y-apartaestudios" if tipo_slug in ["apartamento", "casa"] else tipo_slug + "s"
 
 
 
67
 
 
68
  url_fr_base = f"https://www.fincaraiz.com.co/{op_slug}/{tipo_fr}/{b_slug}/{c_slug}/{int(hab)}-o-mas-habitaciones/{int(ban)}-o-mas-banos/{slug_park}/{slug_ant}/m2-desde-{int(m2_min)}/m2-hasta-{int(m2_max)}"
 
 
69
  if ascensor: url_fr_base += "/con-ascensor"
70
  if piscina: url_fr_base += "/con-piscina"
71
 
 
 
 
 
72
  url_mc = f"https://www.metrocuadrado.com/{tipo_slug}-casa-oficina/{op_slug}/{c_slug}/{b_slug}/{int(ban)}-banos-{int(hab)}-habitaciones/?search=form"
73
 
74
- return url_fr_base, url_mc
75
 
76
- # --- 2. EXTRACTORES INTELIGENTES ---
77
- def extraer_precio(texto, operacion):
78
  patron = r'\$\s?(\d{1,3}(?:[.,]\d{3})*)'
79
  coincidencias = re.findall(patron, texto)
80
  if coincidencias:
81
  precios = [int(p.replace('.', '').replace(',', '')) for p in coincidencias]
82
- precios_validos = [p for p in precios if (600000 <= p <= 40000000 if operacion == "Arriendo" else p >= 40000000)]
83
- if precios_validos: return precios_validos[0]
 
 
 
 
 
 
 
84
  return 0
85
 
86
- def extraer_ubicacion(texto):
87
- lineas = [l.strip() for l in texto.split('\n') if len(l.strip()) > 5]
88
- if len(lineas) > 1:
89
- for linea in lineas[1:4]:
90
- if "$" not in linea and not linea.isdigit() and "m²" not in linea:
91
- return linea[:60]
92
- return "Ubicación en zona solicitada"
93
-
94
- # --- 3. MOTOR DE EXTRACCIÓN (SÍNCRONO Y ESTABLE) ---
95
- def motor_tramitia_visual(operacion, barrio, ciudad, area, m2_min, m2_max, tipo, hab, ban, park, antiguedad, ascensor, piscina):
96
  resultados = []
97
- log_visible = ""
98
 
99
- try:
100
- url_fr, url_mc = construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
101
- log_visible = f"✅ INICIANDO EXTRACCIÓN:\nFR: {url_fr}\nMC: {url_mc}\n\n"
102
- ua = UserAgent()
103
-
104
- with sync_playwright() as p:
105
- browser = p.chromium.launch(headless=True, args=['--disable-blink-features=AutomationControlled', '--no-sandbox'])
106
- context = browser.new_context(viewport={'width': 1366, 'height': 768}, user_agent=ua.random)
107
-
108
- # --- FINCA RAÍZ ---
109
- try:
110
- page = context.new_page()
111
- log_visible += "🔄 FR: Buscando inmuebles...\n"
112
- page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
113
-
114
- for _ in range(3):
115
- page.mouse.wheel(0, 1000)
116
- time.sleep(1.5)
117
 
118
- cards = page.query_selector_all("article, div[class*='card']")
119
- for card in cards[:12]:
120
- try:
121
- txt = card.inner_text()
122
- precio = extraer_precio(txt, operacion)
123
- if precio > 0:
124
- enlace_el = card.query_selector("a")
125
- img_el = card.query_selector("img")
126
-
127
- href = enlace_el.get_attribute("href") if enlace_el else ""
128
- img_url = img_el.get_attribute("src") if img_el else ""
 
129
 
130
- if href:
131
- resultados.append({
132
- "Portal": "Finca Raiz",
133
- "Precio": precio,
134
- "Precio_M2": precio / area,
135
- "Ubicacion": extraer_ubicacion(txt),
136
- "Descripcion": txt.replace('\n', ' | ')[:120] + "...",
137
- "URL": f"https://www.fincaraiz.com.co{href}" if href.startswith("/") else href,
138
- "Imagen": img_url
139
- })
140
- except: continue # Si una tarjeta falla, salta a la siguiente
141
- page.close()
142
- except Exception as e: log_visible += f"⚠️ Error buscando en FR.\n"
143
 
144
- # --- METROCUADRADO ---
145
- try:
146
- page = context.new_page()
147
- log_visible += "🔄 MC: Buscando inmuebles...\n"
148
- page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
 
149
 
150
- for _ in range(4):
151
- page.mouse.wheel(0, 1000)
152
- time.sleep(1.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- cards = page.query_selector_all("li, div.property-card")
155
- for card in cards[:12]:
156
- try:
157
- txt = card.inner_text()
158
- precio = extraer_precio(txt, operacion)
159
- if precio > 0:
160
- enlace_el = card.query_selector("a")
161
- img_el = card.query_selector("img")
162
 
163
- href = enlace_el.get_attribute("href") if enlace_el else ""
164
- img_url = img_el.get_attribute("src") if img_el else ""
165
-
166
- if href:
167
  resultados.append({
168
  "Portal": "Metrocuadrado",
169
  "Precio": precio,
170
  "Precio_M2": precio / area,
171
- "Ubicacion": extraer_ubicacion(txt),
172
- "Descripcion": txt.replace('\n', ' | ')[:120] + "...",
173
- "URL": f"https://www.metrocuadrado.com{href}" if href.startswith("/") else href,
174
- "Imagen": img_url
175
  })
176
- except: continue
177
- page.close()
178
- except Exception as e: log_visible += f"⚠️ Error buscando en MC.\n"
179
-
180
- browser.close()
181
-
182
- if not resultados:
183
- return f"{log_visible}\n❌ NO SE ENCONTRARON DATOS.", pd.DataFrame(), None, "---"
184
-
185
- # --- LIMPIEZA ---
186
- df_crudo = pd.DataFrame(resultados).drop_duplicates(subset=['URL'])
187
- df_fr = df_crudo[df_crudo['Portal'] == 'Finca Raiz'].head(6)
188
- df_mc = df_crudo[df_crudo['Portal'] == 'Metrocuadrado'].head(6)
189
- df_final = pd.concat([df_fr, df_mc]).reset_index(drop=True)
190
-
191
- log_visible += f"\n✨ PROCESANDO PDF CON {len(df_final)} INMUEBLES..."
192
-
193
- # --- PDF ---
194
- pdf_path = f"Reporte_Visual_{int(time.time())}.pdf"
195
- pdf = FPDF()
196
- pdf.add_page()
197
 
198
- pdf.set_font("Arial", 'B', 16)
199
- pdf.set_fill_color(40, 53, 147)
200
- pdf.set_text_color(255, 255, 255)
201
- pdf.cell(0, 15, f" REPORTE INMOBILIARIO: {barrio.upper()} ({operacion.upper()})", ln=True, fill=True)
202
- pdf.set_text_color(0, 0, 0)
203
- pdf.ln(5)
204
 
205
- for idx, r in df_final.iterrows():
206
- if pdf.get_y() > 220: pdf.add_page()
207
-
208
- y_start = pdf.get_y()
209
- img_path = descargar_imagen(r['Imagen'], idx)
210
- text_x = 10
211
-
212
- if img_path and os.path.exists(img_path):
213
- try:
214
- pdf.image(img_path, x=10, y=y_start, w=45, h=30)
215
- text_x = 60
216
- except: pass
217
 
218
- pdf.set_xy(text_x, y_start)
219
- pdf.set_font("Arial", 'B', 12)
220
- pdf.cell(0, 6, f"${r['Precio']:,.0f} COP", ln=True)
221
-
222
- pdf.set_x(text_x)
223
- pdf.set_font("Arial", 'B', 9)
224
- pdf.set_text_color(100, 100, 100)
225
- pdf.cell(0, 5, f"📍 {r['Ubicacion']} | Fuente: {r['Portal']}", ln=True)
226
-
227
- pdf.set_x(text_x)
228
- pdf.set_font("Arial", '', 8)
229
- pdf.set_text_color(0, 0, 0)
230
- pdf.multi_cell(0, 4, f"{r['Descripcion']}")
231
-
232
- pdf.set_x(text_x)
233
- pdf.set_font("Arial", 'U', 8)
234
- pdf.set_text_color(0, 102, 204)
235
- pdf.cell(0, 5, "🔗 Hacer clic aquí para ver publicación", link=r['URL'], ln=True)
236
- pdf.set_text_color(0, 0, 0)
237
-
238
- y_end = pdf.get_y()
239
- pdf.set_y(max(y_start + 35, y_end + 5))
240
-
241
- pdf.set_draw_color(200, 200, 200)
242
- pdf.line(10, pdf.get_y(), 200, pdf.get_y())
243
- pdf.ln(5)
244
-
245
- if img_path and os.path.exists(img_path):
246
- try: os.remove(img_path)
247
- except: pass
248
 
249
- pdf.output(pdf_path)
250
-
251
- # --- CÁLCULOS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  promedio = df_final['Precio_M2'].mean() * area
 
 
 
253
  resumen = (
254
  f"💰 **ESTIMACIÓN DE {operacion.upper()}**\n"
255
  f"🔹 **Valor Sugerido:** ${promedio:,.0f}\n"
256
- f"📉 Mínimo Zona: ${df_final['Precio'].min():,.0f}\n"
257
- f"📈 Máximo Zona: ${df_final['Precio'].max():,.0f}"
258
  )
259
-
260
- df_mostrar = df_final[['Portal', 'Precio', 'Ubicacion', 'Descripcion', 'URL']]
261
-
262
- return f"{log_visible}\n✅ PDF Generado Exitosamente.", df_mostrar, pdf_path, resumen
263
-
264
- except Exception as error_fatal:
265
- # PARACAÍDAS: Si todo colapsa, te mostrará exactamente DÓNDE y POR QUÉ en la pantalla
266
- traza = traceback.format_exc()
267
- return f"❌ ERROR GRAVE DEL SISTEMA:\n{str(error_fatal)}\n\nDetalles:\n{traza}", pd.DataFrame(), None, "⚠️ Falló la ejecución"
268
 
269
  # --- INTERFAZ GRÁFICA ---
270
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
271
- gr.Markdown("## 📸 TramitIA Pro: Generador Visual (Versión Estable)")
272
 
273
  with gr.Row():
274
  with gr.Column(scale=1):
275
  op = gr.Radio(["Arriendo", "Venta"], label="¿Qué deseas hacer?", value="Arriendo")
 
276
  c = gr.Textbox(label="Ciudad", value="Barranquilla")
277
  b = gr.Textbox(label="Barrio (Ej: La Concepcion)", value="La Concepcion")
278
 
279
  with gr.Row():
280
  t = gr.Dropdown(["Apartamento", "Casa", "Bodega", "Lote", "Oficina"], label="Tipo", value="Apartamento")
281
- a = gr.Number(label="Área M2", value=70)
282
 
 
283
  with gr.Row():
284
  m2_min = gr.Number(label="M2 Mínimo", value=10)
285
  m2_max = gr.Number(label="M2 Máximo", value=200)
@@ -289,20 +247,25 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
289
  piscina = gr.Checkbox(label="Con Piscina")
290
 
291
  with gr.Row():
292
- h = gr.Number(label="Hab", value=3)
293
  ban = gr.Number(label="Baños", value=2)
294
- p = gr.Number(label="Park", value=1)
295
 
296
- e = gr.Dropdown(["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"], label="Antigüedad", value="1 a 8 años")
297
- btn = gr.Button("GENERAR REPORTE", variant="primary")
 
 
 
298
 
299
  with gr.Column(scale=2):
300
  res_fin = gr.Markdown("### 💰 El resultado aparecerá aquí...")
301
  with gr.Tabs():
 
 
302
  with gr.TabItem("Descargar PDF"): out_pdf = gr.File()
303
- with gr.TabItem("Tabla de Datos"): out_df = gr.Dataframe()
304
- with gr.TabItem("Auditoría"): msg = gr.Textbox(lines=10)
305
 
306
- btn.click(motor_tramitia_visual, [op, b, c, a, m2_min, m2_max, t, h, ban, p, e, ascensor, piscina], [msg, out_df, out_pdf, res_fin])
 
 
307
 
308
  demo.launch()
 
 
 
 
 
 
 
 
 
1
  import os
2
  import subprocess
3
  import pandas as pd
 
8
  from playwright.sync_api import sync_playwright
9
  import time
10
  import random
 
 
 
 
11
 
12
  # --- INSTALACIÓN DE DEPENDENCIAS ---
13
  try:
 
20
  subprocess.run(["pip", "install", "fake-useragent"], check=True)
21
  from fake_useragent import UserAgent
22
 
23
+ # --- 1. GENERADOR DE URLS (VENTA/ARRIENDO Y EXTRAS) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  def construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina):
25
  mapa_ant = {
26
+ "Menos de 1 año": "de-0-a-1-anos",
27
+ "1 a 8 años": "de-1-a-8-anos",
28
+ "9 a 15 años": "de-9-a-15-anos",
29
+ "16 a 30 años": "de-16-a-30-anos",
30
+ "Más de 30 años": "mas-de-30-anos"
31
  }
32
  slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anos")
33
  slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
34
 
35
  b_slug = barrio.lower().strip().replace(" ", "-")
36
  c_slug = ciudad.lower().strip().replace(" ", "-")
37
+ op_slug = operacion.lower().strip() # 'venta' o 'arriendo'
38
 
39
  tipo_slug = tipo.lower().strip()
40
+ if tipo_slug in ["apartamento", "casa"]:
41
+ tipo_fr = "casas-y-apartamentos-y-apartaestudios"
42
+ else:
43
+ tipo_fr = tipo_slug + "s"
44
 
45
+ # --- URL FINCA RAÍZ MULTIFILTRO ---
46
  url_fr_base = f"https://www.fincaraiz.com.co/{op_slug}/{tipo_fr}/{b_slug}/{c_slug}/{int(hab)}-o-mas-habitaciones/{int(ban)}-o-mas-banos/{slug_park}/{slug_ant}/m2-desde-{int(m2_min)}/m2-hasta-{int(m2_max)}"
47
+
48
+ # Extras condicionales
49
  if ascensor: url_fr_base += "/con-ascensor"
50
  if piscina: url_fr_base += "/con-piscina"
51
 
52
+ url_fr = url_fr_base
53
+
54
+ # --- URL METROCUADRADO ---
55
+ # MC usa un formato más simple, le pasamos la operación dinámica
56
  url_mc = f"https://www.metrocuadrado.com/{tipo_slug}-casa-oficina/{op_slug}/{c_slug}/{b_slug}/{int(ban)}-banos-{int(hab)}-habitaciones/?search=form"
57
 
58
+ return url_fr, url_mc
59
 
60
+ # --- 2. EXTRACTOR DE PRECIO DINÁMICO ---
61
+ def extraer_precio_regex(texto, operacion):
62
  patron = r'\$\s?(\d{1,3}(?:[.,]\d{3})*)'
63
  coincidencias = re.findall(patron, texto)
64
  if coincidencias:
65
  precios = [int(p.replace('.', '').replace(',', '')) for p in coincidencias]
66
+
67
+ # Filtro de cordura según si es Venta o Arriendo
68
+ if operacion == "Arriendo":
69
+ precios_validos = [p for p in precios if 600000 <= p <= 40000000] # Ignora ventas y admón
70
+ else: # Venta
71
+ precios_validos = [p for p in precios if p >= 40000000] # Ignora arriendos cruzados
72
+
73
+ if precios_validos:
74
+ return precios_validos[0]
75
  return 0
76
 
77
+ # --- 3. MOTOR DE EXTRACCIÓN ---
78
+ def motor_tramitia_final(operacion, barrio, ciudad, area, m2_min, m2_max, tipo, hab, ban, park, antiguedad, ascensor, piscina):
 
 
 
 
 
 
 
 
79
  resultados = []
80
+ url_fr, url_mc = construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
81
 
82
+ log_visible = f"✅ URLs GENERADAS (AUDITORÍA):\nFR: {url_fr}\nMC: {url_mc}\n\n"
83
+ ua = UserAgent()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ with sync_playwright() as p:
86
+ browser = p.chromium.launch(
87
+ headless=True,
88
+ args=[
89
+ '--disable-blink-features=AutomationControlled',
90
+ '--no-sandbox',
91
+ '--disable-infobars',
92
+ '--window-position=0,0',
93
+ f'--user-agent={ua.random}'
94
+ ]
95
+ )
96
+ context = browser.new_context(viewport={'width': 1366, 'height': 768})
97
 
98
+ # --- FINCA RAÍZ ---
99
+ try:
100
+ page = context.new_page()
101
+ log_visible += f"🔄 Escaneando Finca Raíz ({operacion})...\n"
102
+ page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
103
+
104
+ page.mouse.move(random.randint(100, 500), random.randint(100, 500))
105
+ for _ in range(4):
106
+ page.mouse.wheel(0, 1000)
107
+ time.sleep(1.5)
 
 
 
108
 
109
+ elementos = page.query_selector_all("a")
110
+ cont_fr = 0
111
+
112
+ for el in elementos:
113
+ if cont_fr >= 15: break
114
+ txt = el.inner_text()
115
 
116
+ if "$" in txt:
117
+ precio = extraer_precio_regex(txt, operacion) # Pasamos la operación
118
+ if precio > 0:
119
+ href = el.get_attribute("href")
120
+ full_url = f"https://www.fincaraiz.com.co{href}" if href and href.startswith("/") else href
121
+
122
+ if full_url:
123
+ resultados.append({
124
+ "Portal": "Finca Raiz",
125
+ "Precio": precio,
126
+ "Precio_M2": precio / area,
127
+ "Descripcion": txt.replace('\n', ' ')[:90] + "...",
128
+ "URL": full_url
129
+ })
130
+ cont_fr += 1
131
+ log_visible += f"✅ FR: Encontrados {cont_fr} candidatos brutos.\n"
132
+ page.close()
133
+ except Exception as e: log_visible += f"⚠️ Error FR: {e}\n"
134
+
135
+ # --- METROCUADRADO ---
136
+ try:
137
+ page = context.new_page()
138
+ log_visible += f"🔄 Escaneando Metrocuadrado ({operacion})...\n"
139
+ page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
140
+
141
+ for _ in range(5):
142
+ page.mouse.wheel(0, 1000)
143
+ time.sleep(1.5)
144
+
145
+ cards = page.query_selector_all("li, div[class*='card']")
146
+ cont_mc = 0
147
+
148
+ for card in cards:
149
+ if cont_mc >= 15: break
150
+ txt = card.inner_text()
151
 
152
+ if "$" in txt:
153
+ precio = extraer_precio_regex(txt, operacion)
154
+ if precio > 0:
155
+ enlace = card.query_selector("a")
156
+ if enlace:
157
+ href = enlace.get_attribute("href")
158
+ full_url = f"https://www.metrocuadrado.com{href}" if href and href.startswith("/") else href
 
159
 
160
+ if full_url:
 
 
 
161
  resultados.append({
162
  "Portal": "Metrocuadrado",
163
  "Precio": precio,
164
  "Precio_M2": precio / area,
165
+ "Descripcion": txt.replace('\n', ' ')[:90] + "...",
166
+ "URL": full_url
 
 
167
  })
168
+ cont_mc += 1
169
+ log_visible += f"✅ MC: Encontrados {cont_mc} candidatos brutos.\n"
170
+ page.close()
171
+ except Exception as e: log_visible += f"⚠️ Error MC: {e}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ browser.close()
 
 
 
 
 
174
 
175
+ if not resultados:
176
+ return f"{log_visible}\n❌ NO SE ENCONTRARON DATOS VÁLIDOS.", None, None, "---"
 
 
 
 
 
 
 
 
 
 
177
 
178
+ # --- LIMPIEZA Y SELECCIÓN ---
179
+ df_crudo = pd.DataFrame(resultados)
180
+ df_limpio = df_crudo.drop_duplicates(subset=['URL'])
181
+
182
+ df_fr = df_limpio[df_limpio['Portal'] == 'Finca Raiz'].head(6)
183
+ df_mc = df_limpio[df_limpio['Portal'] == 'Metrocuadrado'].head(6)
184
+
185
+ df_final = pd.concat([df_fr, df_mc]).reset_index(drop=True)
186
+
187
+ log_visible += f"\n✨ PROCESADO FINAL: {len(df_final)} inmuebles únicos seleccionados."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
+ # --- PDF ---
190
+ pdf_path = f"Reporte_{int(time.time())}.pdf"
191
+ pdf = FPDF()
192
+ pdf.add_page()
193
+ pdf.set_font("Arial", 'B', 14)
194
+ pdf.cell(0, 10, f"ESTUDIO DE MERCADO: {barrio.upper()} ({operacion.upper()})", ln=True)
195
+ pdf.ln(5)
196
+
197
+ for _, r in df_final.iterrows():
198
+ pdf.set_fill_color(240, 240, 240)
199
+ pdf.set_font("Arial", 'B', 10)
200
+ pdf.cell(0, 8, f"{r['Portal']} - ${r['Precio']:,.0f}", ln=True, fill=True)
201
+ pdf.set_font("Arial", '', 9)
202
+ pdf.multi_cell(0, 5, f"{r['Descripcion']}")
203
+ pdf.set_font("Arial", 'U', 8); pdf.set_text_color(0,0,255)
204
+ pdf.cell(0, 6, "Ver Publicacion Original", link=r['URL'], ln=True)
205
+ pdf.set_text_color(0,0,0); pdf.ln(3)
206
+ pdf.output(pdf_path)
207
+
208
+ # --- CÁLCULOS ---
209
+ if not df_final.empty:
210
  promedio = df_final['Precio_M2'].mean() * area
211
+ minimo = df_final['Precio'].min()
212
+ maximo = df_final['Precio'].max()
213
+
214
  resumen = (
215
  f"💰 **ESTIMACIÓN DE {operacion.upper()}**\n"
216
  f"🔹 **Valor Sugerido:** ${promedio:,.0f}\n"
217
+ f"📉 Mínimo Zona: ${minimo:,.0f}\n"
218
+ f"📈 Máximo Zona: ${maximo:,.0f}"
219
  )
220
+ else:
221
+ resumen = "⚠️ No hay suficientes datos para calcular."
222
+
223
+ return f"{log_visible}\n✅ Tarea Completada.", df_final, pdf_path, resumen
 
 
 
 
 
224
 
225
  # --- INTERFAZ GRÁFICA ---
226
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
227
+ gr.Markdown("## 🤖 TramitIA Pro: Tasador Inmobiliario Integral")
228
 
229
  with gr.Row():
230
  with gr.Column(scale=1):
231
  op = gr.Radio(["Arriendo", "Venta"], label="¿Qué deseas hacer?", value="Arriendo")
232
+
233
  c = gr.Textbox(label="Ciudad", value="Barranquilla")
234
  b = gr.Textbox(label="Barrio (Ej: La Concepcion)", value="La Concepcion")
235
 
236
  with gr.Row():
237
  t = gr.Dropdown(["Apartamento", "Casa", "Bodega", "Lote", "Oficina"], label="Tipo", value="Apartamento")
238
+ a = gr.Number(label="Área M2 (Tu Cliente)", value=70)
239
 
240
+ gr.Markdown("### Filtros de Búsqueda")
241
  with gr.Row():
242
  m2_min = gr.Number(label="M2 Mínimo", value=10)
243
  m2_max = gr.Number(label="M2 Máximo", value=200)
 
247
  piscina = gr.Checkbox(label="Con Piscina")
248
 
249
  with gr.Row():
250
+ h = gr.Number(label="Habitaciones", value=3)
251
  ban = gr.Number(label="Baños", value=2)
252
+ p = gr.Number(label="Parqueaderos", value=1)
253
 
254
+ e = gr.Dropdown(
255
+ ["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"],
256
+ label="Antigüedad", value="1 a 8 años"
257
+ )
258
+ btn = gr.Button("EJECUTAR ANÁLISIS COMERCIAL", variant="primary")
259
 
260
  with gr.Column(scale=2):
261
  res_fin = gr.Markdown("### 💰 El resultado aparecerá aquí...")
262
  with gr.Tabs():
263
+ with gr.TabItem("Auditoría"): msg = gr.Textbox(lines=10, label="Log del Sistema")
264
+ with gr.TabItem("Tabla de Resultados"): out_df = gr.Dataframe()
265
  with gr.TabItem("Descargar PDF"): out_pdf = gr.File()
 
 
266
 
267
+ btn.click(motor_tramitia_final,
268
+ [op, b, c, a, m2_min, m2_max, t, h, ban, p, e, ascensor, piscina],
269
+ [msg, out_df, out_pdf, res_fin])
270
 
271
  demo.launch()