WP Manifestindependent plugin directory
manifest / content / real-state-headless

Real State Headless

CMS headless inmobiliario en WP puro (sin ACF ni terceros): CPTs, campos dinámicos, API REST con keys/OpenAPI, leads, instalador demo y backup.

by sn4p.dev · github.com/yosnap/real-state-headless · website

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/yosnap/real-state-headless/archive/refs/heads/main.zip

Plugin WordPress "Zero-Dependencies" para CMS inmobiliario headless: sin ACF ni librerías de terceros, PHP 8.0+ orientado a objetos con namespaces RealStateHeadless\*, y admin en Vanilla JS ESM (wp_enqueue_script_module, WordPress 6.5+) sin build step. WordPress actúa como motor de datos headless, exponiendo todo vía una API REST propia (inmo/v1) para consumo desde un frontend externo (Astro, React, etc.).

Requisitos

  • WordPress 6.5+ (usa wp_enqueue_script_module, disponible desde 6.5)
  • PHP 8.0+

Instalación

  1. Copiar la carpeta real-state-headless a wp-content/plugins/.
  2. Activar el plugin desde el panel de administración de WordPress.
  3. El menú "Real State" agrupa todas las pantallas del plugin: Estado, Inmuebles/Agentes/Reseñas/Leads, Campos personalizados, Instalador Demo, API Keys y Backup.

Arquitectura

real-state-headless/
├── real-state-headless.php     # bootstrap + header plugin
├── uninstall.php                # borrado condicional (opt-in)
├── src/Core|PostTypes|Taxonomies|Fields|Admin|Agents|Leads|Api|Installer|Backup/
├── assets/admin/{js,css}/       # ES modules nativos, sin bundler
├── templates/                   # formulario de leads + panel de agente (frontend)
└── README.md

Uso rápido

  1. Cargar contenido: creá inmuebles/agentes/reseñas manualmente desde wp-admin, o usá Instalador Demo (menú "Real State") para poblar datos de ejemplo (con imágenes de Unsplash si configurás una API key, o placeholders si no).
  2. Generar una API key: menú "Real State → API Keys" — elegí nivel test o live y si la key puede ver ubicación precisa (lat/lng). La key completa se muestra una sola vez; guardala.
  3. Consumir la API: mandá el header X-API-Key con cada request a inmo/v1 (opcional para los endpoints de solo lectura — sin key, la respuesta omite campos sensibles y aplica un límite de tráfico más bajo).
  4. Recibir consultas: insertá el shortcode [rsh_lead_form property_id="123"] en cualquier página/entrada para mostrar el formulario de contacto de un inmueble.
  5. Panel de agente: un usuario con el rol "Agente Inmobiliario" puede ver sus inmuebles y leads asignados con el shortcode [rsh_agent_panel], sin acceder a wp-admin.

Referencia de la API (inmo/v1)

Base URL: https://tu-sitio.com/wp-json/inmo/v1

Autenticación: header X-API-Key: <tu key> (opcional en los endpoints de lectura). Sin key, el tráfico se limita a 60 req/min por IP y se ocultan campos sensibles (rsh_lat/rsh_lng). Con key válida, la cuota es horaria según el nivel (test: 100/h, live: 1000/h) y se agregan headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset a cada respuesta. Se recomienda HTTPS siempre que se use una API key.

Método Endpoint Descripción
GET /properties Búsqueda de inmuebles con filtros: type, operation, location (slugs de taxonomía), min_price, max_price, min_m2, bedrooms, bathrooms, page, per_page (máx. 100, cap duro server-side).
GET /properties/{id} Detalle de un inmueble publicado.
GET /agents Listado de agentes (perfiles públicos).
GET /agents/{id} Detalle de un agente.
GET /reviews Listado de reseñas moderadas (post_status=publish únicamente).
GET /openapi.json Especificación OpenAPI 3.x generada desde las rutas y campos reales (incluye campos dinámicos del Fields Builder).
GET /docs Visor HTML/JS propio, 100% offline, que renderiza /openapi.json (ver nota abajo).

Ejemplo (fetch desde React/Astro):

const res = await fetch('https://tu-sitio.com/wp-json/inmo/v1/properties?type=piso&operation=venta&max_price=250000', {
    headers: { 'X-API-Key': 'rsh_live_...' },
});
const { items, total, page, per_page } = await res.json();

Nota sobre /docs: el plan original preveía vendorizar el paquete oficial swagger-ui-dist. Este entorno de desarrollo no tuvo acceso a red/npm para descargarlo, así que /docs sirve un visor HTML/JS propio, liviano y sin dependencias, que consume el /openapi.json real. Si se desea el Swagger UI oficial, assets/vendor/swagger-ui/ queda como punto de extensión: reemplazar Api\SwaggerUi::render_html() para servir esos archivos en su lugar.

Campos de un inmueble (meta)

