Cómo Crear un Paso de Resumen en Formularios de Reserva JetFormBuilder + JetAppointment en WordPress

¿Quieres que tus usuarios revisen todos sus datos antes de confirmar una reserva online? Aquí te enseño paso a paso cómo agregar un paso de resumen profesional a cualquier formulario multipaso (multistep) creado con JetFormBuilder y JetAppointment en WordPress, usando un bloque HTML + JS personalizado que recoge absolutamente todos los campos (incluyendo servicios, multiselección, dirección, horario y más).

 

En este tutorial aprenderás a implementar un paso de resumen de datos en formularios multistep de JetFormBuilder, ideal para reservas con JetAppointment, totalmente personalizable y fácil de adaptar a los campos que tú requieras.

📋 ¿Qué logramos?

  • Formularios multipaso y de reserva (servicios, horarios, datos de contacto, etc.), totalmente personalizados.
  • Resumen de datos editable, que refleja exactamente lo que el usuario llenó antes de enviar.
  • Visual limpio: los campos vacíos simplemente desaparecen del resumen.
  • 100% compatible y visualmente integrado con Gutenberg, JetFormBuilder y JetAppointment.

🚦 Paso a Paso

1️⃣ Prepara tu formulario JetFormBuilder

  • Crea o edita tu formulario JetFormBuilder normal.
  • Usa bloques multipasos («Page Break») para dividir el llenado por secciones.
  • Incluye todos los campos que desees: nombre, correo, dirección, área, fecha, hora, servicios extra, etc.
  • Si usas JetAppointment, incluye también el calendario y los campos relacionados.

2️⃣ Agrega el bloque de Resumen

  • Al final de tu formulario, antes del botón de envío, añade un «Custom HTML block» en el editor de Gutenberg.
  • Pega el siguiente código HTML para la estructura visual del resumen.
  • Modifica los ids de los <span> según los campos de tu formulario.
<div id="booking-review-summary" class="summary-card" style="margin-bottom:32px; display:none;">
  <div class="dateselected">
    <div class="summary-main-service">
      <h3 class="summary-main-title">Date Selected : <span id="summary-date"></span>  <span id="summary-time"></span></h3>
    </div>
  </div>
  <div class="summary-main-service">
    <h3 class="summary-main-title">Main Service Selected</h3>
    <ul id="summary-main-list" class="summary-main-list"></ul>
    <ul id="summary-secondary-list" class="summary-secondary-list"></ul>
  </div>
  <div class="summary-row">
    <div class="summary-location">
      <h3 class="summary-location-title">Location & Schedule</h3>
      <ul class="summary-location-list">
        <li><span>Area:</span> <span id="summary-zona"></span></li>
        <li><span>Address:</span> <span id="summary-address-line"></span></li>
        <li><span>Address Notes:</span> <span id="summary-address"></span></li>
      </ul>
    </div>
    <div class="summary-contact">
      <h3 class="summary-contact-title">Your Contact Details</h3>
      <ul class="summary-contact-list">
        <li><span>Full Name:</span> <span id="summary-name"></span></li>
        <li><span>Phone:</span> <span id="summary-phone"></span></li>
        <li><span>Email:</span> <span id="summary-email"></span></li>
        <li><span>Preferred Contact:</span> <span id="summary-contact"></span></li>
      </ul>
    </div>
  </div>
</div>

Recuerda: Puedes cambiar, agregar o eliminar <li> según los campos que quieras mostrar. Lo importante es que el id del <span> coincida con el campo a inyectar desde JS.

3️⃣ El JavaScript responsable del resumen dinámico

  • Pega este bloque en el mismo Custom HTML block (o usa un plugin para código JS en la página).
