Volver a Casos de Ingeniería
13 de febrero de 2025

SaaS Inmobiliario Multi-Tenant de Alto Rendimiento con Arquitectura MLS y Edge Caching

TTFB subsegundo vía Edge Storage, 90%+ de reducción de memoria en consultas geográficas, Pipeline automatizado Lead-to-WhatsApp

Laravel 12 PHP 8.2 MySQL 8.0 Cloudflare R2 Alpine.js 3 Tailwind CSS 4 OpenStreetMap Nominatim

El Cuello de Botella

El mercado inmobiliario venezolano opera bajo una fragmentación estructural: los listados de propiedades, los perfiles de corredores y los registros transaccionales están aislados en redes de franquicias heredadas (RE/MAX, Century 21, Rent-A-House) y cientos de agentes independientes que carecen de un servicio de listado múltiple (MLS) centralizado. Esta fragmentación generó cuatro restricciones críticas de ingeniería. Primero, la asimetría de información y datos obsoletos impedía que consumidores y corredores accedieran a una única fuente de verdad para valores inmobiliarios en tiempo real, estado legal y geolocalización precisa.

Segundo, los cuellos de botella en consultas geográficas surgieron porque los seeds estándar de la geografía venezolana contienen volúmenes masivos de entidades de ciudades y estados; la carga ingenua (eager-loading) de estas relaciones en vistas de búsqueda causaba agotamiento repetido de memoria en los procesos de PHP-FPM y picos de errores 502 bajo carga concurrente. Tercero, los altos costos de infraestructura eran inevitables al almacenar imágenes de alta resolución directamente en volúmenes de bloque de VPS, lo que provocaba inflación de almacenamiento, un Time to First Byte (TTFB) lento y límites duros de escalado vertical. Cuarto, los flujos de trabajo inconsistentes de los agentes significaban que los independientes carecían de herramientas accesibles de CRM, programación y marketing automatizado, forzando un seguimiento manual de clientes y visibilidad nula del pipeline.


Arquitectura y Automatización

Inmueble.lat fue diseñado como una plataforma SaaS multi-tenant basada en planes, construida sobre un monolito moderno de Laravel 12 ejecutándose en PHP 8.2. La capa de presentación utiliza Tailwind CSS v4 para estilos utility-first y Alpine.js 3 para reactividad ligera del lado del cliente, sin la sobrecarga de un framework SPA completo. La arquitectura prioriza la eficiencia de costos, la integridad de datos y el rendimiento del renderizado del lado del servidor (SSR).

Decisiones clave de diseño y capas de integración:

  • Almacenamiento de Objetos Desacoplado con Cloudflare R2: Todos los avatares de usuario, logotipos de oficinas y fotografías de propiedades se descargan a Cloudflare R2 a través del driver de compatibilidad S3 (league/flysystem-aws-s3-v3). Esto elimina la inflación de disco local y aprovecha la red perimetral global de Cloudflare para servir activos desde nodos cercanos al usuario, reduciendo drásticamente el TTFB y descargando el ancho de banda de salida de la capa de cómputo.
  • Consolidación ETL Automatizada: Un comando CLI personalizado (import:offices) analiza un directorio de corredores heredado en formato texto mediante patrones de extracción con expresiones regulares. Normaliza los datos de contacto, direcciones y dominios, y luego realiza upserts en un esquema relacional limpio. Esto reemplaza la entrada manual de datos con un pipeline de importación idempotente y reproducible.
  • Geocodificación Geográfica Dirigida por Eventos: La plataforma se integra directamente con la API de OpenStreetMap Nominatim. Los modelos Property y Office utilizan hooks del ciclo de vida de Eloquent (booted()) para disparar geocodificación asíncrona cada vez que se modifican los campos de ubicación. El sistema construye una cadena de dirección localizada ("Dirección, Ciudad, Estado, País"), resuelve latitud y longitud con precisión, y persiste las coordenadas sin requerir entrada manual por parte de los agentes—garantizando precisión cartográfica y completitud de metadatos Schema.org.
  • Guardarraíles de Onboarding vía Middleware: Un middleware personalizado EnsureOnboardingIsCompleted bloquea el acceso al panel de publicación hasta que los nuevos usuarios completen su perfil de agente (nombre, agencia, teléfono, marca). Esto actúa como una compuerta de calidad de datos, evitando que listados incompletos o de baja calidad aparezcan en el MLS público.
  • Aplicación de Límites de Plan en la Capa del Modelo: Las barreras de facturación están incrustadas directamente en Eloquent. El modelo User expone canAddProperty() y getAllowedImagesAttribute(), que consultan la relación Plan activa para imponer límites estrictos en la cantidad de listados y fotografías permitidas (por ejemplo, fallback de 3 imágenes para planes gratuitos, ilimitado para premium). Esto evita llamadas a servicios de autorización externos y mantiene la latencia dentro del ciclo de la petición.
  • API de Búsqueda de Ubicaciones con Eficiencia de Memoria: En lugar de hidratar dropdowns masivos de ciudades/estados en el DOM, un endpoint dedicado /api/ubicaciones sirve joins SQL optimizados entre las tablas cities y states. Los resultados son consumidos por un componente typeahead de Tom Select, recuperando registros bajo demanda vía AJAX y reduciendo la sobrecarga de memoria por petición en más del 90% en comparación con el eager-loading del árbol geográfico completo.
  • Pipeline de Pre-cualificación para WhatsApp: El LeadController implementa un enrutador de consultas que captura la intención del cliente (plazo, rango de presupuesto, información de contacto), la persiste como un registro estructurado Lead, y genera un enlace profundo wa.me codificado. Esto direcciona al usuario directamente a una conversación de WhatsApp pre-llenada con el agente del listado, colapsando el ciclo de lead-to-respuesta de horas a segundos.