Predefinidos (prefijo rsh_): precio, m2, habitaciones, banos, certificado_energetico (A-G), lat/lng (ocultos por defecto vía show_in_rest=false y por Api\FieldFilter — requieren permiso reveal_precise_location en la API key), galeria (IDs de adjuntos).

Campos dinámicos: se agregan desde "Real State → Campos personalizados" (tipos: texto, número, selección, checkbox) y aparecen automáticamente en el meta box del inmueble y en la respuesta de la API.

Hooks de extensión

Contrato público congelado a partir de v1.0.0 — el naming (rsh_*) y las firmas no cambian sin un bump mayor.

Hook Tipo Cuándo dispara Firma
rsh_before_lead_saved action Justo antes de insertar un lead enviado desde [rsh_lead_form]. Un callback puede rechazar el envío (ej. spam) respondiendo wp_send_json_error() + exit él mismo. do_action( 'rsh_before_lead_saved', array $lead ) — $lead = ['name','email','phone','message','property_id']
rsh_after_lead_saved action Después de guardar el lead e intentar el email de notificación (chequeá el meta rsh_mail_sent para saber si se entregó). do_action( 'rsh_after_lead_saved', int $lead_id, int[] $agent_ids )
rsh_property_fields filter Antes de aplicar las reglas de visibilidad de Api\FieldFilter, sobre el set de campos crudo de un inmueble en la respuesta de inmo/v1. apply_filters( 'rsh_property_fields', array $meta, int $post_id )
rsh_api_field_permissions filter Último filtro sobre los campos de un inmueble antes de responder, después del default-deny de lat/lng. $key es null en requests anónimos. apply_filters( 'rsh_api_field_permissions', array $meta, ?array $key )
rsh_export_data filter Sobre el dataset completo de un backup, justo antes de escribirlo a disco. Nunca agregar secretos acá — el archivo resultante es descargable por cualquier usuario manage_options y puede compartirse/restaurarse en otro sitio. apply_filters( 'rsh_export_data', array $dataset )

Ejemplo — rechazar un lead con un dominio de email bloqueado:

add_action( 'rsh_before_lead_saved', function ( array $lead ) {
    if ( str_ends_with( $lead['email'], '@spamdomain.test' ) ) {
        // wp_send_json_error() already calls wp_die(), which halts execution here.
        wp_send_json_error( [ 'message' => 'No podemos procesar tu consulta.' ], 400 );
    }
} );

Ejemplo — agregar un campo calculado a la respuesta de /properties:

add_filter( 'rsh_property_fields', function ( array $meta, int $post_id ) {
    $meta['rsh_precio_por_m2'] = ( (float) $meta['rsh_m2'] > 0 )
        ? round( (float) $meta['rsh_precio'] / (float) $meta['rsh_m2'], 2 )
        : null;
    return $meta;
}, 10, 2 );

Seguridad

  • Nonce + current_user_can() en cada acción de admin/AJAX; sanitización tipada en cada guardado de meta (Fields\FieldSanitizer).
  • X-API-Key nunca se guarda en claro — solo su hash SHA-256; la key completa se muestra una única vez al generarla.
  • Rate-limiting completo (cuota por key + límite por IP para tráfico anónimo) con headers X-RateLimit-* y 429 al exceder.
  • Campos sensibles (lat/lng precisos) con default-deny tanto en la API propia como en la REST API nativa de WordPress.
  • Leads (datos personales) nunca se exponen en ninguna REST API; CPT restringido a manage_options.
  • Backups: formato JSON únicamente (sin parser XML), nunca exportan wp_rsh_api_keys ni ningún secreto, se descargan solo vía admin-ajax autenticado (nunca una URL pública), y los archivos temporales se purgan automáticamente cada hora.
  • Se recomienda servir el sitio siempre por HTTPS cuando se usan API keys.

Desinstalación

Por defecto, desinstalar el plugin no borra ningún dato (solo limpia 2 opciones internas de bajo nivel). Si en "Real State → Estado" activás la casilla "Borrar todos los datos del plugin al desinstalarlo", uninstall.php elimina inmuebles/agentes/reseñas/leads (+ sus adjuntos), términos de taxonomía, opciones del plugin y la tabla wp_rsh_api_keys. Las cuentas de usuario de WordPress (incluidos los agentes) nunca se borran, aunque hayan sido creadas por el Instalador Demo.

Roadmap

Fase Descripción Versión
1 Fundación del plugin v0.1.0
2 Arquitectura de datos + Fields Builder v0.2.0
3 Agentes, Leads y comunicación v0.3.0
4 API Headless (keys, OpenAPI, búsqueda+caché) v0.4.0
5 Instalador de datos demo v0.5.0
6 Backup / Restore v0.6.0
7 QA, Seguridad y Cierre v1.0.0

Licencia

Free para uso no comercial.