Propiedades
Las propiedades son el núcleo de la plataforma. Pueden ser publicadas por agency_admin, agent, broker o developer, y asignadas a un agente específico.
Estados de una propiedad
Estado (propertyStatus) | Descripción |
|---|---|
draft | Borrador, no visible públicamente |
published | Publicada y visible |
sold | Vendida / arrendada |
inactive | Desactivada temporalmente |
Estado (listingStatus) | Descripción |
|---|---|
active | Disponible para compradores/arrendatarios |
inactive | No disponible actualmente |
rented | Arrendada |
Listar propiedades (público)
Solo devuelve propiedades con propertyStatus = published y listingStatus = active.
GET /api/propertiesQuery params opcionales:
| Parámetro | Tipo | Descripción |
|---|---|---|
page | number | Página (default: 1) |
limit | number | Resultados por página (default: 10) |
listing_type | string | sale o rent |
category | string | Valor exacto del catálogo de categorías, ej. Houses, Apartments |
city | string | Filtrar por ciudad |
min_price | number | Precio mínimo |
max_price | number | Precio máximo |
bedrooms | number | Número de recámaras |
bathrooms | number | Número de baños |
featured | boolean | Solo propiedades destacadas |
agent_id | uuid | Propiedades de un agente específico |
agency_id | uuid | Propiedades de una agencia específica |
Respuesta 200:
{
"success": true,
"data": {
"data": [
{
"id": "uuid-propiedad",
"title": "Departamento en Polanco",
"price": 4500000,
"currency": "MXN",
"listingType": "sale",
"categories": ["Apartments"],
"bedrooms": 3,
"bathrooms": 2,
"constructionArea": 120,
"city": "Ciudad de México",
"state": "CDMX",
"address": "Calle Masaryk 45, Polanco",
"propertyStatus": "published",
"listingStatus": "active",
"isFeatured": false,
"viewsCount": 148,
"agentId": "uuid-agente",
"agencyId": "uuid-agencia",
"user": { "firstName": "Carlos", "lastName": "Mendoza" },
"agent": { "company": "Inmobiliaria Centro MX" },
"assets": [
{
"id": "uuid-asset",
"url": "https://cdn.havi.app/properties/foto1.jpg",
"type": "image",
"sortOrder": 1
}
],
"createdAt": "2026-05-13T10:00:00.000Z"
}
],
"meta": {
"total": 250,
"perPage": 10,
"currentPage": 1,
"lastPage": 25
}
}
}Buscar propiedades
GET /api/properties/searchAcepta los mismos parámetros que el listado.
Detalle de propiedad
GET /api/properties/:idIncrementa el contador de vistas (viewsCount) en cada llamada.
Propiedades similares
GET /api/properties/:id/similarDevuelve hasta 6 propiedades similares en la misma ciudad y categoría.
Crear propiedad
Requiere autenticación
agency_admin, agent, broker o developer.
POST /api/properties
Authorization: Bearer {token}
Content-Type: application/jsonBody:
{
"title": "Departamento en Polanco",
"description": "Hermoso departamento de 3 recámaras con vista a Masaryk. Acabados de lujo, cocina integral, 2 cajones de estacionamiento.",
"price": 4500000,
"currency": "MXN",
"listingType": "sale",
"categories": ["Apartments"],
"bedrooms": 3,
"bathrooms": 2,
"halfBaths": 1,
"parkingSpaces": 2,
"constructionArea": 120,
"landArea": 0,
"yearBuilt": 2021,
"floors": 1,
"city": "Ciudad de México",
"state": "CDMX",
"zipCode": "11560",
"address": "Calle Masaryk 45, Polanco",
"latitude": 19.4313,
"longitude": -99.1893,
"amenities": ["Gimnasio", "Roof Garden Común", "Caseta de Vigilancia 24h", "Área de Mascotas"],
"agentId": "uuid-del-agente",
"propertyStatus": "draft",
"listingStatus": "inactive",
"isFeatured": false
}Comportamiento al asignar agente:
- Si incluyes
agentId, la propiedad queda asignada a ese agente y a su agencia automáticamente. - Si el usuario autenticado tiene rol
agenty no especificasagentId, se asigna a sí mismo.
Respuesta 201:
{
"success": true,
"data": {
"id": "uuid-propiedad",
"title": "Departamento en Polanco",
"propertyStatus": "draft",
"listingStatus": "inactive",
"agentId": "uuid-agente",
"agencyId": "uuid-agencia",
"userId": "uuid-usuario"
}
}Subir imágenes (URL pre-firmada S3)
El proceso de subida de imágenes se hace en dos pasos: obtener URLs pre-firmadas y subir directamente a S3.
Paso 1 — Solicitar URLs pre-firmadas:
POST /api/upload/presigned
Authorization: Bearer {token}
Content-Type: application/json{
"files": [
{ "filename": "fachada.jpg", "contentType": "image/jpeg" },
{ "filename": "sala.jpg", "contentType": "image/jpeg" },
{ "filename": "recamara.jpg","contentType": "image/jpeg" }
],
"propertyId": "uuid-propiedad"
}Respuesta:
{
"success": true,
"data": [
{
"filename": "fachada.jpg",
"uploadUrl": "https://s3.amazonaws.com/bucket/...",
"publicUrl": "https://cdn.havi.app/properties/fachada.jpg"
}
]
}Paso 2 — Subir cada archivo directamente a S3:
await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' },
body: fileBlob
})Agregar imágenes por URL (CDN propio)
Si tus imágenes ya viven en tu propio CDN, no necesitas subirlas a nuestro S3. Basta con enviar las URLs públicas en el campo images al actualizar (o crear) la propiedad:
PUT /api/properties/:id
Authorization: Bearer {token}
Content-Type: application/json{
"images": [
"https://cdn.tuempresa.com/propiedades/123/fachada.jpg",
{ "url": "https://cdn.tuempresa.com/propiedades/123/sala.jpg", "floorLabel": "ground-floor" }
]
}Reglas importantes:
:ides el UUID de la propiedad que devolvió la API al crearla (formatoxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). No uses tucustomIdnimlsNumber— recibirásINVALID_PROPERTY_ID.- Cada elemento de
imagesdebe ser un string con la URL o un objeto{ "url": "...", "floorLabel": "..." }. Cualquier otra forma (ej.{ "src": ... }o{ "imageUrl": ... }) se rechaza conINVALID_IMAGE_FORMAT. - Las URLs deben empezar con
http://ohttps://y ser públicamente accesibles. - Enviar
imagesreemplaza el set completo de imágenes de la propiedad. Si quieres agregar una imagen, envía la lista completa (las existentes + la nueva). Si omites el campoimages, las imágenes actuales se conservan. - La primera imagen válida queda como destacada (
isFeatured). - El campo
videosfunciona igual, con un array de URLs (strings).
Respuesta 200 — incluye un reporte media que indica cuántas imágenes se aceptaron y cuáles se omitieron y por qué:
{
"success": true,
"message": "Property updated successfully",
"data": { "id": "uuid-propiedad", "assets": [ /* ... */ ] },
"media": {
"images": {
"received": 3,
"created": 2,
"skipped": [
{
"index": 2,
"code": "INVALID_IMAGE_FORMAT",
"message": "Each image must be a URL string or an object { url, floorLabel? }",
"received": { "src": "https://cdn.tuempresa.com/foto.jpg" }
}
]
},
"videos": null
}
}Si created es 0 y no ves imágenes en la propiedad, revisa el array skipped: ahí está la razón exacta por cada imagen rechazada.
Códigos de error:
| HTTP | code | Causa |
|---|---|---|
400 | INVALID_PROPERTY_ID | El :id no es un UUID válido |
404 | PROPERTY_NOT_FOUND | No existe propiedad con ese UUID |
403 | NOT_AUTHORIZED | El token no pertenece al dueño, agente asignado ni admin de la agencia de la propiedad |
422 | VALIDATION_ERROR | Campos inválidos en el body (detalle en errors) |
200 con media.images.skipped[].code | INVALID_IMAGE_FORMAT, BLOB_URL_NOT_ALLOWED, INVALID_URL_SCHEME | Imágenes individuales rechazadas (la propiedad sí se actualiza) |
Publicar propiedad
Cambia el estado de borrador a publicado:
PUT /api/properties/:id
Authorization: Bearer {token}
Content-Type: application/json
{
"propertyStatus": "published",
"listingStatus": "active"
}Actualizar propiedad
PUT /api/properties/:id
Authorization: Bearer {token}
Content-Type: application/jsonAcepta los mismos campos que la creación. Todos son opcionales.
Asignar agente a propiedad
PUT /api/properties/:id/assign-agent
Authorization: Bearer {token}
Content-Type: application/json
{
"agentId": "uuid-del-agente"
}Respuesta 200:
{
"success": true,
"message": "Agent assigned successfully",
"data": {
"id": "uuid-propiedad",
"agentId": "uuid-agente",
"agencyId": "uuid-agencia"
}
}Eliminar propiedad
DELETE /api/properties/:id
Authorization: Bearer {token}Mis propiedades
GET /api/my-properties
Authorization: Bearer {token}Incluye propiedades en todos los estados (drafts, publicadas, inactivas).
Campos de propiedad
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
title | string | Sí | Título de la propiedad |
description | string | No | Descripción detallada |
price | number | Sí | Precio |
currency | string | No | MXN (default), USD, COP, PEN, CLP |
listingType | string | Sí | sale o rent |
categories | array | Sí | Valores del catálogo de categorías, ej. ["Apartments"] |
bedrooms | number | No | Número de recámaras |
bathrooms | number | No | Baños completos |
halfBaths | number | No | Medios baños |
parkingSpaces | number | No | Cajones de estacionamiento |
constructionArea | number | No | Área construida en m² |
landArea | number | No | Área de terreno en m² |
yearBuilt | number | No | Año de construcción |
floors | number | No | Número de pisos |
city | string | No | Ciudad |
state | string | No | Estado/Departamento/Provincia |
zipCode | string | No | Código postal |
address | string | No | Dirección completa |
latitude | number | No | Latitud para mapa |
longitude | number | No | Longitud para mapa |
amenities | array | No | Lista de amenidades — ver catálogo de amenidades |
agentId | uuid | No | Asignar a un agente |
propertyStatus | string | No | draft, published, sold, inactive |
listingStatus | string | No | active, inactive, rented |
isFeatured | boolean | No | Marcar como propiedad destacada |
Catálogo de categorías
El campo categories acepta un array con uno o más de estos valores. Envía el valor exacto en inglés (columna value); la plataforma lo muestra con su etiqueta en español. Los filtros públicos (?category=) comparan contra estos mismos valores, así que una categoría fuera de catálogo hará que la propiedad no aparezca en los resultados filtrados.
value (enviar este) | Etiqueta mostrada |
|---|---|
Adult Community | Comunidad para Adultos |
Agriculture | Agrícola |
Apartments | Apartamentos / Departamentos |
Boat Slip | Amarre para Embarcación |
Bungalow | Bungalow |
Business | Negocio |
Cabin | Cabaña |
Condominium | Condominio |
Deeded Parking | Estacionamiento con Escritura |
Duplex | Dúplex |
Farm | Granja |
Hotel-Motel | Hotel / Motel |
Houses | Casas |
Industrial | Industrial |
Loft | Loft |
Manufactured Home | Casa Prefabricada |
Manufactured On Land | Casa Prefabricada en Terreno Propio |
Mixed Use | Uso Mixto |
Mobile Home | Casa Móvil |
Multi-Family | Multifamiliar |
Office | Oficina |
Other | Otro |
Own Your Own | Propiedad Individual (Own Your Own) |
Quadruplex | Cuádruplex |
Ranch | Rancho |
Retail | Local Comercial |
Single Family Attached | Casa Unifamiliar Adosada |
Single Family Detached | Casa Unifamiliar Independiente |
Stock Cooperative | Cooperativa de Vivienda |
Timeshare | Tiempo Compartido |
Townhome | Casa Adosada |
Triplex | Tríplex |
Unimproved Land | Terreno sin Desarrollar |
Vacation Home | Casa Vacacional |
Villa | Villa |
Warehouse | Bodega |
Equivalencias
Si tu sistema maneja Apartment o Townhouse (nomenclatura RESO), envía sus equivalentes del catálogo: Apartments y Townhome respectivamente.
{ "categories": ["Houses"] }Catálogo de amenidades
El campo amenities acepta un array de strings. Usa los valores exactamente como aparecen aquí (con mayúsculas y acentos); se muestran tal cual en el detalle de la propiedad y en los filtros.
De la vivienda:
Aire Acondicionado, Calefacción, Cisterna, Filtro de Agua, Boiler, Gas Natural, Paneles Solares, Cocina Integral, Línea Blanca, Lavadora, Secadora, Refrigerador, Microondas, Lavavajillas, Closets, Vestidor, Bodega Interior, Cuarto de Servicio, Estudio / Oficina, Chimenea, Jacuzzi Privado, Piscina Privada, Terraza / Balcón, Roof Garden Privado, Jardín Privado, Parrilla / BBQ, WiFi, TV Cable / Satélite, Sistema de Seguridad, Domótica, Cava de Vinos
Del condominio / desarrollo:
Caseta de Vigilancia 24h, Acceso Controlado, Cámaras de Seguridad, Elevador, Generador Eléctrico, Estacionamiento de Visitas, Lobby / Recepción, Alberca Común, Jacuzzi Común, Gimnasio, Cancha de Tenis, Cancha de Pádel, Cancha de Basketball, Cancha de Fútbol, Área de Juegos Infantiles, Área de Mascotas, Jardines y Áreas Verdes, Asadores Comunes, Roof Garden Común, Salón de Eventos, Salón de Usos Múltiples, Business Center / Coworking, Lavandería Común, Bodega Común, Administración en sitio
{ "amenities": ["Cocina Integral", "Terraza / Balcón", "Gimnasio", "Alberca Común"] }Valores fuera de catálogo
La API no rechaza strings fuera de estos catálogos (se guardan tal cual), pero no harán match con los filtros de la plataforma y se mostrarán sin traducción. Para una integración correcta, usa siempre los valores listados.