Spaces:
Sleeping
Sleeping
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>InmoGuard V35</title> | |
| <style> | |
| :root { --p: #0f172a; --a: #3b82f6; --s: #10b981; --d: #ef4444; --w: #f59e0b; --bg: #f8fafc; } | |
| body { font-family: 'Segoe UI', sans-serif; background: var(--bg); padding: 20px; color: #334155; } | |
| .container { max-width: 1450px; margin: 0 auto; } | |
| .header { text-align: center; margin-bottom: 25px; } | |
| .header h1 { margin: 0; color: var(--p); font-size: 2rem; } | |
| .header span { color: var(--a); } | |
| .dropzone { background: white; border: 2px dashed #cbd5e1; padding: 30px; text-align: center; cursor: pointer; border-radius: 10px; transition: 0.3s; } | |
| .dropzone:hover { border-color: var(--a); background: #eff6ff; } | |
| .btn-group { text-align: center; margin-top: 20px; } | |
| button { padding: 12px 25px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; margin: 5px; color: white; font-size: 1rem; } | |
| .btn-go { background: var(--a); } | |
| .btn-pdf { background: var(--d); display: none; } | |
| .btn-json { background: #8b5cf6; display: none; } | |
| .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; margin-top: 30px; display: none; } | |
| .card { background: white; padding: 20px; border-radius: 8px; border-top: 5px solid #94a3b8; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); } | |
| .card h3 { margin-top: 0; color: var(--p); border-bottom: 1px solid #e2e8f0; padding-bottom: 10px; font-size: 1.15rem; } | |
| /* COLORES */ | |
| .c-sae { border-top-color: var(--d); background: #fff5f5; } | |
| .c-hist { border-top-color: var(--a); grid-column: span 2; } | |
| .c-vur { border-top-color: var(--w); grid-column: 1 / -1; } | |
| .c-amb { border-top-color: var(--s); } | |
| .c-riesgo { border-top-color: var(--p); } | |
| .lbl { font-weight: bold; font-size: 0.85rem; color: #64748b; display: block; margin-top: 8px; text-transform: uppercase; } | |
| .val { font-weight: 500; font-size: 1rem; color: #1e293b; } | |
| /* RIESGOS */ | |
| .risk-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px; font-size: 0.9em; } | |
| .bar-bg { flex: 1; height: 8px; background: #e2e8f0; border-radius: 4px; margin-left: 10px; overflow: hidden; } | |
| .bar-fill { height: 100%; transition: width 0.5s; } | |
| .bg-low { background: var(--s); width: 33%; } | |
| .bg-med { background: var(--w); width: 66%; } | |
| .bg-high { background: var(--d); width: 100%; } | |
| table { width: 100%; border-collapse: collapse; font-size: 0.9rem; margin-top: 10px; } | |
| th { text-align: left; padding: 8px; background: #f8fafc; color: #475569; border-bottom: 2px solid #e2e8f0; } | |
| td { padding: 8px; border-bottom: 1px solid #e2e8f0; } | |
| .tag-vig { color: #b91c1c; font-weight: bold; background: #fee2e2; padding: 2px 6px; border-radius: 4px; } | |
| .tag-cancel { color: #15803d; background: #dcfce7; padding: 2px 6px; border-radius: 4px; font-style: italic; } | |
| iframe { width: 100%; height: 250px; border: none; background: #e2e8f0; border-radius: 6px; } | |
| #status { text-align: center; margin-top: 15px; font-weight: bold; color: #64748b; } | |
| #err-box { background: #fee2e2; color: #b91c1c; padding: 15px; border-radius: 8px; margin-top: 20px; display: none; text-align: center; border: 1px solid #fecaca; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🛡️ InmoGuard <span>AI V35</span></h1> | |
| <p style="color:#64748b">Análisis Multi-Activo & Localización Profunda</p> | |
| </div> | |
| <div class="dropzone" onclick="document.getElementById('files').click()"> | |
| <h3>📂 Cargar Expediente</h3> | |
| <p id="prev-txt">Soporta PDFs y Fotos</p> | |
| <input type="file" id="files" multiple style="display:none" onchange="document.getElementById('prev-txt').innerText = `${this.files.length} Archivos`"> | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn-go" id="btnGo" onclick="run()">🔍 Analizar Completo</button> | |
| <button class="btn-pdf" id="btnPdf" onclick="dl('pdf')">📄 PDF</button> | |
| <button class="btn-json" id="btnJson" onclick="dl('json')">💾 JSON</button> | |
| </div> | |
| <div id="status"></div> | |
| <div id="err-box"></div> | |
| <div id="dash" class="grid"> | |
| <div class="card" style="border-top-color: #3b82f6;"> | |
| <h3>🏠 Identificación Legal</h3> | |
| <span class="lbl">FMI / Matrículas Detectadas:</span> | |
| <span class="val" id="fmi" style="color:var(--a); font-weight:bold; display:block; margin-top:5px; line-height:1.4;"></span> | |
| <span class="lbl">Cédula Catastral:</span> <span class="val" id="cedula" style="font-weight:bold;"></span> | |
| <span class="lbl">Dirección:</span> <span class="val" id="dir"></span> | |
| </div> | |
| <div class="card c-riesgo"> | |
| <h3>🚥 Tablero de Riesgos</h3> | |
| <div id="risk-box"></div> | |
| </div> | |
| <div class="card c-sae"> | |
| <h3>⚖️ Análisis Ley 1708 (SAE)</h3> | |
| <span class="lbl">Estado:</span> <span class="val" id="sae-st"></span> | |
| <span class="lbl">Concepto Legal:</span> <p class="val" id="sae-leg" style="font-size:0.9rem; font-style:italic"></p> | |
| <span class="lbl">Viabilidad FRISCO:</span> <span class="val" id="sae-via" style="font-weight:bold;"></span> | |
| </div> | |
| <div class="card c-hist"> | |
| <h3>📜 Historial de Propiedad (Cadena)</h3> | |
| <div id="hist-table"></div> | |
| </div> | |
| <div class="card c-amb"> | |
| <h3>🌳 Ambiental & Urbanístico</h3> | |
| <span class="lbl">Restricciones:</span> <span class="val" id="amb-res"></span> | |
| <span class="lbl">Autoridad:</span> <span class="val" id="amb-aut"></span> | |
| </div> | |
| <div class="card c-vur"> | |
| <h3>📑 VUR Detallado</h3> | |
| <div id="vur-table"></div> | |
| <div style="margin-top:15px; padding:10px; background:#fff7ed; border-radius:5px"> | |
| <span class="lbl" style="color:#c2410c; margin:0">Falsa Tradición:</span> <span class="val" id="falsa"></span> | |
| </div> | |
| </div> | |
| <div class="card c-fisc"> | |
| <h3>💰 Fiscal Detallado</h3> | |
| <span class="lbl">Municipal:</span><span class="val" id="fisc-mun"></span> | |
| <span class="lbl">Departamental:</span><span class="val" id="fisc-dep"></span> | |
| <span class="lbl">Avalúo:</span><span class="val" id="fisc-ava"></span> | |
| </div> | |
| <div class="card"><h3>🏢 Corporativo</h3><span class="lbl">Rep Legal:</span><span class="val" id="rep"></span></div> | |
| <div class="card"><h3>🚨 LAFT</h3><span class="val" id="alertas"></span></div> | |
| <div class="card"><h3>📍 Mapa</h3><div id="map-box"></div></div> | |
| <div class="card c-res" style="grid-column: 1 / -1; background:#e2e8f0; border-top-color:var(--p)"> | |
| <h3>🏁 Dictamen Final</h3> | |
| <h2 id="res-t" style="text-align:center;"></h2> | |
| <p id="res-d" style="padding:0 15px; font-size:1.05rem; line-height:1.6"></p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let gData = null; | |
| async function run() { | |
| let f = document.getElementById('files').files; | |
| if(!f.length) return alert("Sube archivos"); | |
| document.getElementById('status').innerText = "⏳ Consultando Ley 1708, VUR y FMI Múltiples..."; | |
| document.getElementById('btnGo').disabled = true; | |
| document.getElementById('err-box').style.display = 'none'; | |
| let fd = new FormData(); | |
| for(let x of f) fd.append('files', x); | |
| try { | |
| let req = await fetch('/analyze', { method: 'POST', body: fd }); | |
| let res = await req.json(); | |
| if(res.error) { | |
| document.getElementById('err-box').innerText = "❌ " + res.msg; | |
| document.getElementById('err-box').style.display = 'block'; | |
| document.getElementById('status').innerText = ""; | |
| } else { | |
| gData = res; | |
| render(res); | |
| document.getElementById('status').innerText = "✅ Análisis Completado"; | |
| } | |
| } catch(e) { | |
| document.getElementById('err-box').innerText = "❌ Error Red: " + e.message; | |
| document.getElementById('err-box').style.display = 'block'; | |
| } | |
| document.getElementById('btnGo').disabled = false; | |
| } | |
| function render(d) { | |
| document.getElementById('dash').style.display = 'grid'; | |
| document.getElementById('btnPdf').style.display = 'inline-block'; | |
| document.getElementById('btnJson').style.display = 'inline-block'; | |
| const get = (o, k) => (o && o[k]) ? o[k] : '---'; | |
| const m=d.meta||{}, sae=d.analisis_sae_ley||{}, hist=d.historial_propiedad||[], amb=d.ambiental||{}, fisc=d.fiscal_completo||{}; | |
| const v=d.vur||{}, end=d.dic||{}, c=d.corp||{}, l=d.laft||{}, val=d.val||{}, sem=d.semaforo_riesgos||{}; | |
| document.getElementById('fmi').innerText = get(m,'fmi'); | |
| // FIX CÉDULA OBJECT | |
| let ced = m.cedula_catastral; | |
| document.getElementById('cedula').innerText = (typeof ced === 'object') ? JSON.stringify(ced) : (ced || '---'); | |
| document.getElementById('dir').innerText = get(m,'dir_legal'); | |
| // Riesgos Visuales | |
| let rHtml = ""; | |
| for(let k in sem) { | |
| let s = (sem[k]||"BAJO").toUpperCase(); | |
| let cls = s.includes("ALTO")?"bg-high":(s.includes("MEDIO")?"bg-med":"bg-low"); | |
| rHtml += `<div class="risk-row"><span>${k.toUpperCase()}</span><div class="bar-bg"><div class="bar-fill ${cls}"></div></div></div>`; | |
| } | |
| document.getElementById('risk-box').innerHTML = rHtml; | |
| // SAE | |
| document.getElementById('sae-st').innerText = get(sae,'estado_proceso'); | |
| document.getElementById('sae-leg').innerText = get(sae,'fundamento_legal'); | |
| document.getElementById('sae-via').innerText = get(sae,'viabilidad_comercializacion'); | |
| // Ambiental | |
| document.getElementById('amb-res').innerText = get(amb,'restricciones'); | |
| document.getElementById('amb-aut').innerText = get(amb,'autoridad'); | |
| // Historial | |
| let hh = "<table style='width:100%'><tr><th>Fecha</th><th>Acto</th><th>Detalle</th></tr>"; | |
| hist.forEach(x => hh+=`<tr><td>${x.fecha||''}</td><td>${x.acto||''}</td><td>${x.detalles||''}</td></tr>`); | |
| document.getElementById('hist-table').innerHTML = hh + "</table>"; | |
| // VUR | |
| document.getElementById('falsa').innerText = get(v,'falsa_tradicion'); | |
| let hv = "<table><tr><th>Nro</th><th>Desc</th><th>Estado</th></tr>"; | |
| (v.anotaciones_detalle||[]).forEach(x => { | |
| let st = x.estado || '---'; | |
| let cls = st.toUpperCase().includes('VIGENTE') ? 'tag-vig' : 'tag-cancel'; | |
| hv += `<tr><td>${x.nro}</td><td>${x.desc}</td><td><span class="${cls}">${st}</span></td></tr>`; | |
| }); | |
| document.getElementById('vur-table').innerHTML = hv + "</table>"; | |
| // Fiscal | |
| document.getElementById('fisc-mun').innerText = get(fisc,'municipal'); | |
| document.getElementById('fisc-dep').innerText = get(fisc,'departamental'); | |
| document.getElementById('fisc-ava').innerText = get(fisc,'avaluo'); | |
| // Otros | |
| document.getElementById('rep').innerText = get(c,'rep'); | |
| document.getElementById('alertas').innerText = get(l,'alertas'); | |
| if(d.mapa) { | |
| let fr = document.createElement('iframe'); fr.srcdoc = d.mapa; | |
| document.getElementById('map-box').innerHTML = ''; document.getElementById('map-box').appendChild(fr); | |
| } else document.getElementById('map-box').innerHTML = '<div style="padding:30px; text-align:center; color:#94a3b8">Ubicación no encontrada</div>'; | |
| let r = get(end,'res'); | |
| let el = document.getElementById('res-t'); | |
| el.innerText = r; | |
| el.style.color = r.includes('NO') ? 'var(--d)' : 'var(--s)'; | |
| document.getElementById('res-d').innerText = get(end,'txt'); | |
| } | |
| async function dl(t) { | |
| if(!gData) return; | |
| try { | |
| let ep = t === 'pdf' ? '/print-pdf' : '/download-json'; | |
| let req = await fetch(ep, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(gData)}); | |
| let b = await req.blob(); | |
| let url = window.URL.createObjectURL(b); | |
| let a = document.createElement('a'); a.href=url; a.download=`Informe.${t}`; a.click(); | |
| } catch(e) { alert("Error descarga"); } | |
| } | |
| </script> | |
| </body> | |
| </html> |