{"id":3785,"date":"2025-09-02T16:14:43","date_gmt":"2025-09-02T16:14:43","guid":{"rendered":"https:\/\/doctorasalva.es\/?page_id=3785"},"modified":"2025-09-17T12:12:28","modified_gmt":"2025-09-17T12:12:28","slug":"sistema-de-reservas","status":"publish","type":"page","link":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/","title":{"rendered":"Sistema de reservas"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"3785\" class=\"elementor elementor-3785\">\n\t\t\t\t<div class=\"elementor-element elementor-element-a2eedf6 e-flex e-con-boxed e-con e-parent\" data-id=\"a2eedf6\" data-element_type=\"container\" data-settings=\"{&quot;tc_container_hover_selector&quot;:&quot;container&quot;,&quot;tc_container_background_parallax&quot;:&quot;no&quot;,&quot;tcg_advanced_hover&quot;:&quot;no&quot;,&quot;tc_dark_mode_responsive_hide_in_dark&quot;:&quot;no&quot;,&quot;tc_dark_mode_responsive_hide_in_light&quot;:&quot;no&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-dd3fd75 elementor-widget elementor-widget-image\" data-id=\"dd3fd75\" data-element_type=\"widget\" data-settings=\"{&quot;tc_dark_mode_responsive_hide_in_dark&quot;:&quot;no&quot;,&quot;tc_dark_mode_responsive_hide_in_light&quot;:&quot;no&quot;}\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" width=\"467\" height=\"100\" data-src=\"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png\" class=\"attachment-large size-large wp-image-2234 lazyload\" alt=\"\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 467px; --smush-placeholder-aspect-ratio: 467\/100;\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-75887cb elementor-widget elementor-widget-html\" data-id=\"75887cb\" data-element_type=\"widget\" data-settings=\"{&quot;tc_dark_mode_responsive_hide_in_dark&quot;:&quot;no&quot;,&quot;tc_dark_mode_responsive_hide_in_light&quot;:&quot;no&quot;}\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<div class=\"booking-container\">\n  <!-- Barra de progreso -->\n  <div class=\"progress-bar\">\n      <div class=\"progress-step active\" id=\"step-1\" data-step=\"1\">Servicio<\/div>\n      <div class=\"progress-step\" id=\"step-2\" data-step=\"2\">Fecha y Hora<\/div>\n      <div class=\"progress-step\" id=\"step-3\" data-step=\"3\">Informaci\u00f3n<\/div>\n      <div class=\"progress-step\" id=\"step-4\" data-step=\"4\">Confirmaci\u00f3n<\/div>\n  <\/div>\n\n  <!-- Selecci\u00f3n de servicio -->\n  <div class=\"services-container active\" id=\"services-step\">\n      <h2>Selecciona un servicio<\/h2>\n      <!-- Los servicios se cargar\u00e1n din\u00e1micamente -->\n  <\/div>\n\n  <!-- Selecci\u00f3n de fecha y hora -->\n  <div class=\"calendar-container\" id=\"calendar-step\">\n      <h2>Selecciona fecha y hora<\/h2>\n      <div class=\"calendar-header\">\n          <button class=\"button button-secondary\" id=\"prev-month\"><i class=\"fas fa-chevron-left\"><\/i><\/button>\n          <h3 id=\"current-month\">Febrero 2024<\/h3>\n          <button class=\"button button-secondary\" id=\"next-month\"><i class=\"fas fa-chevron-right\"><\/i><\/button>\n      <\/div>\n      <div class=\"calendar-grid\" id=\"calendar-grid\">\n          <!-- Se generar\u00e1 din\u00e1micamente con JavaScript -->\n      <\/div>\n      <div class=\"time-slots\" id=\"time-slots\">\n          <h3>Horarios disponibles<\/h3>\n          <div class=\"time-grid\">\n              <!-- Se generar\u00e1 din\u00e1micamente con JavaScript -->\n          <\/div>\n      <\/div>\n  <\/div>\n\n  <!-- Formulario de informaci\u00f3n -->\n  <div class=\"form-container\" id=\"form-step\">\n      <h2>Completa tu informaci\u00f3n<\/h2>\n      <!-- El formulario se generar\u00e1 din\u00e1micamente con JavaScript -->\n  <\/div>\n\n  <!-- Confirmaci\u00f3n -->\n  <div class=\"confirmation-container\" id=\"confirmation-step\">\n      <!-- Se generar\u00e1 din\u00e1micamente con JavaScript -->\n  <\/div>\n\n  <!-- Botones de navegaci\u00f3n -->\n  <div class=\"button-container\">\n      <button class=\"button button-secondary\" id=\"back-button\" style=\"display: none;\">Atr\u00e1s<\/button>\n      <button class=\"button button-primary\" id=\"next-button\">Siguiente<\/button>\n  <\/div>\n<\/div>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-82bbbce elementor-widget elementor-widget-html\" data-id=\"82bbbce\" data-element_type=\"widget\" data-settings=\"{&quot;tc_dark_mode_responsive_hide_in_dark&quot;:&quot;no&quot;,&quot;tc_dark_mode_responsive_hide_in_light&quot;:&quot;no&quot;}\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script>\nconst API = {\n  URL: 'https:\/\/script.google.com\/macros\/s\/AKfycbwINQrEF2PC--koxyf1PAIZMIADt_FHT0oz3JxKXUMAKer24NFy5qPSL54ujsxGIX0D9A\/exec', \/\/ \ud83d\udd25 REEMPLAZAR URL APPSCRIPT\n\n  async get(action, params = {}) {\n      const controller = new AbortController();\n      const timeout = setTimeout(() => controller.abort(), 10000); \/\/ 10 segundos\n      try {\n          const queryParams = new URLSearchParams({ action, ...params }).toString();\n          const response = await fetch(`${this.URL}?${queryParams}`, {\n              signal: controller.signal\n          });\n          clearTimeout(timeout);\n          return response.json();\n      } catch (error) {\n          clearTimeout(timeout);\n          throw error;\n      }\n  },\n\n  async post(action, data) {\n      const response = await fetch(`${this.URL}?action=${action}`, {\n          method: 'POST',\n          body: JSON.stringify({ data: JSON.stringify(data) })\n      });\n      return response.json();\n  }\n};\n\nclass BookingSystem {\n  constructor() {\n      this.currentStep = 1;\n      this.maxSteps = 4;\n      this.bookingData = {\n          service: null,\n          date: null,\n          time: null,\n          userInfo: {}\n      };\n      this.currentDate = new Date();\n      this.selectedDate = null;\n\n      this.festiveDays = [];  \n      this.workingDays = []; \n      this.weekStartsOn = 1;\n\n      this.initializeElements();\n      this.loadServices();\n      this.loadConfiguration(); \n      this.attachEventListeners();\n      this.isLoading = false;\n  }\n\n  setLoading(loading) {\n      this.isLoading = loading;\n      this.nextButton.disabled = loading;\n      this.backButton.disabled = loading;\n  }\n\n  showAlert(message) {\n      const alertBox = document.createElement('div');\n      alertBox.style.cssText = `\n          position: fixed;\n          top: 50%;\n          left: 50%;\n          transform: translate(-50%, -50%);\n          padding: 20px;\n          background: white;\n          border-radius: 8px;\n          box-shadow: 0 2px 10px rgba(0,0,0,0.2);\n          z-index: 1000;\n          text-align: center;\n      `;\n      alertBox.innerHTML = `\n          <p style=\"margin-bottom: 15px\">${message}<\/p>\n          <button class=\"button button-primary\">Aceptar<\/button>\n      `;\n      document.body.appendChild(alertBox);\n      alertBox.querySelector('button').onclick = () => alertBox.remove();\n  }\n\n  loadConfiguration() {\n      API.get('getConfiguration')\n        .then(config => {\n            this.limiteReservaHoras = parseInt(config.limite_reserva_horas, 10) || 0;\n            this.festiveDays = config.dias_festivos || [];\n            this.workingDays = config.dias_laborables || [];\n            this.weekStartsOn = config.semana_comienza_en === undefined ? 1 : parseInt(config.semana_comienza_en, 10);\n            this.renderCalendar();\n        })\n        .catch(error => console.error('Error:', error));\n  }\n\n  loadFormFields() {\n    this.containers.form.innerHTML = `\n        <h2>Completa tu informaci\u00f3n<\/h2>\n        <div class=\"loader-container\">\n            <div class=\"loader\"><\/div>\n            <p>Cargando formulario...<\/p>\n        <\/div>`;\n    API.get('getFormFields')\n        .then(fields => {\n            this.renderFormFields(fields);\n        })\n        .catch(error => {\n            this.renderDefaultForm();\n        });\n  }\n\n  renderFormFields(fields) {\n    const formContainer = this.containers.form;\n    formContainer.innerHTML = '<h2>Completa tu informaci\u00f3n<\/h2>';\n    const form = document.createElement('form');\n    form.id = 'booking-form';\n    fields.forEach(field => {\n        const fieldGroup = document.createElement('div');\n        fieldGroup.className = 'form-group';\n        const label = document.createElement('label');\n        label.setAttribute('for', field.name);\n        label.textContent = field.label + (field.required ? ' *' : '');\n        let input;\n        if (field.type === 'textarea') {\n            input = document.createElement('textarea');\n            input.rows = 4;\n        } else {\n            input = document.createElement('input');\n            input.type = field.type;\n        }\n        input.id = field.name;\n        input.name = field.name;\n        input.placeholder = field.placeholder || '';\n        if (field.required) {\n            input.setAttribute('required', 'required');\n        }\n        input.addEventListener('keypress', function(e) {\n            if (e.key === 'Enter' && input.type !== 'textarea') {\n                e.preventDefault();\n            }\n        });\n        fieldGroup.appendChild(label);\n        fieldGroup.appendChild(input);\n        form.appendChild(fieldGroup);\n    });\n    const seguroDkvGroup = document.createElement('div');\n    seguroDkvGroup.className = 'form-group';\n    const seguroDkvLabel = document.createElement('label');\n    seguroDkvLabel.setAttribute('for', 'seguro_dkv');\n    seguroDkvLabel.textContent = 'Aseguradora *';\n    seguroDkvGroup.appendChild(seguroDkvLabel);\n    const seguroDkvSelect = document.createElement('select');\n    seguroDkvSelect.id = 'seguro_dkv';\n    seguroDkvSelect.name = 'seguro_dkv';\n    seguroDkvSelect.setAttribute('required', 'required');\n    const option1 = document.createElement('option');\n    option1.value = 'Reservo sin aseguradora';\n    option1.textContent = 'Reservo sin aseguradora';\n    seguroDkvSelect.appendChild(option1);\n    const option2 = document.createElement('option');\n    option2.value = 'DKV Seguros';\n    option2.textContent = 'DKV Seguros';\n    seguroDkvSelect.appendChild(option2);\n    seguroDkvGroup.appendChild(seguroDkvSelect);\n    form.appendChild(seguroDkvGroup);\n    formContainer.appendChild(form);\n  }\n\n  renderDefaultForm() {\n    const formContainer = this.containers.form;\n    formContainer.innerHTML = `\n        <h2>Completa tu informaci\u00f3n<\/h2>\n        <form id=\"booking-form\">\n            <div class=\"form-group\">\n                <label for=\"name\">Nombre *<\/label>\n                <input type=\"text\" id=\"name\" name=\"name\" required>\n            <\/div>\n            <div class=\"form-group\">\n                <label for=\"surname\">Apellidos *<\/label>\n                <input type=\"text\" id=\"surname\" name=\"surname\" required>\n            <\/div>\n            <div class=\"form-group\">\n                <label for=\"phone\">Tel\u00e9fono *<\/label>\n                <input type=\"tel\" id=\"phone\" name=\"phone\" required>\n            <\/div>\n            <div class=\"form-group\">\n                <label for=\"email\">Email *<\/label>\n                <input type=\"email\" id=\"email\" name=\"email\" required>\n            <\/div>\n        <\/form>`;\n  }\n\n  initializeElements() {\n      this.containers = {\n          services: document.getElementById('services-step'),\n          calendar: document.getElementById('calendar-step'),\n          form: document.getElementById('form-step'),\n          confirmation: document.getElementById('confirmation-step')\n      };\n      this.calendarGrid = document.getElementById('calendar-grid');\n      this.currentMonthElement = document.getElementById('current-month');\n      this.timeSlotsContainer = document.getElementById('time-slots');\n      this.timeGrid = this.timeSlotsContainer.querySelector('.time-grid');\n      this.backButton = document.getElementById('back-button');\n      this.nextButton = document.getElementById('next-button');\n      this.prevMonthButton = document.getElementById('prev-month');\n      this.nextMonthButton = document.getElementById('next-month');\n      this.renderCalendar();\n      this.loadFormFields();\n  }\n\n  loadServices() {\n      const container = document.getElementById('services-step');\n      container.innerHTML = `\n          <h2>Selecciona un servicio<\/h2>\n          <div class=\"loader-container\">\n              <div class=\"loader\"><\/div>\n              <p>Cargando servicios...<\/p>\n          <\/div>`;\n      API.get('getServices')\n          .then(response => {\n              this.renderServices(response);\n          })\n          .catch(error => {\n              container.innerHTML = `\n                  <h2>Error al cargar servicios<\/h2>\n                  <p>Por favor, intenta de nuevo m\u00e1s tarde.<\/p>\n              `;\n          });\n  }\n\n  renderServices(services) {\n    if (!Array.isArray(services)) {\n        return;\n    }\n    const container = document.getElementById('services-step');\n    container.innerHTML = '<h2>Selecciona un servicio<\/h2>';\n    services.forEach(service => {\n        if (service.activo) {\n            const serviceCard = document.createElement('div');\n            serviceCard.className = 'service-card fade-in-card';\n            serviceCard.setAttribute('data-service', service.id);\n            serviceCard.setAttribute('data-price', service.precio);\n            serviceCard.setAttribute('data-duration', service.duracion);\n            let serviceHTML = `\n                <div class=\"service-info\">\n                    <div class=\"service-icon\">\n                        ${service.imagen_url ? \n                            `<img decoding=\"async\" src=\"${service.imagen_url}\" alt=\"${service.nombre}\">` : \n                            service.nombre.charAt(0)}\n                    <\/div>\n                    <div class=\"service-details\">\n                        <h3>${service.nombre}<\/h3>`;\n            if (service.descripcion && service.descripcion.trim() !== '') {\n                serviceHTML += `<p class=\"service-description\">${service.descripcion}<\/p>`;\n            }\n            serviceHTML += `<span class=\"service-duration\">${service.duracion}min<\/span>\n                    <\/div>\n                <\/div>\n                <div class=\"service-price\">${service.precio}<\/div>`;\n            serviceCard.innerHTML = serviceHTML;\n            container.appendChild(serviceCard);\n        }\n    });\n    setTimeout(() => {\n        document.querySelectorAll('.fade-in-card').forEach(card => {\n            card.classList.add('fade-in-active');\n        });\n    }, 50);\n    this.attachServiceEvents();\n  }\n\n  attachEventListeners() {\n      this.backButton.addEventListener('click', () => this.navigateStep(-1));\n      this.nextButton.addEventListener('click', () => this.navigateStep(1));\n      this.prevMonthButton.addEventListener('click', () => this.changeMonth(-1));\n      this.nextMonthButton.addEventListener('click', () => this.changeMonth(1));\n  }\n\n  attachServiceEvents() {\n      const serviceCards = document.querySelectorAll('.service-card');\n      serviceCards.forEach(card => {\n          card.addEventListener('click', () => {\n              this.selectService(card);\n              window.scrollTo({top: 0, behavior: \"smooth\"});\n          });\n      });\n  }\n\n  selectService(card) {\n    document.querySelectorAll('.service-card').forEach(c => \n        c.style.borderColor = '#eee');\n    card.style.borderColor = 'var(--primary-color)';\n    const previousServiceId = this.bookingData.service?.id;\n    const newServiceId = card.dataset.service;\n    this.bookingData.service = {\n        id: newServiceId,\n        name: card.querySelector('h3').textContent,\n        price: card.dataset.price,\n        duration: card.dataset.duration\n    };\n    if (previousServiceId && previousServiceId !== newServiceId) {\n        this.bookingData.date = null;\n        this.bookingData.time = null;\n        this.selectedDate = null;\n        this.timeSlotsContainer.classList.remove('active');\n    }\n    this.nextButton.disabled = true;\n    setTimeout(() => {\n        this.navigateStep(1);\n        this.nextButton.disabled = false;\n    }, 200);\n }\n\nrenderCalendar() {\n  if (!this.festiveDays || !this.workingDays) {\n      return;\n  }\n  const ahora = new Date(); \n  const limiteHoras = parseInt(this.limiteReservaHoras, 10) || 0; \n  const year = this.currentDate.getFullYear();\n  const month = this.currentDate.getMonth();\n  const monthNames = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',\n                      'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];\n  this.currentMonthElement.textContent = `${monthNames[month]} ${year}`;\n  this.calendarGrid.innerHTML = '';\n  const allWeekDays = ['Dom', 'Lun', 'Mar', 'Mi\u00e9', 'Jue', 'Vie', 'S\u00e1b'];\n  const weekDays = [...allWeekDays.slice(this.weekStartsOn), ...allWeekDays.slice(0, this.weekStartsOn)];\n  weekDays.forEach(day => {\n      const dayElement = document.createElement('div');\n      dayElement.className = 'weekday';\n      dayElement.textContent = day;\n      this.calendarGrid.appendChild(dayElement);\n  });\n  const firstDay = new Date(year, month, 1);\n  let startingDay = firstDay.getDay() - this.weekStartsOn;\n  if (startingDay < 0) startingDay += 7;\n  for (let i = 0; i < startingDay; i++) {\n      const emptyDay = document.createElement('div');\n      emptyDay.className = 'calendar-day disabled';\n      this.calendarGrid.appendChild(emptyDay);\n  }\n  const daysInMonth = new Date(year, month + 1, 0).getDate();\n  for (let day = 1; day <= daysInMonth; day++) {\n      const dayElement = document.createElement('div');\n      dayElement.className = 'calendar-day';\n      dayElement.textContent = day;\n      const currentDate = new Date(year, month, day);\n      const dateStr = currentDate.toISOString().split('T')[0];\n      const diferenciaHoras = (currentDate - ahora) \/ (1000 * 60 * 60);\n      if (currentDate < ahora) {\n          dayElement.classList.add('disabled');\n      } else if (diferenciaHoras < limiteHoras) {\n          dayElement.classList.add('disabled');\n      } else if (this.festiveDays.includes(dateStr) || !this.workingDays.includes(currentDate.getDay())) {\n          dayElement.classList.add('disabled');\n      } else {\n          dayElement.classList.add('available');\n          dayElement.addEventListener('click', () => this.selectDate(currentDate, dayElement));\n      }\n      this.calendarGrid.appendChild(dayElement);\n  }\n}\n\nselectDate(date, element) {\n  document.querySelectorAll('.calendar-day').forEach(d => d.classList.remove('selected'));\n  element.classList.add('selected');\n  this.selectedDate = new Date(date);\n  this.bookingData.date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0);\n  this.timeGrid.innerHTML = `\n      <div class=\"loader-container\" style=\"display: flex; flex-direction: column; align-items: center; justify-content: center;\">\n          <div class=\"loader\"><\/div>\n          <p style=\"margin-top: 10px;\">Cargando horarios...<\/p>\n      <\/div>`;\n  this.timeGrid.style.display = 'flex';\n  this.timeGrid.style.justifyContent = 'center';\n  this.timeGrid.style.alignItems = 'center';\n  this.checkAvailability(date);\n  this.timeSlotsContainer.classList.add('active');\n}\n\ncheckAvailability(date) {\n  const dateObj = new Date(date);\n  const cleanDate = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate(), 12, 0, 0);\n  const dateStr = cleanDate.toISOString().split('T')[0];\n  API.get('checkAvailability', { \n      date: dateStr, \n      serviceId: this.bookingData.service.id \n  })\n      .then(slots => {\n          this.timeSlots = slots;\n          this.renderTimeSlots();\n      })\n      .catch(error => {});\n}\n\nrenderTimeSlots() {\n    this.timeGrid.innerHTML = \"\"; \n    this.timeGrid.style.display = 'grid'; \n    this.timeSlots.forEach(slot => {\n        const slotElement = document.createElement(\"div\");\n        slotElement.classList.add(\"time-slot\");\n        slotElement.textContent = slot.time;\n        if (slot.reserved) {\n            slotElement.classList.add(\"disabled\");\n        } else {\n            slotElement.classList.remove(\"disabled\");\n            slotElement.addEventListener(\"click\", () => {\n                this.selectTime(slotElement, slot.time);\n            });\n        }\n        this.timeGrid.appendChild(slotElement);\n    });\n}\n\nselectTime(slotElement, time) {\n    document.querySelectorAll('.time-slot').forEach(t => t.classList.remove('selected'));\n    slotElement.classList.add('selected');\n    this.bookingData.time = time;\n    this.nextButton.disabled = true;\n    setTimeout(() => {\n        this.navigateStep(1);\n        this.nextButton.disabled = false;\n    }, 200);\n}\n\nchangeMonth(direction) {\n    this.currentDate.setDate(1);\n    this.currentDate.setMonth(this.currentDate.getMonth() + direction);\n    this.renderCalendar();\n}\n\nnavigateStep(direction) {\n  const newStep = this.currentStep + direction;\n  if (newStep < 1 || newStep > this.maxSteps) return;\n  if (direction > 0) {\n    if (newStep === 2 && !this.bookingData.service) {\n        this.showAlert('Por favor, selecciona un servicio');\n        return;\n    }\n    if (newStep === 3 && !this.bookingData.time) {\n        this.showAlert('Por favor, selecciona una fecha y hora');\n        return;\n    }\n    if (newStep === 4 && !this.validateForm()) {\n        this.showAlert('Por favor, completa todos los campos requeridos');\n        return;\n    }\n }\n if (this.currentStep === 3 && newStep === 2 && !this.selectedDate) {\n      this.timeSlotsContainer.classList.remove('active');\n  }\n  const stepContainers = {\n      1: this.containers.services,\n      2: this.containers.calendar,\n      3: this.containers.form,\n      4: this.containers.confirmation\n  };\n  const currentContainer = stepContainers[this.currentStep];\n  const newContainer = stepContainers[newStep];\n  if (currentContainer) {\n      currentContainer.style.opacity = '0';\n      setTimeout(() => {\n          currentContainer.classList.remove('active');\n      }, 500);\n  }\n  setTimeout(() => {\n      if (newContainer) {\n          newContainer.classList.add('active');\n          newContainer.style.opacity = '1';\n          if (newStep === 4) {\n              this.submitForm();\n              this.containers.confirmation.style.display = 'block';\n          }\n      }\n  }, 500);\n  document.getElementById(`step-${this.currentStep}`).classList.remove('active');\n  document.getElementById(`step-${newStep}`).classList.add('active');\n  if (direction > 0) {\n      document.getElementById(`step-${this.currentStep}`).classList.add('completed');\n  } else {\n      document.getElementById(`step-${this.currentStep}`).classList.remove('completed');\n  }\n  this.backButton.style.display = newStep > 1 ? 'inline-block' : 'none';\n  this.nextButton.style.display = newStep === this.maxSteps ? 'none' : 'inline-block';\n  this.currentStep = newStep;\n\n  \/\/ Scroll autom\u00e1tico al top cada vez que cambias de paso\n  window.scrollTo({top: 0, behavior: \"smooth\"});\n}\n\nvalidateForm() {\n    const form = document.getElementById('booking-form');\n    const requiredFields = form.querySelectorAll('[required]');\n    let isValid = true;\n    requiredFields.forEach(field => {\n        if (!field.value.trim()) {\n            isValid = false;\n            field.classList.add('error');\n        } else {\n            field.classList.remove('error');\n        }\n    });\n    return isValid;\n}\n\nsubmitForm() {\n    const form = document.getElementById('booking-form');\n    const formElements = form.elements;\n    this.bookingData.userInfo = {};\n    for (let i = 0; i < formElements.length; i++) {\n        const element = formElements[i];\n        if (element.name && element.name !== '') {\n            this.bookingData.userInfo[element.name] = element.value;\n        }\n    }\n    this.showConfirmation();\n}\n\nshowConfirmation() {\n    const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };\n    const formattedDate = this.bookingData.date.toLocaleDateString('es-ES', dateOptions);\n    let confirmationHTML = `\n        <div class=\"confirmation-details\">\n            <h3><i class=\"fas fa-check-circle\"><\/i> Resumen de tu reserva<\/h3>\n            <p><i class=\"fas fa-star\"><\/i> <strong>Servicio:<\/strong> ${this.bookingData.service.name}<\/p>\n            <p><i class=\"fas fa-calendar\"><\/i> <strong>Fecha:<\/strong> ${formattedDate}<\/p>\n            <p><i class=\"far fa-clock\"><\/i> <strong>Hora:<\/strong> ${this.bookingData.time}<\/p>`;\n    if (this.bookingData.service.duration) {\n        confirmationHTML += `<p><i class=\"fas fa-hourglass-half\"><\/i> <strong>Duraci\u00f3n:<\/strong> ${this.bookingData.service.duration} minutos<\/p>`;\n    }\n    const precio = this.bookingData.service?.price;\n    if (precio && precio.trim() !== '') {\n        confirmationHTML += `<p><i class=\"fas fa-tag\"><\/i> <strong>Total:<\/strong> ${precio}<\/p>`;\n    }\n    confirmationHTML += `<div class=\"user-details\">\n            <p><i class=\"fas fa-user\"><\/i> <strong>Cliente:<\/strong> ${this.bookingData.userInfo.name} ${this.bookingData.userInfo.surname}<\/p>`;\n    if (this.bookingData.userInfo?.phone && this.bookingData.userInfo.phone.trim() !== '') {\n        confirmationHTML += `<p><i class=\"fas fa-phone\"><\/i> <strong>Tel\u00e9fono:<\/strong> ${this.bookingData.userInfo.phone}<\/p>`;\n    }\n    if (this.bookingData.userInfo?.email && this.bookingData.userInfo.email.trim() !== '') {\n        confirmationHTML += `<p><i class=\"fas fa-envelope\"><\/i> <strong>Email:<\/strong> ${this.bookingData.userInfo.email}<\/p>`;\n    }\n    Object.entries(this.bookingData.userInfo).forEach(([key, value]) => {\n        if (!['name', 'surname', 'email', 'phone'].includes(key) && value && value.trim() !== '') {\n            const fieldLabel = key.replace(\/_\/g, ' ').replace(\/\\b\\w\/g, l => l.toUpperCase());\n            confirmationHTML += `<p><i class=\"fas fa-info-circle\"><\/i> <strong>${fieldLabel}:<\/strong> ${value}<\/p>`;\n        }\n    });\n    confirmationHTML += `\n            <\/div>\n            <div class=\"terms-checkbox-container\">\n                <label class=\"terms-label\">\n                    <input type=\"checkbox\" id=\"terms-checkbox\"> \n                    Acepto los <a href=\"#\" class=\"terms-link\">t\u00e9rminos y condiciones<\/a>\n                <\/label>\n            <\/div>\n            <div class=\"whatsapp-button-container\" style=\"display:none;\">\n                <button id=\"whatsapp-button\" class=\"button button-whatsapp\" disabled>\n                    <i class=\"fab fa-whatsapp\"><\/i> Confirmar reserva v\u00eda WhatsApp\n                <\/button>\n                <div id=\"success-message\" class=\"success-message\" style=\"display: none;\">\n                    <i class=\"fas fa-check-circle\"><\/i> Reserva enviada con \u00e9xito\n                <\/div>\n            <\/div>\n            <div class=\"button-container\" style=\"justify-content:center; margin-top:24px;\">\n                <button id=\"confirm-reserva-btn\" class=\"button button-primary\">Confirmar Reserva<\/button>\n            <\/div>\n        <\/div>`;\n    this.containers.confirmation.innerHTML = confirmationHTML;\n    const termsCheckbox = document.getElementById('terms-checkbox');\n    const confirmBtn = document.getElementById('confirm-reserva-btn');\n    confirmBtn.disabled = true;\n    termsCheckbox.addEventListener('change', function() {\n        confirmBtn.disabled = !this.checked;\n    });\n    document.querySelector('.terms-link').addEventListener('click', (e) => {\n        e.preventDefault();\n        this.showTermsAndConditions();\n    });\n    confirmBtn.addEventListener('click', () => {\n        confirmBtn.disabled = true;\n        this.sendBookingEmailAndGoogle();\n    });\n}\n\nsendBookingEmailAndGoogle() {\n    \/\/ Construir el HTML igual que el resumen\n    const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };\n    const formattedDate = this.bookingData.date.toLocaleDateString('es-ES', dateOptions);\n    let htmlMsg = `\n        <h2 style=\"color:#E68D65; margin-bottom:18px;\">Confirmaci\u00f3n de Reserva<\/h2>\n        <ul style=\"font-size:16px; color:#333; margin-bottom:20px;\">\n            <li><strong>Servicio:<\/strong> ${this.bookingData.service.name}<\/li>\n            <li><strong>Fecha:<\/strong> ${formattedDate}<\/li>\n            <li><strong>Hora:<\/strong> ${this.bookingData.time}<\/li>\n            ${this.bookingData.service.duration ? `<li><strong>Duraci\u00f3n:<\/strong> ${this.bookingData.service.duration} minutos<\/li>` : ''}\n            ${this.bookingData.service.price ? `<li><strong>Total:<\/strong> ${this.bookingData.service.price}<\/li>` : ''}\n        <\/ul>\n        <h3 style=\"color:#E68D65;\">Datos del cliente<\/h3>\n        <ul style=\"font-size:15px; color:#444;\">\n            <li><strong>Nombre:<\/strong> ${this.bookingData.userInfo.name} ${this.bookingData.userInfo.surname}<\/li>\n            <li><strong>Tel\u00e9fono:<\/strong> ${this.bookingData.userInfo.phone}<\/li>\n            <li><strong>Email:<\/strong> ${this.bookingData.userInfo.email}<\/li>\n            ${\n                Object.entries(this.bookingData.userInfo).map(([key, value]) => {\n                    if (!['name','surname','phone','email'].includes(key) && value && value.trim() !== '') {\n                        const label = key.replace(\/_\/g, ' ').replace(\/\\b\\w\/g, l => l.toUpperCase());\n                        return `<li><strong>${label}:<\/strong> ${value}<\/li>`;\n                    }\n                    return '';\n                }).join('')\n            }\n        <\/ul>\n     <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"font-family: sans-serif; font-size: 14px; color: #000;\">\n  <tr>\n    <td style=\"padding-bottom: 10px;\">\n      <strong>Direcci\u00f3n:<\/strong><br>\n      Cl\u00ednica Dental Dra. Salv\u00e0 Molins<br>\n      Ronda del General Mitre 5, Sarri\u00e0-Sant Gervasi, Barcelona\n    <\/td>\n  <\/tr>\n  <tr>\n    <td style=\"padding-bottom: 10px;\">\n      <strong>Tel\u00e9fono:<\/strong><br>\n      <a href=\"tel:+34932800604\" style=\"color: #000; text-decoration: none;\">+34 932 800 604<\/a>\n    <\/td>\n  <\/tr>\n  <tr>\n    <td>\n      <strong>Mapa:<\/strong><br>\n      <a href=\"https:\/\/www.google.com\/maps\/search\/?api=1&query=41.3931503,2.1308064\" style=\"color: #000; text-decoration: none;\">Ver Mapa<\/a>\n    <\/td>\n  <\/tr>\n<\/table>\n              \n\n        <p style=\"margin-top:18px; color:#888;\">Cita generada desde el Sistema de Reservas de doctorasalva.es<\/p>\n    `;\n    const dataToSend = {\n        booking: this.bookingData,\n        html: htmlMsg\n    };\n    \/\/ Env\u00eda al endpoint WordPress REST API\n    fetch('\/wp-json\/reservas\/v1\/confirmar', {\n        method: 'POST',\n        headers: {'Content-Type':'application\/json'},\n        body: JSON.stringify(dataToSend)\n    })\n    .then(r => r.json())\n    .then(resp => {\n        if (resp.success) {\n            this.sendToGoogleSheets();\n            document.getElementById('confirm-reserva-btn').style.display = 'none';\n            this.showSuccessMsg('\u00a1Reserva confirmada! Hemos enviado un correo a tu email.');\n        } else {\n            this.showAlert('Ocurri\u00f3 un error al enviar el correo. Intenta de nuevo m\u00e1s tarde.');\n            document.getElementById('confirm-reserva-btn').disabled = false;\n        }\n    })\n    .catch(err => {\n        this.showAlert('No se pudo conectar con el servidor. Intenta de nuevo m\u00e1s tarde.');\n        document.getElementById('confirm-reserva-btn').disabled = false;\n    });\n}\n\nsendToGoogleSheets() {\n    \/\/ Env\u00eda la info a Google Apps Script para Sheets y Calendar\n    const bookingDataToSave = {\n        ...this.bookingData,\n        date: this.bookingData.date.toISOString().split('T')[0]\n    };\n    API.post('createBooking', bookingDataToSave)\n        .then(response => {\n            \/\/ Opcional: puedes mostrar mensaje si quieres\n        })\n        .catch(error => {\n            \/\/ Opcional: puedes mostrar alerta si quieres\n        });\n}\n\nshowSuccessMsg(msg) {\n    const div = document.createElement('div');\n    div.className = 'success-message';\n    div.innerHTML = `<i class=\"fas fa-check-circle\"><\/i> ${msg}`;\n    this.containers.confirmation.appendChild(div);\n}\n\nshowTermsAndConditions() {\n  const modal = document.createElement('div');\n  modal.className = 'terms-modal';\n  modal.innerHTML = `\n     <div class=\"terms-modal-content\">\n    <span class=\"close-modal\">\u00d7<\/span>\n    <h2>T\u00e9rminos y Condiciones<\/h2>\n    <div class=\"terms-content\">\n        <ol>\n            <li>Las reservas est\u00e1n sujetas a disponibilidad.<\/li>\n            <li>Se recomienda llegar 10 minutos antes de la hora programada.<\/li>\n            <li>En caso de cancelaci\u00f3n, por favor notifique con al menos 24 horas de anticipaci\u00f3n.<\/li>\n            <li>Las cancelaciones con menos de 24 horas de anticipaci\u00f3n pueden estar sujetas a un cargo.<\/li>\n            <li>Sus datos ser\u00e1n tratados de acuerdo con nuestra <a href=\"https:\/\/doctorasalva.es\/index.php\/legal\" target=\"_blank\">Pol\u00edtica de Privacidad<\/a>.<\/li>\n        <\/ol>\n    <\/div>\n    <p>Al hacer clic en \"Aceptar\", usted acepta nuestros T\u00e9rminos y Condiciones y la Pol\u00edtica de Privacidad.<\/p>\n    <button class=\"button button-primary close-terms\">Aceptar<\/button>\n<\/div>\n  `;\n  document.body.appendChild(modal);\n  const closeModal = () => {\n      modal.style.opacity = '0';\n      setTimeout(() => {\n          document.body.removeChild(modal);\n      }, 300);\n  };\n  modal.querySelector('.close-modal').addEventListener('click', closeModal);\n  modal.querySelector('.close-terms').addEventListener('click', closeModal);\n  modal.addEventListener('click', (e) => {\n      if (e.target === modal) {\n          closeModal();\n      }\n  });\n  setTimeout(() => {\n      modal.style.opacity = '1';\n  }, 10);\n}\n\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  window.bookingSystem = new BookingSystem();\n});\n<\/script>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Servicio Fecha y Hora Informaci\u00f3n Confirmaci\u00f3n Selecciona un servicio Selecciona fecha y hora Febrero 2024 Horarios disponibles Completa tu informaci\u00f3n Atr\u00e1s Siguiente<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"footnotes":""},"class_list":["post-3785","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Sistema de reservas - Doctora Salva<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Sistema de reservas - Doctora Salva\" \/>\n<meta property=\"og:description\" content=\"Servicio Fecha y Hora Informaci\u00f3n Confirmaci\u00f3n Selecciona un servicio Selecciona fecha y hora Febrero 2024 Horarios disponibles Completa tu informaci\u00f3n Atr\u00e1s Siguiente\" \/>\n<meta property=\"og:url\" content=\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/\" \/>\n<meta property=\"og:site_name\" content=\"Doctora Salva\" \/>\n<meta property=\"article:modified_time\" content=\"2025-09-17T12:12:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/\",\"url\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/\",\"name\":\"Sistema de reservas - Doctora Salva\",\"isPartOf\":{\"@id\":\"https:\/\/doctorasalva.es\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png\",\"datePublished\":\"2025-09-02T16:14:43+00:00\",\"dateModified\":\"2025-09-17T12:12:28+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage\",\"url\":\"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png\",\"contentUrl\":\"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png\",\"width\":467,\"height\":100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\/\/doctorasalva.es\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Sistema de reservas\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/doctorasalva.es\/#website\",\"url\":\"https:\/\/doctorasalva.es\/\",\"name\":\"Doctora Salva\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/doctorasalva.es\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Sistema de reservas - Doctora Salva","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/","og_locale":"es_ES","og_type":"article","og_title":"Sistema de reservas - Doctora Salva","og_description":"Servicio Fecha y Hora Informaci\u00f3n Confirmaci\u00f3n Selecciona un servicio Selecciona fecha y hora Febrero 2024 Horarios disponibles Completa tu informaci\u00f3n Atr\u00e1s Siguiente","og_url":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/","og_site_name":"Doctora Salva","article_modified_time":"2025-09-17T12:12:28+00:00","og_image":[{"url":"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png","type":"","width":"","height":""}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/","url":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/","name":"Sistema de reservas - Doctora Salva","isPartOf":{"@id":"https:\/\/doctorasalva.es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage"},"image":{"@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage"},"thumbnailUrl":"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png","datePublished":"2025-09-02T16:14:43+00:00","dateModified":"2025-09-17T12:12:28+00:00","breadcrumb":{"@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#primaryimage","url":"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png","contentUrl":"https:\/\/doctorasalva.es\/wp-content\/uploads\/2024\/12\/Logo_Dra.Salva_04.png","width":467,"height":100},{"@type":"BreadcrumbList","@id":"https:\/\/doctorasalva.es\/index.php\/sistema-de-reservas\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/doctorasalva.es\/"},{"@type":"ListItem","position":2,"name":"Sistema de reservas"}]},{"@type":"WebSite","@id":"https:\/\/doctorasalva.es\/#website","url":"https:\/\/doctorasalva.es\/","name":"Doctora Salva","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/doctorasalva.es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"}]}},"_links":{"self":[{"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/pages\/3785","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/comments?post=3785"}],"version-history":[{"count":91,"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/pages\/3785\/revisions"}],"predecessor-version":[{"id":3989,"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/pages\/3785\/revisions\/3989"}],"wp:attachment":[{"href":"https:\/\/doctorasalva.es\/index.php\/wp-json\/wp\/v2\/media?parent=3785"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}