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
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);
});
};
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.