Bienvenidos

PRComps — Ventas Comparables

Datos del mercado
Inmobiliario de

Puerto Rico

Comparables y herramientas para el mercado de Bienes Raíces de Puerto Rico. PRComps ofrece a tasadores, corredores, inversionistas y consumidores acceso a transacciones de compraventa, mapas interactivos y utilidades en un solo lugar.

PRComps — mercado inmobiliario de Puerto Rico

Tasadores

Corredores

Inversionistas

Consumidores

Completar evaluaciones Crear listados Invertir Desarrollar Decidir con claridad

PRComps está para ayudarte.

Funciones

¿Necesitas comparables en minutos?
Ahorra tiempo y maximiza tu productividad con acceso instantáneo a ventas actualizadas y herramientas interactivas.

Qué ofrecemos

Realiza búsquedas de propiedades residenciales, multifamiliares, solares vacantes y comerciales con criterios precisos y accede a información completa de cada transacción.

  • Múltiples filtros de búsqueda
  • Detalles de la transacción
  • Mapas interactivos esenciales
  • Exportación a Excel para tu análisis
  • Fotos y escrituras
Características principales de PRComps

Membresía

Un solo plan, acceso total. Selecciona la modalidad que prefieras

Acceso — Premium

35.00 / mensual

Todas las funcionalidades incluidas en cualquier modalidad.

Contenido del plan:

  • Detalles de la transacción
  • Varios filtros de búsqueda
  • Mapas interactivos
  • Exportación a Excel
  • Gráficas
  • Fotos y Escrituras

Sujeto a disponibilidad.

Nuestra visión es facilitar a profesionales y consumidores el acceso a datos confiables y herramientas prácticas que les permitan tomar decisiones inmobiliarias exitosas.

