homlity / sdk-smarthome
SDK oficial de PHP de Homlity para consultar inventario inmobiliario (inmuebles, ubicaciones y tipos) y registrar leads en el CRM.
Requires
- php: >=7.4
- curl/curl: ^2.5
Requires (Dev)
- phpunit/phpunit: ^10.1
README
Homlity SDK · Smart Inmobiliario
SDK oficial de PHP para integrar el inventario inmobiliario y el CRM de Homlity en cualquier sitio o aplicación.
Sitio principal · Portal de desarrolladores · Packagist · GitHub
Tabla de contenido
- ¿Qué es este SDK?
- ¿Para qué sirve?
- Requisitos
- Instalación
- Credenciales
- Inicio rápido
- Referencia de la API del SDK
- Modelos de datos
- Catálogos y códigos
- Ejemplos completos
- Integración con frameworks
- Buenas prácticas
- Limitaciones y advertencias
- Endpoints REST subyacentes
- Solución de problemas
- Pruebas
- Versionado y changelog
- Soporte
¿Qué es este SDK?
homlity/sdk-smarthome es una librería PHP sin frameworks que envuelve la API REST del
integrador inmobiliario de Homlity (https://api.smart-home.com.co). En lugar de armar URLs,
manejar cURL y navegar arrays asociativos en inglés, trabajas con objetos PHP con métodos en
español:
$propiedades = $sdk->obtenerPropiedades(); echo $propiedades[0]->getNombre(); // "VENTA DE OFICINA EN CENTRO COMERCIAL PRIMAVERA" echo $propiedades[0]->getValorVenta(); // 650000000.00 echo $propiedades[0]->getCiudad(); // "VILLAVICENCIO"
El SDK resuelve tres cosas por ti:
| Sin el SDK | Con el SDK |
|---|---|
Construir URLs con companyCode / projectCode en cada llamada |
Configuras las credenciales una vez en el builder |
Manejar cURL, headers Accept y compresión gzip a mano |
Ya viene configurado en cada petición |
Recorrer arrays con claves en inglés (unitCategoryDescription, mainImageURL…) |
Objetos tipados con getters legibles en español |
¿Para qué sirve?
Casos de uso reales que este SDK habilita:
- Portal inmobiliario propio. Publicar en tu sitio web el inventario que la inmobiliaria administra en Homlity, siempre sincronizado, sin duplicar la carga de datos.
- Ficha de inmueble. Página de detalle con galería de fotos, video, tour virtual 360°, características, mapa (latitud/longitud) y datos del asesor.
- Buscador y filtros. Construir filtros por ciudad, localidad, barrio, tipo de inmueble, gestión (venta/arriendo), precio, alcobas o baños usando los catálogos que expone la API.
- Captación de leads hacia el CRM. Enviar los formularios de contacto de tu web directamente al CRM de Homlity, asociados al inmueble específico que generó el interés.
- Landing pages y micrositios de proyectos que consumen un subconjunto del inventario.
- Sindicación / feeds hacia portales de terceros o alimentación de un índice de búsqueda.
Requisitos
| Requisito | Versión |
|---|---|
| PHP | >= 7.4 (probado hasta PHP 8.3) |
| Extensión | ext-curl habilitada |
| Extensión | ext-json habilitada |
| Extensión (recomendada) | ext-zlib para descompresión gzip |
| Dependencia | curl/curl ^2.5 (se instala automáticamente) |
| Red | Salida HTTPS hacia api.smart-home.com.co |
Instalación
composer require homlity/sdk-smarthome
⚠️ Nota de versión importante
La última etiqueta publicada en Packagist es v2.2.1, que expone una API distinta y
anterior (SDKSmartInmoBuilder::build() + SmartHomeFacade). Toda esta documentación
describe la API actual de la rama main (SDKSmartHomeBuilder), que aún no tiene etiqueta.
Mientras se publica la versión estable, instala la rama de desarrollo:
composer require homlity/sdk-smarthome:dev-main
O fijando el commit para builds reproducibles:
{
"require": {
"homlity/sdk-smarthome": "dev-main#bda386f"
},
"minimum-stability": "dev",
"prefer-stable": true
}
Para el mantenedor: etiquetar
v3.0.0desdemainpublica esta API como versión estable y permitecomposer require homlity/sdk-smarthome:^3.0.
Instalación sin Composer
spl_autoload_register(function ($class) { $prefix = 'Codwelt\\SDK\\SmartHome\\'; $baseDir = __DIR__ . '/sdk-smartinmobiliario/src/'; if (strncmp($prefix, $class, strlen($prefix)) !== 0) { return; } $file = $baseDir . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php'; if (file_exists($file)) { require $file; } });
Necesitarás instalar también
curl/curlmanualmente.
Namespace
El paquete se llama homlity/sdk-smarthome, pero el namespace PHP sigue siendo
Codwelt\SDK\SmartHome\ por compatibilidad hacia atrás. No lo confundas:
use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder;
Credenciales
El SDK necesita hasta tres identificadores, que entrega el equipo de Homlity al habilitar la integración:
| Credencial | Método | Se usa en | Ejemplo |
|---|---|---|---|
| Company Code | setCompanyCode() |
Todas las consultas de inventario | 695704c3 |
| Project Code | setProjectCode() |
Todas las consultas de inventario | 5bec54a5 |
| Project ID | setProjectId() |
Únicamente agregarLead() |
-IxD-MeYCo32TRLf… |
- Company Code identifica a la inmobiliaria dentro de la plataforma.
- Project Code identifica el proyecto o bolsa de inventario que quieres exponer.
- Project ID es el token largo del CRM al que se asocian los leads.
Solicítalos en el portal de desarrolladores de Homlity.
Seguridad: aunque el Company Code y el Project Code viajan en la URL y solo dan acceso de lectura al inventario que ya es público, nunca expongas el
projectIden JavaScript del lado del cliente. Las llamadas del SDK deben ejecutarse siempre en el servidor.
Guárdalas fuera del código:
$sdk = new SDKSmartHomeBuilder(); $sdk->setCompanyCode(getenv('HOMLITY_COMPANY_CODE')); $sdk->setProjectCode(getenv('HOMLITY_PROJECT_CODE')); $sdk->setProjectId(getenv('HOMLITY_PROJECT_ID'));
Inicio rápido
<?php require __DIR__ . '/vendor/autoload.php'; use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder; $sdk = new SDKSmartHomeBuilder(); $sdk->setCompanyCode('695704c3'); $sdk->setProjectCode('5bec54a5'); $sdk->setProjectId('-IxD-MeYCo32TRLfUCbfBpdb1rxYAsdHPqc1GL0Mc9CAdWenV8FW/V6iQAgBl-Zh'); // 1. Listado de inmuebles $propiedades = $sdk->obtenerPropiedades(); // 2. Detalle de uno $propiedad = $sdk->obtenerPropiedad($propiedades[0]->getCodigo()); // 3. Catálogo de ubicaciones $ubicaciones = $sdk->obtenerUbicaciones(); // 4. Catálogo de tipos de inmueble $tipos = $sdk->obtenerTipoPropiedades(); // 5. Registrar un lead en el CRM $idLead = $sdk->agregarLead([ 'nombre' => 'Sergio Wiesner', 'correo' => 'webdev@homlity.com', 'telefono' => '+573506573588', 'web' => 'mi-inmobiliaria.com', 'commentario' => 'Quiero agendar una visita', 'codigo' => $propiedad->getModuleId(), ]);
Referencia de la API del SDK
SDKSmartHomeBuilder
Clase de entrada. Codwelt\SDK\SmartHome\SDKSmartHomeBuilder.
__construct(string $endpoint = "https://api.smart-home.com.co")
Crea el cliente. Solo necesitas pasar $endpoint si Homlity te asignó un entorno alterno
(staging, on-premise):
$sdk = new SDKSmartHomeBuilder(); // producción $sdk = new SDKSmartHomeBuilder('https://staging.ejemplo.co'); // entorno alterno
No incluyas la barra final: el SDK concatena las rutas directamente.
setCompanyCode(string $companyCode): void
Define el código de la inmobiliaria. Obligatorio para todas las consultas de inventario.
setProjectCode(string $projectCode): void
Define el código del proyecto. Obligatorio para todas las consultas de inventario.
setProjectId(string $projectId): void
Define el identificador del proyecto en el CRM. Solo se usa en agregarLead(); si no vas a
enviar leads puedes omitirlo.
obtenerPropiedades(): InmueblePreview[]
Devuelve todos los inmuebles del proyecto como un array de
InmueblePreview.
$propiedades = $sdk->obtenerPropiedades(); foreach ($propiedades as $p) { printf("%s — %s — $%s\n", $p->getCodigo(), $p->getNombre(), number_format($p->getValorVenta())); }
- Devuelve
[](array vacío) si la petición falla o no hay inventario. No lanza excepciones. - La respuesta no está paginada desde el cliente: la API devuelve hasta 10.000 registros por llamada. Si tu inventario es grande, cachea el resultado.
obtenerPropiedad(string $unitCode): ?InmuebleDetail
Devuelve el detalle de un inmueble a partir de su código (getCodigo() del listado).
$propiedad = $sdk->obtenerPropiedad('20230022'); if ($propiedad === null || $propiedad->getCodigo() === null) { http_response_code(404); exit('Inmueble no disponible'); }
- El parámetro es el
codedel inmueble (ej."20230022"), no elmoduleId. - Devuelve
nullsolo si la petición HTTP falla (red caída, endpoint inalcanzable). - 🔴 Un código inexistente NO devuelve
null. La API responde200conunits: nully el SDK entrega unInmuebleDetailvacío: el objeto existe, pero su array interno esnull, así que cada getter emite el avisoTrying to access array offset on value of type nully devuelvenull. Por eso comprobar=== nullno basta: valida además un campo obligatorio comogetCodigo(), tal como en el ejemplo de arriba. - El propio
obtenerPropiedad()emite ese mismo aviso desde dentro del SDK antes de devolverte el objeto. Si quieres logs limpios, llámalo como@$sdk->obtenerPropiedad($codigo).
obtenerUbicaciones(): ?Ubicaciones
Devuelve el árbol geográfico del inventario (países, ciudades, localidades, barrios y zonas), con el conteo de inmuebles de cada nodo — ideal para construir filtros.
$ubicaciones = $sdk->obtenerUbicaciones(); foreach ($ubicaciones->getCiudades() as $ciudad) { echo $ciudad->getNombre() . ' (' . $ciudad->getCantidad() . ")\n"; } // VILLAVICENCIO (12) // ACACIAS (2)
obtenerTipoPropiedades(): ?TipoPropiedad[]
Devuelve los tipos de inmueble presentes en el inventario, con su conteo.
foreach ($sdk->obtenerTipoPropiedades() ?? [] as $tipo) { echo "{$tipo->getCodigo()} · {$tipo->getNombre()} ({$tipo->getCantidad()})\n"; } // 1 · Apartamento (6) // 2 · Casa (6) // 4 · Oficina (2)
⚠️ Este método devuelve
null(no[]) cuando el proyecto no tiene tipos, y en ese caso además emite el avisoforeach() argument must be of type array|object, null givendesde dentro del propio SDK. Usa siempre?? []antes de iterar.
agregarLead(array $datos): ?string
Registra un contacto en el CRM de Homlity y lo asocia a un inmueble.
$respuesta = $sdk->agregarLead([ 'nombre' => 'Ana Ramírez', 'correo' => 'ana@ejemplo.com', 'telefono' => '+573001112233', 'web' => 'mi-inmobiliaria.com', 'commentario' => 'Me interesa agendar una visita el sábado', 'codigo' => $propiedad->getModuleId(), ]);
Claves del array $datos — todas son obligatorias:
| Clave | Se envía como | Descripción |
|---|---|---|
nombre |
first_name |
Nombre del interesado |
correo |
email |
Correo electrónico |
telefono |
mobile_number |
Celular, preferiblemente en formato E.164 (+57…) |
web |
origin |
Origen del lead: dominio, campaña o landing |
commentario |
comment |
Mensaje del interesado |
codigo |
moduleId |
getModuleId() del inmueble, no getCodigo() |
🔴 Dos detalles que causan la mayoría de los errores:
- La clave se escribe
commentario(con doble m), tal como la espera el SDK. Si la omites, PHP emitirá un warning de clave indefinida y el comentario viajará vacío.codigodebe ser elmoduleId(un UUID como35f98252-6db1-43d6-bd6b-18752b883c41), no el código visible del inmueble.Además,
setProjectId()debe haberse llamado antes; de lo contrario el lead llegará sin proyecto asociado.
Devuelve el cuerpo crudo de la respuesta del CRM (string) o null si la petición falló.
Comprueba siempre !== null antes de mostrar un mensaje de éxito al usuario.
Modelos de datos
Todos los modelos viven en Codwelt\SDK\SmartHome\InfraStructure\ y son objetos de solo
lectura construidos a partir de la respuesta JSON.
InmueblePreview / InmuebleDetail
InmuebleDetail extiende InmueblePreview sin añadir métodos: ambos exponen exactamente la
misma interfaz. La diferencia está en el origen de los datos, no en la clase.
Identificación
| Método | Campo JSON | Tipo | Notas |
|---|---|---|---|
getCodigo() |
code |
string |
Código visible del inmueble. Úsalo en obtenerPropiedad() |
getModuleId() |
moduleId |
string (UUID) |
Identificador interno. Úsalo en agregarLead() |
getNombre() |
name |
string |
Título comercial |
getDescripcion() |
description |
string |
Descripción larga, puede traer saltos de línea |
getMls() |
mls |
array|null |
Códigos MLS asociados |
Áreas
| Método | Campo JSON | Tipo |
|---|---|---|
getArea() |
area |
float — área construida (m²) |
getAreaPrivada() |
privateArea |
float — área privada (m²) |
getAreaLote() |
lotArea |
float — área de lote (m²) |
Precios
| Método | Campo JSON | Tipo | Notas |
|---|---|---|---|
getValorVenta() |
price |
float |
Precio de venta |
getvalorArriendo() |
monthlyFee |
float |
Canon mensual. ⚠️ La v va en minúscula |
getMantenimiento() |
maintenanceFee |
float |
Administración |
getInmpuesto() |
tax |
float |
Impuesto. ⚠️ Nombre con errata: Inmpuesto |
getTieneImpuestos() |
hasTax |
bool |
|
getTotalMensualidad() |
totalMonthlyFee |
float |
Canon + administración + impuestos |
Gestión y tipo
| Método | Campo JSON | Tipo | Notas |
|---|---|---|---|
getGestionId() |
unitCategory |
int |
Ver catálogo de gestión |
getGestion() |
unitCategoryDescription |
string |
"Venta", "Arriendo"… |
getTipoUnidadId() |
unitType |
int |
Ver catálogo de tipos |
getTipoUnidad() |
unitTypeDescription |
string |
"Apartamento", "Casa"… |
Distribución
| Método | Campo JSON | Tipo |
|---|---|---|
getAlcobas() |
bedroom |
float |
getBanos() |
bathrooms |
float |
getGarajes() |
garages |
int |
getEstrato() |
strata |
string |
getNivel() |
floor |
int — piso |
getAnoConstruccion() |
yearBuild |
int — 0 si no se registró |
Ubicación
| Método | Campo JSON | Tipo | Notas |
|---|---|---|---|
getDireccion() |
address |
string |
|
getLatitud() |
latitude |
float |
Para mapas |
getLongitud() |
longitude |
float |
Para mapas |
getCiudad() / getCiudadId() |
city / cityId |
string |
|
getLocalidad() / getLocalidadId() |
locality / localityId |
?string |
Suele venir null |
getBarrio() / getBarrioId() |
neighborhood / neighborhoodId |
?string |
neighborhoodId puede ser null aunque haya nombre |
getZona() / getZonaId() |
zone / zoneId |
?string |
Frecuentemente null |
Multimedia y relaciones
| Método | Campo JSON | Devuelve | Notas |
|---|---|---|---|
getImagenDestacada() |
mainImageURL |
?string |
Puede ser null |
getFotos() |
images |
Fotos[] |
⚠️ Falla si images es null |
getCaracteristicas() |
features |
Caracteristicas[] |
⚠️ Falla si features es null |
getAsesor() |
seller |
Asesor |
⚠️ En inventarios reales,
imagesyfeaturesllegannullcon frecuencia (en el inventario de demostración: 3 de 15 y 4 de 15 respectivamente). Como los métodos hacenforeachdirecto sobre el campo, PHP 8 emite el avisoforeach() argument must be of type array|object, null giveny el método devuelve[].No es fatal por sí solo, pero sí es ruido en los logs — y en Laravel, Symfony o cualquier proyecto con un
set_error_handlerque convierte los avisos en excepciones, sí se convierte en unaErrorException. Usa accesores seguros que cubran ambos escenarios:function fotosSeguras(InmueblePreview $inmueble): array { try { $fotos = @$inmueble->getFotos(); // silencia el aviso de foreach sobre null return is_array($fotos) ? $fotos : []; } catch (\Throwable $e) { // frameworks que convierten avisos en excepciones return []; } }
Fotos
Representa una pieza multimedia: foto, video o tour virtual.
| Método | Campo JSON | Notas |
|---|---|---|
getNombre() |
title |
Si viene vacío, cae al nombre del inmueble — útil como alt |
getTipoId() |
mediaType |
Ver catálogo multimedia |
getUbicacion() |
source |
URL absoluta del recurso |
getOrden() |
sequence |
Entero para ordenar la galería |
usort($fotos, fn($a, $b) => $a->getOrden() <=> $b->getOrden()); foreach ($fotos as $foto) { if ($foto->getTipoId() === 1) { echo '<img src="' . htmlspecialchars($foto->getUbicacion()) . '" alt="' . htmlspecialchars($foto->getNombre()) . '">'; } }
Caracteristicas
| Método | Campo JSON |
|---|---|
getNombre() |
name — ej. "BALCON", "ILUMINACION" |
Asesor
Datos de contacto del comercial responsable del inmueble.
| Método | Campo JSON |
|---|---|
getNombre() |
name |
getCelular() |
mobileNumber |
getEmail() |
email |
$asesor = $propiedad->getAsesor(); $wa = 'https://wa.me/57' . preg_replace('/\D/', '', $asesor->getCelular()); echo '<a href="' . $wa . '">Escribir a ' . htmlspecialchars($asesor->getNombre()) . '</a>';
Ubicaciones
Contenedor del árbol geográfico devuelto por obtenerUbicaciones().
| Método | Devuelve | Campo JSON |
|---|---|---|
getPaises() |
Paises[] |
countries |
getCiudades() |
Ciudades[] |
cities |
getLocalidades() |
Localidades[] |
localities |
getBarrios() |
Barrios[] |
neighborhoods |
getZonas() |
Paises[] |
zones — ⚠️ ver nota |
⚠️
getZonas()envuelve las zonas en la clasePaises, por lo quegetCodigo()leerácountryIdy nozoneId. En los proyectos de pruebazonesviene vacío, así que rara vez se nota; evita depender de este método hasta que se corrija.
Paises
| Método | Campo JSON |
|---|---|
getCodigo() |
countryId (UUID) |
getNombre() |
name |
getCantidad() |
count — inmuebles en el país |
Ciudades
| Método | Campo JSON |
|---|---|
getCodigo() |
cityId (UUID) |
getPaisId() |
countryId |
getNombre() |
name |
getCantidad() |
count |
Localidades
| Método | Campo JSON |
|---|---|
getCodigo() |
localityId (UUID) |
getCiudadId() |
cityId |
getNombre() |
name |
getCantidad() |
count |
Barrios
| Método | Campo JSON |
|---|---|
getCodigo() |
neighborhoodId (UUID) |
getCiudadId() |
cityId |
getPaisId() |
countryId |
getNombre() |
name |
getCantidad() |
count |
TipoPropiedad
| Método | Campo JSON |
|---|---|
getCodigo() |
code (int) |
getNombre() |
name |
getCantidad() |
count |
Catálogos y códigos
Gestión (unitCategory)
| Código | Descripción |
|---|---|
0 |
Sin Definir |
1 |
Arriendo |
2 |
Venta |
3 |
Arriendo y Venta |
Usa siempre
getGestion()para mostrar el texto: la API ya envía la descripción oficial.
Tipos de inmueble (unitType)
Los códigos son estables, pero el catálogo disponible varía por proyecto. Obtén siempre el
listado real con obtenerTipoPropiedades(). Valores observados:
| Código | Nombre |
|---|---|
0 |
Sin Definir |
1 |
Apartamento |
2 |
Casa |
4 |
Oficina |
23 |
Casa Campestre |
Tipo de multimedia (mediaType)
| Código | Contenido | Cómo renderizarlo |
|---|---|---|
1 |
Imagen | <img src="…"> |
3 |
Video (.mp4) |
<video controls src="…"> |
5 |
Tour virtual 360° (Matterport) | <iframe src="…" allowfullscreen> |
Ejemplos completos
En la carpeta examples/ encontrarás estos scripts listos para ejecutar.
1. Listado con tarjetas
<?php require __DIR__ . '/vendor/autoload.php'; use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder; $sdk = new SDKSmartHomeBuilder(); $sdk->setCompanyCode(getenv('HOMLITY_COMPANY_CODE')); $sdk->setProjectCode(getenv('HOMLITY_PROJECT_CODE')); $propiedades = $sdk->obtenerPropiedades(); if ($propiedades === []) { echo '<p>No hay inmuebles disponibles en este momento.</p>'; return; } foreach ($propiedades as $p) { $imagen = $p->getImagenDestacada() ?: '/img/placeholder.jpg'; $precio = $p->getGestionId() === 1 ? $p->getvalorArriendo() : $p->getValorVenta(); ?> <article class="card"> <img src="<?= htmlspecialchars($imagen) ?>" alt="<?= htmlspecialchars($p->getNombre()) ?>" loading="lazy"> <span class="badge"><?= htmlspecialchars($p->getGestion()) ?></span> <h3><?= htmlspecialchars($p->getNombre()) ?></h3> <p><?= htmlspecialchars($p->getTipoUnidad()) ?> · <?= htmlspecialchars($p->getBarrio() ?? $p->getCiudad()) ?></p> <p><strong>$<?= number_format($precio, 0, ',', '.') ?></strong></p> <ul> <li><?= (int) $p->getAlcobas() ?> alcobas</li> <li><?= (int) $p->getBanos() ?> baños</li> <li><?= (int) $p->getArea() ?> m²</li> </ul> <a href="/inmueble.php?codigo=<?= urlencode($p->getCodigo()) ?>">Ver detalle</a> </article> <?php }
2. Filtrar en memoria
La API devuelve el inventario completo, así que los filtros se aplican en PHP:
$filtrados = array_values(array_filter($propiedades, function ($p) { return $p->getGestionId() === 2 // solo venta && $p->getCiudadId() === '454cca01-1f8e-4c4e-91ba-24ce3a37f19f' && $p->getAlcobas() >= 3 && $p->getValorVenta() <= 500000000; })); // Ordenar por precio ascendente usort($filtrados, fn($a, $b) => $a->getValorVenta() <=> $b->getValorVenta()); // Paginar $pagina = max(1, (int) ($_GET['pagina'] ?? 1)); $porPagina = 12; $total = count($filtrados); $visibles = array_slice($filtrados, ($pagina - 1) * $porPagina, $porPagina); $paginas = (int) ceil($total / $porPagina);
3. Ficha de inmueble con galería
$codigo = $_GET['codigo'] ?? null; if (!$codigo) { http_response_code(400); exit('Falta el código del inmueble'); } // La @ silencia el aviso que el SDK emite cuando el código no existe $propiedad = @$sdk->obtenerPropiedad($codigo); // Doble validación: null (fallo de red) u objeto vacío (código inexistente) if ($propiedad === null || $propiedad->getCodigo() === null) { http_response_code(404); exit('Inmueble no encontrado'); } // Accesor seguro: `images` / `features` pueden venir null en la respuesta function seguro(callable $fn): array { try { $r = @$fn(); return is_array($r) ? $r : []; } catch (\Throwable $e) { return []; } } $fotos = seguro(fn() => $propiedad->getFotos()); usort($fotos, fn($a, $b) => $a->getOrden() <=> $b->getOrden()); $imagenes = array_filter($fotos, fn($f) => $f->getTipoId() === 1); $videos = array_filter($fotos, fn($f) => $f->getTipoId() === 3); $tours = array_filter($fotos, fn($f) => $f->getTipoId() === 5); $caracteristicas = seguro(fn() => $propiedad->getCaracteristicas()); ?> <h1><?= htmlspecialchars($propiedad->getNombre()) ?></h1> <p><?= nl2br(htmlspecialchars($propiedad->getDescripcion())) ?></p> <div class="galeria"> <?php foreach ($imagenes as $img): ?> <img src="<?= htmlspecialchars($img->getUbicacion()) ?>" alt="<?= htmlspecialchars($img->getNombre()) ?>" loading="lazy"> <?php endforeach; ?> </div> <?php foreach ($videos as $video): ?> <video controls src="<?= htmlspecialchars($video->getUbicacion()) ?>"></video> <?php endforeach; ?> <?php foreach ($tours as $tour): ?> <iframe src="<?= htmlspecialchars($tour->getUbicacion()) ?>" width="100%" height="480" allowfullscreen loading="lazy"></iframe> <?php endforeach; ?> <ul class="caracteristicas"> <?php foreach ($caracteristicas as $c): ?> <li><?= htmlspecialchars($c->getNombre()) ?></li> <?php endforeach; ?> </ul> <!-- Mapa --> <div id="mapa" data-lat="<?= $propiedad->getLatitud() ?>" data-lng="<?= $propiedad->getLongitud() ?>"></div>
4. Construir filtros desde los catálogos
$ubicaciones = $sdk->obtenerUbicaciones(); $tipos = $sdk->obtenerTipoPropiedades() ?? []; ?> <form method="get"> <select name="ciudad"> <option value="">Todas las ciudades</option> <?php foreach ($ubicaciones->getCiudades() as $ciudad): ?> <option value="<?= htmlspecialchars($ciudad->getCodigo()) ?>"> <?= htmlspecialchars($ciudad->getNombre()) ?> (<?= $ciudad->getCantidad() ?>) </option> <?php endforeach; ?> </select> <select name="barrio"> <option value="">Todos los barrios</option> <?php foreach ($ubicaciones->getBarrios() as $barrio): ?> <option value="<?= htmlspecialchars($barrio->getCodigo()) ?>" data-ciudad="<?= htmlspecialchars($barrio->getCiudadId()) ?>"> <?= htmlspecialchars($barrio->getNombre()) ?> (<?= $barrio->getCantidad() ?>) </option> <?php endforeach; ?> </select> <select name="tipo"> <option value="">Todos los tipos</option> <?php foreach ($tipos as $tipo): ?> <option value="<?= $tipo->getCodigo() ?>"> <?= htmlspecialchars($tipo->getNombre()) ?> (<?= $tipo->getCantidad() ?>) </option> <?php endforeach; ?> </select> <button type="submit">Buscar</button> </form>
El atributo
data-ciudadde cada barrio permite encadenar los selects en JavaScript sin volver a llamar a la API.
5. Formulario de contacto que envía al CRM
<?php require __DIR__ . '/vendor/autoload.php'; use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder; $errores = []; $exito = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 1. Validación en tu lado $nombre = trim($_POST['nombre'] ?? ''); $correo = filter_input(INPUT_POST, 'correo', FILTER_VALIDATE_EMAIL) ?: ''; $telefono = preg_replace('/[^\d+]/', '', $_POST['telefono'] ?? ''); $mensaje = trim($_POST['mensaje'] ?? ''); $moduleId = $_POST['module_id'] ?? ''; if ($nombre === '') { $errores[] = 'El nombre es obligatorio.'; } if ($correo === '') { $errores[] = 'El correo no es válido.'; } if ($telefono === '') { $errores[] = 'El teléfono es obligatorio.'; } if ($moduleId === '') { $errores[] = 'No se pudo identificar el inmueble.'; } // 2. Envío al CRM if ($errores === []) { $sdk = new SDKSmartHomeBuilder(); $sdk->setCompanyCode(getenv('HOMLITY_COMPANY_CODE')); $sdk->setProjectCode(getenv('HOMLITY_PROJECT_CODE')); $sdk->setProjectId(getenv('HOMLITY_PROJECT_ID')); // imprescindible para leads $respuesta = $sdk->agregarLead([ 'nombre' => $nombre, 'correo' => $correo, 'telefono' => $telefono, 'web' => $_SERVER['HTTP_HOST'] ?? 'sitio-web', 'commentario' => $mensaje, // ojo: doble "m" 'codigo' => $moduleId, // moduleId, no code ]); if ($respuesta === null) { $errores[] = 'No pudimos enviar tu solicitud. Intenta de nuevo.'; error_log('[Homlity SDK] agregarLead devolvió null para ' . $correo); } else { $exito = true; } } } ?> <?php if ($exito): ?> <p class="ok">¡Gracias! Un asesor te contactará pronto.</p> <?php else: ?> <?php foreach ($errores as $error): ?> <p class="error"><?= htmlspecialchars($error) ?></p> <?php endforeach; ?> <form method="post"> <input type="hidden" name="module_id" value="<?= htmlspecialchars($propiedad->getModuleId()) ?>"> <input name="nombre" placeholder="Nombre completo" required> <input name="correo" type="email" placeholder="Correo" required> <input name="telefono" placeholder="+57 300 000 0000" required> <textarea name="mensaje" placeholder="Cuéntanos qué necesitas"></textarea> <button type="submit">Solicitar información</button> </form> <?php endif; ?>
6. Sitemap y feed JSON
// sitemap-inmuebles.xml header('Content-Type: application/xml; charset=utf-8'); echo '<?xml version="1.0" encoding="UTF-8"?>'; echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'; foreach ($sdk->obtenerPropiedades() as $p) { echo '<url><loc>https://mi-inmobiliaria.com/inmueble/' . urlencode($p->getCodigo()) . '</loc><changefreq>daily</changefreq></url>'; } echo '</urlset>';
// feed.json — para un buscador en el front (Algolia, Fuse.js, etc.) header('Content-Type: application/json; charset=utf-8'); echo json_encode(array_map(fn($p) => [ 'codigo' => $p->getCodigo(), 'nombre' => $p->getNombre(), 'tipo' => $p->getTipoUnidad(), 'gestion' => $p->getGestion(), 'ciudad' => $p->getCiudad(), 'barrio' => $p->getBarrio(), 'precio' => $p->getValorVenta(), 'area' => $p->getArea(), 'alcobas' => $p->getAlcobas(), 'banos' => $p->getBanos(), 'imagen' => $p->getImagenDestacada(), 'lat' => $p->getLatitud(), 'lng' => $p->getLongitud(), ], $sdk->obtenerPropiedades()), JSON_UNESCAPED_UNICODE);
Integración con frameworks
Laravel
Registra el SDK como singleton en un ServiceProvider:
// app/Providers/AppServiceProvider.php use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder; public function register(): void { $this->app->singleton(SDKSmartHomeBuilder::class, function () { $sdk = new SDKSmartHomeBuilder(config('homlity.endpoint')); $sdk->setCompanyCode(config('homlity.company_code')); $sdk->setProjectCode(config('homlity.project_code')); $sdk->setProjectId(config('homlity.project_id')); return $sdk; }); }
// config/homlity.php return [ 'endpoint' => env('HOMLITY_ENDPOINT', 'https://api.smart-home.com.co'), 'company_code' => env('HOMLITY_COMPANY_CODE'), 'project_code' => env('HOMLITY_PROJECT_CODE'), 'project_id' => env('HOMLITY_PROJECT_ID'), ];
// app/Http/Controllers/InmuebleController.php public function index(SDKSmartHomeBuilder $sdk) { $propiedades = Cache::remember('homlity.inmuebles', now()->addMinutes(15), fn() => $sdk->obtenerPropiedades() ); return view('inmuebles.index', compact('propiedades')); }
Cache::rememberserializa los objetos del SDK sin problema: solo contienen arrays.
WordPress
// functions.php require_once get_template_directory() . '/vendor/autoload.php'; use Codwelt\SDK\SmartHome\SDKSmartHomeBuilder; function homlity_sdk(): SDKSmartHomeBuilder { static $sdk = null; if ($sdk === null) { $sdk = new SDKSmartHomeBuilder(); $sdk->setCompanyCode(HOMLITY_COMPANY_CODE); $sdk->setProjectCode(HOMLITY_PROJECT_CODE); $sdk->setProjectId(HOMLITY_PROJECT_ID); } return $sdk; } function homlity_inmuebles(): array { $cache = get_transient('homlity_inmuebles'); if ($cache !== false) { return $cache; } $propiedades = homlity_sdk()->obtenerPropiedades(); set_transient('homlity_inmuebles', $propiedades, 15 * MINUTE_IN_SECONDS); return $propiedades; } // Shortcode: [homlity_inmuebles limite="6"] add_shortcode('homlity_inmuebles', function ($atts) { $atts = shortcode_atts(['limite' => 6], $atts); $items = array_slice(homlity_inmuebles(), 0, (int) $atts['limite']); ob_start(); foreach ($items as $p) { printf('<article><h3>%s</h3><p>%s</p></article>', esc_html($p->getNombre()), esc_html(number_format($p->getValorVenta(), 0, ',', '.')) ); } return ob_get_clean(); });
PHP plano con caché en archivo
function homlityInmuebles(SDKSmartHomeBuilder $sdk, int $ttl = 900): array { $archivo = sys_get_temp_dir() . '/homlity-inmuebles.cache'; if (is_file($archivo) && (time() - filemtime($archivo)) < $ttl) { $datos = @unserialize(file_get_contents($archivo)); if ($datos !== false) { return $datos; } } $propiedades = $sdk->obtenerPropiedades(); // No sobrescribas una caché válida con una respuesta vacía if ($propiedades !== []) { file_put_contents($archivo, serialize($propiedades), LOCK_EX); } elseif (is_file($archivo)) { return unserialize(file_get_contents($archivo)); // sirve la caché rancia } return $propiedades; }
Buenas prácticas
- Cachea siempre.
obtenerPropiedades()trae el inventario completo en cada llamada. Un TTL de 10–30 minutos reduce drásticamente la latencia y la carga sobre la API. - Nunca llames al SDK desde el navegador. Las credenciales, y sobre todo el
projectId, deben quedarse en el servidor. - Sirve caché rancia ante fallos. Como el SDK devuelve
[]en vez de lanzar una excepción, una caída de la API vaciaría tu portal. Conserva la última respuesta buena. - Escapa toda salida. Las descripciones y títulos vienen tal como los cargó la
inmobiliaria: usa
htmlspecialchars()/esc_html(). - Valida antes de renderizar.
null,[]y campos ausentes son escenarios normales, no excepcionales. - Registra los fallos. Un
agregarLead()que devuelvenulles un lead perdido: déjalo en el log y, si es crítico, guarda el formulario en tu base de datos para reintentarlo. - Precarga las imágenes con
loading="lazy". Las galerías suelen traer más de 20 fotos en alta resolución. - Usa
getModuleId()para leads ygetCodigo()para URLs. Confundirlos es el error más frecuente.
Limitaciones y advertencias (importante)
Estas son características actuales del SDK que conviene conocer antes de llevarlo a producción:
| # | Comportamiento | Impacto | Cómo mitigarlo |
|---|---|---|---|
| 1 | Los errores se silencian. Los métodos capturan \Error y devuelven [] / null sin distinguir entre "no hay datos", "credenciales inválidas" y "la API está caída" |
No puedes reaccionar a un fallo real | Envuelve las llamadas y verifica el resultado; conserva caché rancia |
| 2 | obtenerTipoPropiedades() devuelve null, no [], si no hay tipos, y además emite un aviso foreach() … null given |
foreach sobre null en tu código |
?? [] antes de iterar |
| 3 | getFotos() y getCaracteristicas() avisan si el campo es null en la respuesta |
Aviso foreach() … null given; devuelven []. Se convierte en ErrorException si tu framework escala los avisos |
Accesor seguro con @ + try/catch |
| 4 | obtenerPropiedad() con un código inexistente devuelve un objeto vacío, no null |
Cada getter avisa y devuelve null |
Validar === null y getCodigo() === null |
| 5 | Erratas en nombres de métodos: getInmpuesto(), getvalorArriendo() |
Confusión al autocompletar | Documentadas en las tablas de arriba |
| 6 | La clave del lead es commentario (doble m) |
Comentario vacío en el CRM | Copiar la clave literal |
| 7 | getZonas() devuelve objetos Paises |
getCodigo() lee countryId |
Evitar el método por ahora |
| 8 | Sin paginación ni filtros del lado del servidor | Payload grande en inventarios extensos | Cachear y filtrar en PHP |
| 9 | Sin timeout de cURL configurable | Una API lenta bloquea tu petición | Ejecutar la sincronización en un cron, no en la petición del usuario |
| 10 | Sin reintentos | Un fallo transitorio pierde datos | Implementar reintentos con backoff en tu capa |
| 11 | La IP del lead se envía fija (10.0.1.1) |
No refleja el origen real | Limitación conocida del SDK |
| 12 | Namespace Codwelt\SDK\SmartHome pese al nombre homlity/… |
Confusión al importar | Documentado arriba |
Envoltorio recomendado
final class HomlityRepositorio { public function __construct( private SDKSmartHomeBuilder $sdk, private ?\Psr\Log\LoggerInterface $logger = null ) {} /** @return InmueblePreview[] */ public function inmuebles(): array { $resultado = $this->sdk->obtenerPropiedades(); if ($resultado === []) { $this->logger?->warning('[Homlity] Inventario vacío: ¿API caída o credenciales inválidas?'); } return $resultado; } public function inmueble(string $codigo): ?InmuebleDetail { try { $inmueble = $this->sdk->obtenerPropiedad($codigo); } catch (\Throwable $e) { $this->logger?->error('[Homlity] Error al obtener ' . $codigo, ['excepcion' => $e]); return null; } // Un código inexistente devuelve un objeto vacío, no null if ($inmueble === null || @$inmueble->getCodigo() === null) { return null; } return $inmueble; } /** @return Fotos[] */ public function fotos(InmueblePreview $inmueble): array { try { $fotos = @$inmueble->getFotos(); return is_array($fotos) ? $fotos : []; } catch (\Throwable) { return []; } } /** @return Caracteristicas[] */ public function caracteristicas(InmueblePreview $inmueble): array { try { $cars = @$inmueble->getCaracteristicas(); return is_array($cars) ? $cars : []; } catch (\Throwable) { return []; } } }
Endpoints REST subyacentes
Si trabajas en otro lenguaje (Node, Python, Go…), estos son los endpoints que el SDK consume.
Base: https://api.smart-home.com.co
| Método SDK | Verbo | Ruta |
|---|---|---|
obtenerPropiedades() |
GET |
/api/v1Inmobiliario/getUnits/{companyCode}/{projectCode} |
obtenerPropiedad() |
GET |
/api/v1Inmobiliario/getUnit/{companyCode}/{projectCode}/{unitCode} |
obtenerUbicaciones() |
GET |
/api/v1Inmobiliario/getUnitLocation/{companyCode}/{projectCode} |
obtenerTipoPropiedades() |
GET |
/api/v1Inmobiliario/getUnitType/{companyCode}/{projectCode} |
agregarLead() |
POST |
/api/SmartInmobiliario |
Todas las peticiones envían Accept: application/json y aceptan Content-Encoding: gzip.
Respuesta de getUnits
{
"returnCode": "SUCCESS",
"returnDesc": "",
"totalRecords": 15,
"pages": 1,
"currentPage": 1,
"recordsPerPage": 10000,
"units": [
{
"index": 1,
"code": "20230022",
"moduleId": "35f98252-6db1-43d6-bd6b-18752b883c41",
"name": "VENTA DE OFICINA EN CENTRO COMERCIAL PRIMAVERA",
"address": "Cll 15 # 40-01",
"area": 73.00,
"privateArea": 73.00,
"lotArea": 73.00,
"price": 650000000.00,
"maintenanceFee": 0.00,
"monthlyFee": 0.00,
"tax": 0.00,
"totalMonthlyFee": 0.00,
"hasTax": false,
"featuredProperty": false,
"bedroom": 1.0,
"bathrooms": 1.0,
"garages": 0,
"description": "Oficina ubicada en el Centro Comercial Primavera…",
"latitude": 4.134324,
"longitude": -73.640162,
"zone": null,
"zoneId": null,
"neighborhood": "villavicencio urbano",
"neighborhoodId": null,
"locality": null,
"localityId": null,
"city": "VILLAVICENCIO",
"cityId": "454cca01-1f8e-4c4e-91ba-24ce3a37f19f",
"strata": "4",
"floor": 3,
"yearBuild": 0,
"unitCategory": 2,
"unitCategoryDescription": "Venta",
"unitType": 4,
"unitTypeDescription": "Oficina",
"mainImageURL": "https://storage.googleapis.com/…",
"status": 0,
"images": [
{ "title": "4.jpeg", "mediaType": 1, "source": "https://…", "sequence": 4 }
],
"features": [ { "name": "BALCON" }, { "name": "ILUMINACION" } ],
"seller": {
"userId": "9ad2e1bb-37e2-4c15-8999-a673d2e928a5",
"name": "Vanessa Espinosa Quintero",
"mobileNumber": "3216031353",
"email": "comercial@ejemplo.com"
},
"agent": { "userId": "…", "name": "…", "mobileNumber": "…", "email": "…" },
"mls": null,
"listingSites": [
{ "name": "Proppit", "code": "20230021", "status": "1" },
{ "name": "FincaRaíz", "code": "10307114", "status": "1" }
]
}
]
}
Los campos
agent,listingSites,featuredProperty,statuseindexexisten en la respuesta pero el SDK aún no los expone mediante getters.
Respuesta de getUnitLocation
{
"countries": [ { "countryId": "8aa4c927-…", "name": "Colombia", "count": 0 } ],
"cities": [ { "countryId": "8aa4c927-…", "cityId": "454cca01-…", "name": "VILLAVICENCIO", "count": 12 } ],
"localities": [ { "cityId": "454cca01-…", "localityId": "7831f124-…", "name": "otro", "count": 11 } ],
"neighborhoods": [ { "countryId": "8aa4c927-…", "cityId": "454cca01-…", "neighborhoodId": "44d79774-…", "name": "villavicencio urbano", "count": 2 } ],
"zones": [],
"returnCode": "SUCCESS",
"returnDesc": ""
}
Respuesta de getUnitType
{
"types": [
{ "code": 1, "name": "Apartamento", "count": 6 },
{ "code": 2, "name": "Casa", "count": 6 },
{ "code": 4, "name": "Oficina", "count": 2 },
{ "code": 23, "name": "Casa Campestre","count": 1 },
{ "code": 0, "name": "Sin Definir", "count": 2 }
],
"returnCode": "SUCCESS",
"returnDesc": ""
}
Cuerpo de POST /api/SmartInmobiliario
{
"first_name": "Ana Ramírez",
"email": "ana@ejemplo.com",
"mobile_number": "+573001112233",
"origin": "mi-inmobiliaria.com",
"comment": "Me interesa agendar una visita",
"projectId": "-IxD-MeYCo32TRLf…",
"moduleId": "35f98252-6db1-43d6-bd6b-18752b883c41",
"ip": "10.0.1.1"
}
Se envía como application/x-www-form-urlencoded.
Solución de problemas
obtenerPropiedades() devuelve un array vacío
Recuerda que el SDK no lanza excepciones. Comprueba en este orden:
- Que llamaste a
setCompanyCode()ysetProjectCode()antes de consultar. - Que los códigos sean correctos (sin espacios ni saltos de línea al copiarlos).
- Que el servidor tenga salida HTTPS hacia
api.smart-home.com.co. - Que la extensión
curlesté habilitada:php -m | grep curl.
Diagnostica el endpoint directamente:
curl -s --compressed -H 'Accept: application/json' \ "https://api.smart-home.com.co/api/v1Inmobiliario/getUnits/TU_COMPANY/TU_PROJECT" | head -c 400
Una respuesta correcta empieza con {"returnCode":"SUCCESS".
Warning: foreach() argument must be of type array|object, null given
Estás llamando a getFotos() o getCaracteristicas() sobre un inmueble cuyo campo images o
features viene null. Es un escenario habitual, no un error de tu código.
En PHP puro es solo un aviso y el método devuelve []. En Laravel, Symfony o cualquier proyecto
con un set_error_handler que escala los avisos, se convierte en una ErrorException. Usa el
accesor seguro (@ + try/catch) descrito en ficha de inmueble.
El mismo aviso aparece dentro de obtenerTipoPropiedades() cuando el proyecto no tiene tipos.
Un inmueble responde con todos los campos en null
obtenerPropiedad() con un código inexistente no devuelve null: devuelve un
InmuebleDetail cuyo array interno es null. Cada getter emite
Trying to access array offset on value of type null y devuelve null.
Valida siempre dos condiciones:
if ($propiedad === null || $propiedad->getCodigo() === null) { // no existe }
El lead no aparece en el CRM
- ¿Llamaste a
setProjectId()? Sin él, el lead viaja sin proyecto. - ¿Usaste la clave
commentariocon doble m? - ¿Enviaste
getModuleId()(UUID) encodigo, y nogetCodigo()? - ¿
agregarLead()devolviónull? Entonces la petición HTTP falló.
La respuesta llega comprimida o ilegible
El SDK pide gzip. Si tu PHP no tiene ext-zlib, cURL no puede descomprimir. Verifica con
php -m | grep zlib.
Composer instala una versión con otra API
Es el caso descrito en Instalación: la etiqueta v2.2.1
expone SDKSmartInmoBuilder en vez de SDKSmartHomeBuilder. Usa dev-main mientras se publica
la versión estable.
Class "Codwelt\SDK\SmartHome\SDKSmartHomeBuilder" not found
- ¿Incluiste
vendor/autoload.php? - ¿Estás en
dev-main? Env2.2.1esa clase no existe. - Regenera el autoloader:
composer dump-autoload.
Pruebas
composer install ./vendor/bin/phpunit
La suite actual (tests/IndexTestCase.php) es un esqueleto mínimo y no cubre la API vigente:
construye SDKSmartHomeBuilder pasando el companyCode al constructor, cuando el constructor
espera un endpoint. Trátala como código heredado, no como referencia de uso.
Para probar contra la API real sin tocar el CRM, usa únicamente los métodos de lectura y las
credenciales de demostración incluidas en index.php.
Versionado y changelog
El proyecto sigue SemVer y
Keep a Changelog. Consulta
CHANGELOG.md.
| Versión | Estado | API pública |
|---|---|---|
dev-main |
Actual, sin etiquetar | SDKSmartHomeBuilder + InfraStructure\* |
v2.2.1 |
Última etiqueta en Packagist | SDKSmartInmoBuilder::build() + SmartHomeFacade |
v1.x |
Obsoleta | — |
Soporte
| Canal | Enlace |
|---|---|
| Sitio principal | https://homlity.com/ |
| Portal de desarrolladores | https://homlity.com/desarrolladores/ |
| Repositorio | https://github.com/homlity/sdk-smartinmobiliario |
| Reportar un error | https://github.com/homlity/sdk-smartinmobiliario/issues |
| Packagist | https://packagist.org/packages/homlity/sdk-smarthome |
Otros SDK de Homlity
| Paquete | Para qué sirve |
|---|---|
homlity/sdk-smarthome |
Inventario inmobiliario y leads (este paquete) |
homlity/sdk-proppit |
Publicación de inmuebles en Proppit desde PHP/Laravel |
Hecho por Homlity · homlity.com