Skip to main content

Overview

The property display system consists of several specialized rendering functions that populate different sections of the property page:
  • Badges: Key property attributes displayed as pills in the header
  • Details: Structured list of property specifications
  • Features: Property characteristics displayed as tags
  • Services: Estimated monthly service costs

Header Section

From index.html:72-92, the header displays the property title, location, badges, and price:
<header class="relative bg-navy-deep pt-12 pb-32 px-6 lg:px-12 overflow-hidden">
  <div class="absolute top-0 right-0 w-1/2 h-full bg-gradient-to-l from-primary/20 to-transparent pointer-events-none"></div>
  <div class="max-w-7xl mx-auto relative z-10">
    <div class="flex flex-col md:flex-row justify-between items-start gap-6">
      <div>
        <span class="text-primary font-semibold tracking-wider text-sm uppercase mb-2 block">Ficha Inmobiliaria</span>
        <h1 id="property-title" class="text-4xl md:text-5xl font-display font-extrabold text-white mb-4">Cargando inmueble...</h1>
        <div class="flex items-center gap-2 text-slate-300">
          <span class="material-icons-outlined text-sm">location_on</span>
          <span id="property-location">--</span>
        </div>
        <div id="property-badges" class="flex flex-wrap gap-2 mt-6"></div>
      </div>
      <div class="bg-white/10 backdrop-blur-lg border border-white/20 p-6 rounded-2xl text-white min-w-[240px]">
        <p class="text-slate-300 text-sm mb-1 uppercase tracking-tight">Precio de venta</p>
        <p id="property-price" class="text-3xl font-display font-bold">--</p>
        <p id="property-price-prev" class="text-slate-400 text-sm line-through mt-1"></p>
      </div>
    </div>
  </div>
</header>

renderBadges()

From app.js:214-235, this function creates badge elements for key property attributes:
const buildBadge = (text) => {
  const badge = document.createElement("span");
  badge.className = "px-4 py-1.5 rounded-full bg-white/10 text-white text-sm font-medium backdrop-blur-md";
  badge.textContent = text;
  return badge;
};

const renderBadges = (property) => {
  const container = $("#property-badges");
  container.innerHTML = "";

  const badges = [];
  if (property.tipo_inmueble) badges.push(property.tipo_inmueble);
  if (property.area_privada) badges.push(`${formatNumber(property.area_privada)} m²`);
  if (property.estrato) badges.push(`Estrato ${property.estrato}`);
  if (property.estado) badges.push(property.estado);
  if (property.ciudad) badges.push(property.ciudad);

  badges.forEach((text) => container.appendChild(buildBadge(text)));
};

Badge Attributes

Property type (e.g., “Apartamento”, “Casa”)
Private area in square meters, formatted with thousand separators
Socioeconomic stratum (1-6 in Colombian context)
Property state (e.g., “Nuevo”, “Usado”)
City name

Details Section

From index.html:154-160:
<section class="bg-white dark:bg-slate-800 p-8 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-700">
  <h3 class="text-xl font-display font-bold mb-4 flex items-center gap-2">
    <span class="material-icons-outlined text-primary">info</span>
    Detalles principales
  </h3>
  <div id="property-details" class="space-y-4"></div>
</section>

renderDetails()

From app.js:394-429, this function populates the property details grid:
const renderDetails = (property) => {
  const detailsList = $("#property-details");
  detailsList.innerHTML = "";

  const areaPrivada = property.area_privada ? `${formatNumber(property.area_privada)} m²` : null;
  const areaConstruida = property.area_construida
    ? `${formatNumber(property.area_construida)} m²`
    : null;

  const details = [
    ["Área privada", areaPrivada],
    ["Área construida", areaConstruida],
    ["Parqueaderos", property.parqueaderos],
    ["Baños", property.banos],
    ["Habitaciones", property.habitaciones],
    ["Antigüedad", property.antiguedad],
    ["Tipo", property.tipo_inmueble],
    ["Estrato", property.estrato],
  ];

  details
    .filter(([, value]) => value && value !== "--")
    .forEach(([label, value]) => {
      const div = document.createElement("div");
      div.className = "flex justify-between items-center py-2 border-b border-slate-100 dark:border-slate-700/50";
      const span = document.createElement("span");
      span.className = "text-slate-500 text-sm";
      span.textContent = label;
      const strong = document.createElement("span");
      strong.className = "font-bold";
      strong.textContent = value;
      div.appendChild(span);
      div.appendChild(strong);
      detailsList.appendChild(div);
    });
};
Only non-empty details are displayed. Fields with null, undefined, or ”—” values are automatically filtered out.