// ---------- utils ---------- function getSidFromUrl(url){ try{ const u = new URL(url, location.origin); return u.searchParams.get("sid") || ""; }catch(_){ const m = String(url||"").match(/[?&]sid=([^&]+)/i); return m ? decodeURIComponent(m[1]) : ""; } } function norm(tok){ tok = String(tok || ""); tok = tok.replace(/\u00B2/g, "2"); tok = tok.replace(/([a-z0-9])([A-Z])/g, "$1_$2"); tok = tok.replace(/[^A-Za-z0-9]+/g, "_") .replace(/_+/g, "_") .replace(/^_|_$/g, "") .toLowerCase(); return tok; } function lookup(d, raw){ if (!d) return null; if (raw in d) return d[raw]; const g = norm(raw); if (g in d) return d[g]; if (g.endsWith("_url")){ const alt = g.slice(0, -4); if (alt in d) return d[alt]; } return null; } function formatValue(tok, v){ if (v == null) return ""; const key = norm(String(tok)); if (key === "unit_price"){ const num = parseFloat(String(v).replace(/[^\d.-]/g, "")); if (!isNaN(num)) return num.toFixed(2); } return v; } function replaceTextIn(root, d){ const rx = /\{\{\s*([^}]+?)\s*\}\}/g; const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null); const nodes = []; while (w.nextNode()) nodes.push(w.currentNode); nodes.forEach((n) => { const s = n.nodeValue; if (!s || !rx.test(s)) return; n.nodeValue = s.replace(rx, (_, tok) => { let v = lookup(d, tok); v = formatValue(tok, v); return v == null ? "" : String(v); }); }); } function replaceAttrsIn(root, d){ root.querySelectorAll('[src*="{{"],[href*="{{"],[data-src*="{{"]').forEach((el) => { ["src","href","data-src"].forEach((attr) => { const val = el.getAttribute(attr); if (!val || val.indexOf("{{") < 0) return; const newVal = val.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_, tok) => { const v = lookup(d, tok); return v == null ? "" : String(v); }); el.setAttribute(attr, newVal); }); }); } function isEmpty(v){ if (v === null || v === undefined) return true; const s = String(v).trim().toLowerCase(); return s === "" || s === "null" || s === "n/a"; } function setVisible(el, show){ if (!el) return; el.classList[show ? "remove" : "add"]("d-none"); } // ---------- CSV ---------- function esc(v){ v = String(v == null ? "" : v); return /[",\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; } window.makeCsvDownload = function(el){ const d = window.__PRC_MODAL_PAYLOAD__ || {}; const lat = d.lat || d.Lat || ""; const lon = d.lon || d.Lon || ""; const row = { SaleId: d.sale_id || d.SaleID || "", Address: d.lot_apt || d["Lot_Apt"] || d["Lot/Apt"] || "", Dev_Ward_Cond: d.neighborhood || d.Localizacion || "", Municipality: d.municipality || d.Municipio || "", Zip_Code: d.zip || d.Zip || "", GPS: lat && lon ? (lat + ", " + lon) : "", CRIM: d.crim || d.Crim || "", Sale_Price: d.sale_price || d.Sale_Price || "", Dom: d.dom || d.DOM || "", Mortgage: d.mortgage || d.Hipoteca || "", Financing_Type: d.financing_type || d.Financing_Type || "", Concesions: d.concession || d.Concesions || d.Concessions || "", Sale_Date: d.sale_date || d.Fecha || "", Hoa: d.hoa || d.HOA || "", Design_Type: d.design_type || d.Design || "", Quality: d.quality || d.Quality || "", Condition: d.condition || d.Condition || "", Age: d.age || d.Age || "", Rooms: d.rooms || d.Rooms || "", Bedrooms: d.bedrooms || d.Bedrooms || "", Baths: d.baths || d.Baths || "", Gla: d.sqf || d.Sqf || d.gla || d.GLA || "", Carport: d.carport || d.Carport || "", Porch: d.porch || d.Porch || "", Improvements_1: d.improvements_1 || "", Improvements_2: d.improvements_2 || "", Swp: d.swp || d.SWP || "" }; const headers = Object.keys(row); const values = headers.map(k => esc(row[k])); const csv = "\uFEFF" + headers.join(",") + "\n" + values.join(","); const blob = new Blob([csv], { type:"text/csv;charset=utf-8;" }); const url = (window.URL || window.webkitURL).createObjectURL(blob); el.setAttribute("href", url); el.setAttribute("download", (row.SaleId || "record") + ".csv"); setTimeout(()=> (window.URL || window.webkitURL).revokeObjectURL(url), 4000); return true; }; // ---------- map loaders (unchanged) ---------- function attachLoader(iframe){ if (!iframe || iframe.dataset.loaderAttached === "1") return; iframe.dataset.loaderAttached = "1"; const wrap = document.createElement("div"); wrap.className = "embed-wrap"; iframe.parentNode.insertBefore(wrap, iframe); wrap.appendChild(iframe); const overlay = document.createElement("div"); overlay.className = "loading-overlay"; overlay.innerHTML = '' + "Loading…"; wrap.appendChild(overlay); let cleared = false; function clearOverlay(){ if (cleared) return; cleared = true; overlay.classList.add("fade-out"); setTimeout(()=>overlay.remove(), 250); } iframe.addEventListener("load", clearOverlay, { once:true }); } function initMaps(root, d){ const lat = lookup(d, "Lat"); const lon = lookup(d, "Lon"); const gpsChip = root.querySelector("#chip-gps"); const mapsCard = root.querySelector("#card-maps"); if (isEmpty(lat) || isEmpty(lon)){ if (gpsChip) gpsChip.remove(); if (mapsCard) mapsCard.classList.add("map-empty"); }else{ if (mapsCard) mapsCard.classList.remove("map-empty"); } const activePane = root.querySelector(".tab-pane.active"); if (activePane){ activePane.querySelectorAll("iframe[data-src]").forEach((iframe)=>{ const src = iframe.getAttribute("src"); if (!src || src === "about:blank"){ iframe.setAttribute("src", iframe.getAttribute("data-src")); attachLoader(iframe); } }); } const tabEl = root.querySelector("#mapTab"); if (tabEl && !tabEl.dataset.prcTabsInited){ tabEl.dataset.prcTabsInited = "1"; tabEl.addEventListener("shown.bs.tab", (e)=>{ const btn = e.target; const targetSel = btn.getAttribute("data-bs-target"); if (!targetSel) return; const pane = root.querySelector(targetSel); if (!pane) return; pane.querySelectorAll("iframe.map-embed").forEach((iframe)=>{ const src = (iframe.getAttribute("src")||"").trim().toLowerCase(); const ds = (iframe.getAttribute("data-src")||"").trim(); if ((src==="" || src==="about:blank") && ds){ iframe.setAttribute("src", ds); attachLoader(iframe); } }); }); } } function updatePhoto(root, d){ const img = root.querySelector("#photoImg"); const note = root.querySelector("#photoNote"); if (!img || !note) return; const PLACEHOLDER = "https://datasador.s3.amazonaws.com/comparables/no_image.png"; const photo = lookup(d, "photo_url") || lookup(d, "PhotoURL") || lookup(d, "Photo Url") || lookup(d, "Photo_URL") || ""; const clean = String(photo || "").trim().replace(/^"+|"+$/g, ""); img.onerror = null; if (!clean || clean.toLowerCase()==="null" || clean.toLowerCase()==="n/a"){ img.src = PLACEHOLDER; note.textContent = "No photo available (showing placeholder)"; }else{ img.src = clean; note.textContent = "Photos are for reference only and may not reflect current condition."; img.onerror = () => { img.onerror = null; img.src = PLACEHOLDER; note.textContent = "No photo available (showing placeholder)"; }; } } function applyRules(root, d){ const salesDeed = lookup(d, "SalesDeed_URL") || lookup(d, "sales_deed_url"); const mortDeed = lookup(d, "MortgageDeed_URL") || lookup(d, "mortgage_deed_url"); setVisible(root.querySelector("#btn-sales-deed"), !isEmpty(salesDeed)); setVisible(root.querySelector("#btn-mortgage-deed"), !isEmpty(mortDeed)); const deedsCard = root.querySelector("#card-deeds"); if (isEmpty(salesDeed) && isEmpty(mortDeed) && deedsCard) deedsCard.classList.add("d-none"); const typeVal = String(lookup(d,"Type") || lookup(d,"type") || "").trim().toLowerCase(); setVisible(root.querySelector("#card-details-table-dev-ward"), typeVal==="development" || typeVal==="ward"); setVisible(root.querySelector("#card-details-table-condo"), typeVal==="condominium"); setVisible(root.querySelector("#card-details-table-vacant-lot"), typeVal==="vacant lot"); setVisible(root.querySelector("#card-details-table-multifamily"), typeVal==="multifamily"); setVisible(root.querySelector("#card-improvements"), typeVal !== "vacant lot"); const btnFiles = root.querySelector("#btn-files"); let photoStatus = lookup(d,"foto_status") || lookup(d,"Photo_Status") || lookup(d,"Photo Status") || ""; photoStatus = String(photoStatus).trim().toLowerCase(); setVisible(btnFiles, photoStatus === "yes"); const lat = lookup(d, "Lat"); const lon = lookup(d, "Lon"); setVisible(root.querySelector("#btn-firmette"), !isEmpty(lat) && !isEmpty(lon)); setVisible(root.querySelector("#download-1004"), typeVal==="development" || typeVal==="ward"); setVisible(root.querySelector("#download-1073"), typeVal==="condominium"); setVisible(root.querySelector("#download-gpland"), typeVal==="vacant lot"); } // ========================================================= // ✅ NEW: iframe + postMessage payload loader (NO fetch, NO API) // ========================================================= async function fetchPayload(sid, timeoutMs=8000){ const iframe = document.getElementById("detailsBridge"); if (!iframe) return { _error: "bridge_iframe_missing" }; const caspioUrl = "https://c1dcj811.caspio.com/dp/6b06a0002013fd9ac78449c7b2e1?sid=" + encodeURIComponent(sid) + "&cbembed=1"; return await new Promise((resolve) => { let done = false; const timer = setTimeout(() => { if (done) return; done = true; cleanup(); resolve({ _error: "iframe_timeout", url: caspioUrl }); }, timeoutMs); function cleanup(){ clearTimeout(timer); window.removeEventListener("message", onMsg); } function onMsg(ev){ // accept only caspio.com origins if (!ev || typeof ev.origin !== "string" || !ev.origin.includes("caspio.com")) return; const msg = ev.data || {}; if (!msg || msg.type !== "PRC_DETAILS") return; if (msg.sid && String(msg.sid) !== String(sid)) return; if (done) return; done = true; cleanup(); if (!msg.ok) { resolve({ _error: msg.error || "iframe_error", message: msg.message || "", url: caspioUrl }); return; } resolve(msg.data || null); } window.addEventListener("message", onMsg); // start loading caspio page in iframe iframe.src = caspioUrl; }); } function renderIntoModal(payload){ const tpl = document.getElementById("detailsTemplate"); const mount = document.getElementById("detailsMount"); if (!tpl || !mount) return false; mount.innerHTML = ""; mount.appendChild(tpl.content.cloneNode(true)); replaceTextIn(mount, payload); replaceAttrsIn(mount, payload); updatePhoto(mount, payload); initMaps(mount, payload); applyRules(mount, payload); window.__PRC_MODAL_PAYLOAD__ = payload; return true; } document.addEventListener("DOMContentLoaded", function(){ const modalEl = document.getElementById("detailsModal"); const loader = document.getElementById("detailsLoader"); const mount = document.getElementById("detailsMount"); const bridge = document.getElementById("detailsBridge"); if (!modalEl || !loader || !mount || !bridge) return; let lastSid = ""; const modal = (window.bootstrap && bootstrap.Modal) ? bootstrap.Modal.getOrCreateInstance(modalEl) : null; document.addEventListener("click", async function(e){ const link = e.target.closest("a.open-details"); if (!link) return; e.preventDefault(); const sid = getSidFromUrl(link.href); if (!sid) return; lastSid = sid; const titleEl = modalEl.querySelector(".modal-title"); const title = link.dataset.title || "Details"; if (titleEl) titleEl.textContent = title; if (modal) modal.show(); loader.classList.remove("d-none"); mount.innerHTML = ""; const d = await fetchPayload(sid, 9000); if (!d || d._error){ loader.classList.add("d-none"); const msg = (d && d._error) ? d._error : "unknown"; const extra = d && d.message ? `
${String(d.message)}
` : ""; mount.innerHTML = `
Unable to load details.
Error: ${msg}
${extra}
`; return; } if (sid !== lastSid) return; renderIntoModal(d); loader.classList.add("d-none"); }); modalEl.addEventListener("hidden.bs.modal", function(){ lastSid = ""; loader.classList.add("d-none"); mount.innerHTML = ""; window.__PRC_MODAL_PAYLOAD__ = null; // clear bridge iframe so it never shows old record / keeps session bridge.src = "about:blank"; }); }); })();