<script>
document.addEventListener('DOMContentLoaded', function() {
  function isLastStepActive() {
    let activeStep = document.querySelector('.jet-form-builder-page:not(.jet-form-builder-page--hidden)');
    return activeStep && activeStep.dataset.page === "4";
  }
  function printOrEmpty(val) {
    if (val && val.trim() !== '') return val;
    return '<span class="empty-value">Empty</span>';
  }
  function getMainService() {
    const node = document.querySelector('.primary-service');
    if (!node) return null;
    const title = node.querySelector('.elementor-heading-title, .service-title, .jet-listing-dynamic-field__content, span')?.innerText?.trim() || '';
    const img = node.querySelector('img')?.src || '';
    return {img, title};
  }
  function getSecondaryServices() {
    const checked = document.querySelectorAll('input[name="related_service_ids"]:checked, input[name="related_service_ids[]"]:checked');
    let result = [];
    checked.forEach(input => {
      const wrap = input.closest('.jet-form-builder__field-wrap');
      const serviceBlock = wrap?.querySelector('.service-related');
      if (serviceBlock) {
        const img = serviceBlock.querySelector('.jet-listing-dynamic-image img')?.src || '';
        const title = serviceBlock.querySelector('.jet-listing-dynamic-field__content, .service-title, span')?.innerText?.trim() || '';
        result.push({img, title});
      }
    });
    return result;
  }
  function showOrHideField(spanId, value) {
    const span = document.getElementById(spanId);
    if (!span) return;
    const li = span.closest('li');
    if (value && value.trim() !== '') {
      span.textContent = value;
      if(li) li.style.display = '';
    } else {
      span.textContent = '';
      if(li) li.style.display = 'none';
    }
  }
  function updateSummaryCard() {
    const resumen = document.getElementById('booking-review-summary');
    if (!resumen) return;
    resumen.style.display = isLastStepActive() ? 'block' : 'none';
    // Date Selected (caso colocado en el h3, como en tu ejemplo)
    let dateStr = '', timeStr = '';
    const bookField = document.querySelector('[name="appointment_date"]');
    if (bookField && bookField.value) {
      try {
        let obj = JSON.parse(bookField.value)[0];
        dateStr = obj.friendlyDate || '';
        timeStr = obj.friendlyTime || '';
      } catch(e){}
    }
    document.getElementById('summary-date').textContent = dateStr;
    document.getElementById('summary-time').textContent = timeStr;

// Main Service const main = getMainService(); const mainUl = document.getElementById('summary-main-list'); mainUl.innerHTML = ''; if (main && (main.title || main.img)) { mainUl.innerHTML = `<li> <img src="${main.img || 'https://via.placeholder.com/50x50?text=Empty'}" alt="Main service" /> <span class="summary-service-title">${printOrEmpty(main.title)}</span> </li>`; } else { mainUl.innerHTML = ''; }

// Secondary Services const secondary = getSecondaryServices(); const secUl = document.getElementById('summary-secondary-list'); secUl.innerHTML = ''; if (secondary.length) { secUl.innerHTML = secondary.map(serv => `<li> <img src="${serv.img || 'https://via.placeholder.com/50x50?text=Empty'}" alt="Secondary service" /> <span class="summary-service-title">${printOrEmpty(serv.title)}</span> </li>` ).join(''); } else { secUl.innerHTML = ''; }

// LOCATION/SCHEDULE const zonaSel = document.querySelector('[name="address_area"]'); const zonaTxt = zonaSel?.options[zonaSel.selectedIndex]?.text || ''; showOrHideField('summary-zona', zonaTxt); showOrHideField('summary-address-line', document.getElementById('address_line')?.value || ''); showOrHideField('summary-address', document.getElementById('address_notes')?.value || '');

¿Cómo conectar cada campo?

Cada campo que quieras mostrar en el resumen debe tener un <span id="summary-xxxx">. El JavaScript busca el valor desde el formulario y lo inserta allí.
Ejemplo:
Si tu formulario tiene un campo con id customer_name (input para el nombre del usuario), el resumen debe contener <span id="summary-name"></span>,
y el JS hace:
showOrHideField('summary-name', document.getElementById('customer_name')?.value || '');

¿Cómo agregar, quitar o cambiar campos?

  • Para agregar un campo:
    • Agrega un <li><span>Etiqueta:</span> <span id="summary-nuevo"></span></li> en el HTML.
    • Agrega la línea equivalente en tu JS, por ejemplo: showOrHideField('summary-nuevo', document.getElementById('campo_nuevo')?.value || '');
  • Para quitar un campo:
    • Borra el <li>...</li> correspondiente del resumen HTML.
    • Borra o comenta la línea respectiva en el JS.
  • Para cambiar etiqueta/orden:
    • Ajusta sólo el HTML, no es necesario cambiar el JS si los id coinciden.

Tip: El script no depende del idioma ni del nombre, solo del id del <span> y del input en el formulario.
Puedes adaptar esto a cualquier campo y tipo de dato.

🎯 Notas Finales y Mejoras

Este método es escalable, fácil de mantener y 100% seguro ante futuros cambios de JetFormBuilder o JetAppointment. Además, los campos vacíos simplemente no aparecen, el usuario solo ve lo que de verdad llenó.