Features Section

From index.html:165-171:
<section class="bg-white dark:bg-slate-800 p-8 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-700">
  <h3 class="text-xl font-display font-bold mb-6 flex items-center gap-2">
    <span class="material-icons-outlined text-primary">auto_awesome</span>
    Características
  </h3>
  <div id="property-features" class="flex flex-wrap gap-3"></div>
</section>

renderFeatures()

From app.js:456-471, this function displays property characteristics as styled tags:
const renderFeatures = (features) => {
  const container = $("#property-features");
  container.innerHTML = "";

  if (!features || !features.length) {
    container.innerHTML = "<span class=\"text-slate-500 text-sm\">No hay características disponibles.</span>";
    return;
  }

  features.forEach((feature) => {
    const span = document.createElement("span");
    span.className = "bg-slate-50 dark:bg-slate-700/50 px-4 py-2 rounded-lg text-sm text-slate-700 dark:text-slate-300 border border-slate-100 dark:border-slate-600";
    span.textContent = feature;
    container.appendChild(span);
  });
};

Services Section

From index.html:172-178:
<section class="bg-white dark:bg-slate-800 p-8 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-700">
  <h3 class="text-xl font-display font-bold mb-6 flex items-center gap-2">
    <span class="material-icons-outlined text-primary">receipt_long</span>
    Servicios estimados
  </h3>
  <div id="property-services" class="space-y-2"></div>
</section>

renderServices()

From app.js:473-495, this function displays service costs:
const renderServices = (services) => {
  const container = $("#property-services");
  container.innerHTML = "";

  if (!services || !services.length) {
    container.innerHTML = "<p class=\"text-slate-500 text-sm\">No hay servicios registrados.</p>";
    return;
  }

  services.forEach((service) => {
    const item = document.createElement("div");
    item.className = "flex justify-between items-center p-3 rounded-xl bg-slate-50 dark:bg-slate-700/30";
    const label = document.createElement("span");
    label.className = "text-sm font-medium";
    label.textContent = service.nombre || service.servicio || "Servicio";
    const value = document.createElement("span");
    value.className = "text-sm font-bold";
    value.textContent = service.valor ? formatCurrency(service.valor) : "--";
    item.appendChild(label);
    item.appendChild(value);
    container.appendChild(item);
  });
};

Currency and Number Formatting

From app.js:15-27, utility functions for formatting:
const formatCurrency = (value) => {
  if (!value && value !== 0) return "--";
  return new Intl.NumberFormat("es-CO", {
    style: "currency",
    currency: "COP",
    maximumFractionDigits: 0,
  }).format(value);
};

const formatNumber = (value) => {
  if (!value && value !== 0) return "--";
  return new Intl.NumberFormat("es-CO").format(value);
};

Complete Page Population

From app.js:613-663, the main function that coordinates all rendering:
const populatePage = (data) => {
  const property = normalizeProperty(data);

  $("#property-title").textContent = property.titulo || "Inmueble en venta";
  $("#property-location").textContent = [property.barrio, property.ciudad, property.departamento]
    .filter(Boolean)
    .join(" · ");

  $("#property-price").textContent = formatCurrency(property.precio);
  const pricePrev = $("#property-price-prev");
  if (pricePrev) {
    pricePrev.textContent = property.precio_anterior
      ? formatCurrency(property.precio_anterior)
      : "";
  }

  $("#property-description").textContent = property.descripcion || "Sin descripción";

  // Handle 360 tour button
  const btn360Container = $("#btn-360-container");
  const tour360 = $("#property-360");
  const tour360Btn = $("#property-360-btn");
  
  if (property.url_360) {
    if (btn360Container) {
      btn360Container.style.display = "flex";
      btn360Container.dataset.has360 = "true";
    }
    if (tour360) tour360.href = property.url_360;
    if (tour360Btn) {
      tour360Btn.href = property.url_360;
      tour360Btn.style.display = "inline-flex";
    }
  } else {
    if (btn360Container) {
      btn360Container.style.display = "none";
      btn360Container.dataset.has360 = "false";
    }
    if (tour360Btn) tour360Btn.style.display = "none";
  }

  renderBadges(property);
  renderGallery(property.images || []);
  renderDetails(property);
  renderDistribution(property);
  renderFeatures(property.caracteristicas_propiedad || []);
  renderServices(property.servicios || []);
  renderContact(property);
  renderMap(property);
};
All rendering functions are designed to handle missing data gracefully, displaying fallback messages or filtering out empty fields automatically.

Build docs developers (and LLMs) love