┌─────────────┐     ┌─────────────────────────────────────┐     ┌─────────────┐
│   Público   │────▶│       Monolito Laravel 12         │────▶│   MySQL     │
│  (Alpine.js│     │  ┌────────┐ ┌────────┐ ┌────────┐  │     │    8.0      │
│ Tailwind 4) │     │  │Property│ │  User  │ │  Lead  │  │     │  (Almacén   │
└─────────────┘     │  │ Modelo │ │ Modelo │ │  Ctrl  │  │     │   Primario) │
                    │  │+Geocode│ │+Límites│ │        │  │     └──────┬────┘
                    │  └────┬───┘ └───┬────┘ └────────┘  │            │
                    └───────┼─────────┼────────────────┘            │
                            │         │                               │
              ┌─────────────┘         └─────────────┐                 │
              ▼                                     ▼                 ▼
     ┌────────────────┐                  ┌────────────────┐   ┌──────────────┐
     │ Cloudflare R2  │                  │OpenStreetMap   │   │    CLI ETL   │
     │(Almacén Objetos)│                  │Nominatim API   │   │(Parse Legacy)│
     └────────────────┘                  └────────────────┘   └──────────────┘

ROI Medible

Peso de página y TTFB de imágenes:

Almacenamiento local en VPS cargando galerías completas en alta resolución dentro del DOM → Activos en Cloudflare R2 con edge caching y umbral visual de 5 imágenes con inyección de lightbox bajo demanda.

  • Antes: >2.5s de TTFB en páginas de detalle bajo carga.
  • DESPUÉS: TTFB sostenido sub-800ms.

Consumo de memoria en búsquedas geográficas:

Carga eager del árbol completo de entidades geográficas de Venezuela en vistas Blade → Typeahead AJAX vía /api/ubicaciones con joins SQL indexados y virtualización de Tom Select.

  • Antes: Picos de memoria PHP-FPM de 128MB+ y errores 502 intermitentes.
  • DESPUÉS: Promedio de <12MB por petición, agotamiento de workers reducido a cero.

Latencia de respuesta a leads:

Flujos manuales de correo electrónico y devolución de llamada sin traspaso estructurado → Enrutamiento pre-cualificado de WhatsApp en un clic generado del lado del servidor desde registros Lead.

  • Antes: Tiempo promedio de respuesta del agente de 4–24 horas.
  • DESPUÉS: Iniciación instantánea de chat con datos de intención pre-capturados.

Precisión y frescura de datos geoespaciales:

Hojas de cálculo estáticas y coordenadas GPS ausentes en más del 60% de los listados heredados → Geocodificación automatizada vía Nominatim disparada en cada evento de guardado del modelo.

  • Antes: Pines de mapa inconsistentes y entrada manual de coordenadas.
  • DESPUÉS: Resolución automatizada al 100% de lat/long para todas las propiedades nuevas y actualizadas.

Superficie de indexación SEO:

Sin datos estructurados, títulos genéricos y meta descripciones sin límite → JSON-LD Schema.org dinámico (House, Apartment, Accommodation) con recorte server-side de 65/160 caracteres.

  • Antes: Cero rich snippets; truncación incontrolada en SERP.
  • DESPUÉS: Cobertura completa de Schema.org en todas las vistas de propiedad con snippets optimizados para CTR.

Costo de almacenamiento por agente:

Almacenamiento en bloque de VPS premium facturado por volumen provisionado → Cloudflare R2 con almacenamiento de objetos modelo pay-per-request.

  • Antes: Crecimiento lineal de costos de almacenamiento forzando escalado vertical.
  • DESPUÉS: Desacoplamiento de cómputo y almacenamiento con cabeza de escalado horizontal y ~70% de reducción de costos proyectada a escala.

Escrito por

Miguel Ortiz

Growth Engineer & Technical SEO

Hablemos de un Desafío Similar