Restaurantes con huerto propio en Mallorca: de la tierra a la mesa

¿Buscas una experiencia gastronómica en Mallorca que vaya más allá del plato? Los restaurantes con huerto propio en la isla han convertido la cocina de proximidad en una declaración de principios: cada verdura, cada hierba aromática y cada fruta llega a la mesa desde la misma tierra que rodea el comedor. Es la cadena corta en su expresión más pura, y Mallorca es uno de los mejores escenarios del Mediterráneo para vivirla.

Por qué los restaurantes con huerto propio marcan la diferencia

La diferencia entre un restaurante con huerto y uno que simplemente compra producto local no es solo logística: es filosófica. Cuando el chef cultiva sus propios ingredientes, el menú lo dicta la tierra, no el mercado. El cocinero sabe exactamente en qué punto de maduración está cada tomate, cuándo cortar la albahaca para que conserve todo su aceite esencial y qué variedad de lechuga aguanta mejor el calor de agosto.

Este modelo de cocina de proximidad reduce drásticamente la huella de carbono del plato, elimina intermediarios y garantiza una frescura que ninguna cadena de distribución puede replicar. Para el comensal, el resultado es inmediato: sabores más intensos, texturas más vivas y una conexión real con el territorio que se percibe desde el primer bocado. Los restaurantes con huerto logran este resultado precisamente porque el ingrediente nunca abandona la finca antes de estar en el plato.

En Mallorca, la tradición agrícola —el cultivo del olivar, la huerta del Raiguer, los frutales del interior— ofrece el contexto perfecto para que este modelo florezca. No es una tendencia importada: es un regreso a cómo siempre se comió en la isla. La biodiversidad agrícola de la isla, con variedades autóctonas como el tomate ramallet, el pimiento de tap de cortí o la berenjena mallorquina, ofrece a estos chefs una paleta de ingredientes que ningún proveedor externo puede replicar. Por eso los restaurantes con huerto mallorquines tienen una autenticidad difícil de encontrar en otros destinos mediterráneos.

Calendario de temporada: qué esperar en cada estación

Uno de los grandes atractivos de los restaurantes con huerto en Mallorca es que el menú cambia con el calendario agrícola, no con las tendencias del mercado. Entender qué produce el huerto en cada época del año ayuda a elegir el mejor momento para visitar cada establecimiento.

  • Primavera (marzo-mayo): los huertos mallorquines explotan con guisantes, habas tiernas, espárragos silvestres, acelgas y las primeras lechugas de hoja. Es la estación de mayor variedad y frescura, con menús de temporada que celebran el verde. Los restaurantes con huerto propio en esta época ofrecen quizás su propuesta más vibrante del año.
  • Verano (junio-agosto): protagonismo absoluto del tomate ramallet —variedad autóctona ideal para conservar y para el pa amb oli—, los pimientos de tap de cortí, las berenjenas mallorquinas, el calabacín y las hierbas aromáticas en su punto máximo de aceite esencial. La temporada alta turística coincide con la cosecha más generosa.
  • Otoño (septiembre-noviembre): llegan las setas, las granadas, los higos tardíos, las primeras coles y los pimientos secos. Es también la época de la recogida de la aceituna en fincas como Treurer, lo que convierte el AOVE de temporada en protagonista de la mesa. Los restaurantes con huerto y olivar propio brillan especialmente en este periodo.
  • Invierno (diciembre-febrero): los productos de invierno —coles, alcachofas, naranjas amargas, hinojo silvestre— dan paso a menús más reconfortantes. Algunos restaurantes con huerto reducen su oferta o cierran temporalmente; conviene consultar disponibilidad antes de reservar.

Conocer este calendario permite anticipar qué ingredientes propios protagonizarán el menú de temporada en cada visita, y convierte la elección del mes en parte de la experiencia gastronómica. Visitar restaurantes con huerto en distintas estaciones es, en sí mismo, una forma de leer el paisaje mallorquín.

Qanat de Treurer: el huerto como alma del restaurante

Qanat es, sin duda, uno de los restaurantes con huerto más singulares de Mallorca. Ubicado en Finca Treurer, en Algaida, en pleno Pla de Mallorca, nació del deseo de la familia Miralles Gutiérrez de abrir el paisaje, la historia y la cocina de la finca a quienes buscan una experiencia auténtica. El nombre remite a la antigua acequia árabe del siglo XII que durante generaciones abasteció de agua la finca: un guiño a la relación más primaria entre tierra y sustento.

En Qanat se trabaja casi exclusivamente con productos locales y de temporada, aprovechando el huerto y los árboles frutales de la propia finca. Verduras, hortalizas y hierbas aromáticas cultivadas a metros del comedor llegan a la cocina con una frescura imposible de conseguir de otra forma. Es el mismo aceite de oliva virgen extra que el chef José Cortés usa en Qanat para aliñar, cocinar y terminar cada plato de la carta estacional. Como restaurante con huerto integrado en una finca productiva, Qanat representa un modelo especialmente coherente.

Bajo la dirección del chef José Cortés, Qanat ofrece una cocina mallorquina de autor y sostenible. El aceite de oliva virgen extra Treurer —Arbequina, producido con aceitunas recogidas en su punto óptimo de maduración— no es un condimento más: es el hilo conductor que une el huerto, el olivar y la mesa. Una mesa con vistas al olivar y al valle de Randa, con vinos mallorquines y una cocina que respeta el calendario: así es Qanat, uno de los restaurantes con huerto más coherentes de la isla.

Ficha práctica: Qanat de Treurer
Dirección Ctra. Llucmajor–Algaida, km 5,7 — Algaida (Mallorca)
Reservas +34 651 696 322 · treurer.com
Precio orientativo €€€ (menú degustación)
Apertura Temporada alta (consultar disponibilidad en invierno)
Comensales externos Sí, con reserva previa (solo mayores de edad)

Andreu Genestra: un huerto amplio con estrella Michelin

En el interior de la isla, el restaurante Andreu Genestra representa uno de los proyectos más ambiciosos de cocina de proximidad en Mallorca. Galardonado con una estrella de la Guía Michelin y dos soles de la Guía Repsol, el chef mallorquín lleva más de una década trabajando en casa tras pasar por cocinas internacionales de referencia. Entre los restaurantes con huerto de alto nivel de las Baleares, Andreu Genestra es una referencia ineludible.

Uno de sus grandes atractivos es el amplio huerto que rodea el restaurante, que lo convierte en uno de los espacios más destacados vinculados a un restaurante de autor en las Baleares. Para sus elaboraciones, Genestra trabaja con el concepto «Mediterranean», con menús degustación que combinan alimentos locales —muchos producidos en su propio huerto, de kilómetro 0— con las recetas más tradicionales de la región. El chef ha declarado públicamente que «el huerto no es un complemento del restaurante, es su razón de ser»: una filosofía que se traduce en menús donde cada plato puede rastrearse hasta el surco concreto donde creció el ingrediente principal.

El restaurante ha recibido también la estrella verde Michelin por su compromiso con la sostenibilidad gastronómica. Este reconocimiento, que la guía reserva para los establecimientos más comprometidos con la agricultura ecológica certificada y la cadena corta, convierte a Andreu Genestra en una referencia ineludible para cualquier viajero que quiera entender cómo los restaurantes con huerto pueden alcanzar la excelencia sin renunciar a sus raíces.

Ficha práctica: Andreu Genestra
Dirección Carretera Palma-Artà, km 49 — Capdepera (Mallorca)
Reservas andreugenestra.com
Precio orientativo €€€ (menú degustación)
Apertura Temporada alta; consultar cierre invernal
Comensales externos Sí, con reserva previa

Maca de Castro: locavore antes de que existiera la palabra

Si hay una chef que encarna la filosofía del ingrediente propio en Mallorca, esa es Macarena de Castro. Reconocida con estrella Michelin y con la estrella verde que premia su compromiso con la sostenibilidad, Maca de Castro está considerada un referente de la alta cocina balear y española. Su propuesta es uno de los ejemplos más puros de lo que puede conseguir un restaurante con huerto cuando la chef lo entiende como centro de su identidad.

Locavore antes de tiempo, Maca de Castro recorre la isla en búsqueda de los mejores ingredientes: la lonja del Puerto de Sóller para el caproig, el puerto de Alcúdia para el marisco, y las verduras de su propio huerto en Sa Pobla. Su menú degustación se crea cada mañana con lo que ofrece el huerto y los mejores pescados de la bahía. No hay carta fija: el huerto dicta el menú del día. La chef ha explicado en entrevistas que «los productos de Mallorca son los protagonistas de mi cocina, me inspiran y son parte de nuestra identidad» —una declaración que resume mejor que cualquier descripción editorial la esencia de este proyecto.

El menú sorpresa de degustación es creativo e inspirado en el Mediterráneo, con foco en los mejores ingredientes de temporada de Mallorca, la mayoría procedentes de subastas cercanas, pequeños productores de la isla o del huerto propio en Sa Pobla. Una propuesta que convierte cada visita en una experiencia irrepetible, porque ningún día es igual al anterior. Entre los restaurantes con huerto de la isla, el de Maca de Castro destaca por la intensidad con que el jardín cultivado se refleja en cada plato.

Ficha práctica: Maca de Castro
Dirección Port d’Alcúdia (Mallorca)
Reservas macadecastro.com
Precio orientativo €€€ (menú degustación)
Apertura Temporada alta; consultar fechas exactas
Comensales externos Sí, con reserva previa

Jacaranda en Finca Serena: dos huertos ecológicos en el interior

En Montuïri, en el corazón del Pla de Mallorca, el restaurante Jacaranda del hotel-resort Finca Serena ofrece una de las experiencias de cadena corta más completas de la isla. La finca cuenta con dos huertos ecológicos que proveen de verduras y aceite de sus propios olivares, en un entorno que transporta a un interior de la isla que se alimentaba de los productos de la tierra antes de que el turismo lo transformara todo. Pocos restaurantes con huerto ecológico en Mallorca pueden presumir de esta doble estructura de cultivo.

La propuesta de Jacaranda no separa el ingrediente del paisaje: comer aquí es también entender cómo era Mallorca antes de la costa, cuando cada finca era un ecosistema autosuficiente. Jacaranda es, en ese sentido, un restaurante con huerto que hace de la autosuficiencia agrícola un argumento estético y gastronómico al mismo tiempo. La barbacoa bajo las estrellas de los jueves, que se celebra en una terraza mirando a la Serra de Tramuntana, es otra de las experiencias destacadas de este hotel-restaurante.

Para quienes no se alojan en el hotel, existe la posibilidad de acceder al restaurante mediante un day pass que combina comida, spa y piscina. Una fórmula que democratiza el acceso a una experiencia gastronómica de primer nivel sin renunciar a la autenticidad de los ingredientes propios.

Ficha práctica: Jacaranda (Finca Serena)
Dirección Camí de Sabor, s/n — Montuïri (Mallorca)
Reservas fincaserena.com
Precio orientativo €€€ (day pass disponible)
Apertura Abierto todo el año (consultar periodos)
Comensales externos Sí, mediante day pass o reserva de restaurante

S’Era de Pula: verduras ecológicas junto al campo de golf

En Son Servera, en el levante de la isla, S’Era de Pula es uno de esos restaurantes con huerto que llevan décadas demostrando que la sostenibilidad gastronómica no es una moda reciente. El lugar es un pequeño pueblo completamente restaurado donde conviven un campo de golf, una huerta, un restaurante y un hotel. Como restaurante con huerto propio de larga trayectoria, S’Era de Pula ha construido una reputación basada en la coherencia y la constancia.

Todas las verduras son ecológicas, procedentes de un huerto situado junto al restaurante. La carta ofrece productos locales y verduras de su propia huerta, con una cocina sabrosa y muy cuidada. La cocina de proximidad aquí no es un concepto: es geografía. S’Era de Pula lleva tanto tiempo apostando por el huerto propio que bien puede considerarse uno de los precursores de esta tendencia en Mallorca.

Ficha práctica: S’Era de Pula
Dirección Ctra. Son Servera–Capdepera, km 3 — Son Servera (Mallorca)
Reservas serapula.com
Precio orientativo €€
Apertura Temporada alta; consultar invierno
Comensales externos Sí, abierto al público general

Qué hace especiales a los restaurantes con huerto en Mallorca

Mallorca tiene condiciones excepcionales para que este modelo de restauración prospere. El clima mediterráneo permite cultivar casi todo el año; la tradición agrícola de la isla —olivares, almendros, hortalizas del Raiguer— ofrece una base varietal extraordinaria; y la demanda de turistas y residentes con interés en experiencias auténticas crea el mercado necesario para sostener proyectos de alta exigencia. Los restaurantes con huerto encuentran aquí, por tanto, un ecosistema ideal. No es casualidad que algunos de los mejores restaurantes con huerto de España se localicen precisamente en Mallorca.

La cadena corta como garantía de calidad

En un restaurante con huerto propio, la cadena entre el cultivo y el plato se reduce a horas, no a días. Esto tiene consecuencias directas en el sabor: las vitaminas y los aromas volátiles de las hierbas aromáticas se degradan rápidamente tras la cosecha, y ningún sistema de refrigeración puede compensar ese proceso. Un tomate recogido por la mañana y servido al mediodía es un producto radicalmente distinto al que ha pasado por cámara frigorífica y transporte. Cualquier comensal que haya visitado un restaurante con huerto lo confirma desde el primer bocado.

La cocina de proximidad también permite al chef trabajar con variedades autóctonas que no tienen salida comercial en los circuitos convencionales: tomates ramallet, pimientos de tap de cortí, berenjenas mallorquinas. Ingredientes propios que definen la identidad gastronómica de la isla y que difícilmente aparecen en restaurantes que dependen de proveedores externos. Preservar esta biodiversidad agrícola es, además, un acto de conservación cultural: cada variedad local que un chef planta en su huerto es una variedad que no desaparece del paisaje mallorquín. Esta es una de las razones por las que los restaurantes con huerto en Mallorca resultan tan difíciles de replicar fuera del contexto insular.

Sostenibilidad gastronómica más allá del huerto

Los restaurantes con huerto en Mallorca entienden la sostenibilidad gastronómica de forma integral. No se trata solo de cultivar: se trata de cerrar el ciclo. Los restos orgánicos vuelven al compost, el agua de riego se gestiona con criterio, las variedades locales se preservan frente a la homogeneización del mercado y los productores cercanos se integran en la red de abastecimiento cuando el huerto propio no cubre toda la demanda. Algunos establecimientos, como Andreu Genestra, han obtenido certificaciones de agricultura ecológica que avalan este compromiso más allá del discurso. Estos restaurantes con huerto certificado representan la cumbre del movimiento de sostenibilidad gastronómica en las Baleares.

Este enfoque conecta directamente con los valores del territorio mallorquín: una isla que ha aprendido a vivir de sus recursos y que encuentra en la gastronomía uno de sus mejores embajadores culturales. Comer en un restaurante con huerto propio en Mallorca es, en ese sentido, un acto de turismo responsable y de apoyo a la identidad local.

Cómo elegir el restaurante con huerto que mejor se adapta a ti

La oferta de restaurantes con huerto en Mallorca es variada, y cada establecimiento tiene un perfil diferente. Antes de reservar, conviene tener claros algunos criterios:

  • Tipo de experiencia: ¿buscas alta cocina de autor con menú degustación, o prefieres una propuesta más informal y accesible? Qanat, Andreu Genestra y Maca de Castro apuestan por menús cerrados de temporada; S’Era de Pula ofrece carta convencional con ingredientes propios a un precio más accesible. Todos son, en esencia, restaurantes con huerto, pero con registros muy distintos.
  • Ubicación: el interior de la isla —Algaida, Montuïri, Son Servera— concentra la mayor densidad de fincas con huerto. Si te alojas en la costa, calcula el desplazamiento como parte de la experiencia. Maca de Castro, en el Port d’Alcúdia, es la opción más cómoda para quienes se alojan en el norte.
  • Temporada: la mayoría de estos restaurantes con huerto adaptan su oferta al calendario agrícola. Consulta siempre si están abiertos en la fecha de tu visita, especialmente fuera de temporada alta. El invierno reduce la oferta disponible.
  • Reserva anticipada: los restaurantes con huerto propio suelen tener aforos limitados y alta demanda. Reservar con días o semanas de antelación es imprescindible, especialmente entre junio y septiembre.
  • Acceso sin alojamiento: Jacaranda (Finca Serena) ofrece day pass; el resto son accesibles como comensales externos con reserva previa. Ninguno de los cinco establecimientos exige pernoctar para comer.

El aceite de oliva virgen extra Treurer, producido en la misma finca donde nació Qanat, es un buen indicador del nivel de exigencia que estos establecimientos aplican a cada ingrediente. Cuando el AOVE que aliña tus verduras proviene de los mismos olivos que ves desde la mesa, la experiencia adquiere una coherencia que va más allá de la gastronomía. Puedes conocer más sobre la finca Treurer y su historia para entender el contexto que rodea a Qanat. En este sentido, los mejores restaurantes con huerto de Mallorca comparten esa misma voluntad de coherencia total entre entorno, cultivo y cocina.

Preguntas frecuentes

¿Qué es exactamente un restaurante con huerto propio?

Es un establecimiento que cultiva parte o la totalidad de sus verduras, hortalizas y hierbas aromáticas en un terreno propio o vinculado directamente al restaurante. A diferencia de los restaurantes que compran producto local a proveedores externos, aquí la cadena entre el cultivo y el plato se reduce a horas, lo que garantiza una frescura y un control sobre el ingrediente imposibles de replicar de otra forma.

¿En qué zona de Mallorca se concentran más estos restaurantes?

El interior de la isla —el Pla de Mallorca, la comarca del Raiguer y el levante— concentra la mayor densidad de restaurantes con huerto propio, ya que disponen de espacio y de una tradición agrícola sólida. Algaida, Montuïri y Son Servera son municipios de referencia, aunque también existen propuestas en el norte, como Maca de Castro en el Port d’Alcúdia.

¿Es necesario reservar con mucha antelación?

Sí. Los restaurantes con huerto propio en Mallorca suelen tener aforos reducidos y una demanda alta, especialmente en temporada alta (junio-septiembre). Para establecimientos como Qanat de Treurer, Andreu Genestra o Maca de Castro, se recomienda reservar con un mínimo de una semana de antelación, y más en fechas especiales.

¿Qué diferencia hay entre cocina de proximidad y cocina con ingredientes propios?

La cocina de proximidad o kilómetro 0 implica abastecerse de productores locales en un radio reducido. La cocina con ingredientes propios va un paso más allá: el restaurante cultiva directamente sus ingredientes, eliminando cualquier intermediario. Ambos modelos comparten valores de sostenibilidad gastronómica y cadena corta, pero el huerto propio ofrece un control total sobre la variedad, el punto de maduración y el método de cultivo.

¿Qué papel juega el aceite de oliva en estos restaurantes?

En Mallorca, el aceite de oliva virgen extra es mucho más que un condimento: es el nexo entre la tierra y la cocina. En restaurantes como Qanat de Treurer, el AOVE producido en la propia finca se usa para aliñar, cocinar y terminar cada plato, convirtiéndose en el hilo conductor de toda la propuesta gastronómica. Un aceite de calidad, como el Treurer Arbequina, transforma un plato sencillo de huerto en algo verdaderamente memorable.

Fuentes

Reservar mesa ▲

Reserva tu mesa en Qanat

Selecciona fecha, hora y número de comensales

1
Reserva tu mesa
2
Pagar y confirmar
Reserva tu mesa
Comensales
Fecha
Hora

Selecciona una fecha para ver los horarios disponibles

Tus datos
Deseo mesa compartida
Confirma tu reserva
Comensales
—
Fecha
—
Hora
—
Nombre
—
Email
—
Teléfono
—
Mesa compartida
—
Observaciones
—
Garantía con tarjeta
Se requiere un depósito para confirmar tu reserva
0.00 €
Reembolsable si cancelas con 24h de antelación
Datos de la tarjeta
Acepto las condiciones de reserva y la política de privacidad
Condiciones de reserva

Reserva y puntualidad 
Mantendremos tu mesa reservada durante 15 minutos de cortesía. Si prevés llegar más tarde, te agradeceremos que nos lo comuniques. En caso contrario, no podremos garantizar la disponibilidad de la reserva.

Cancelaciones
Las cancelaciones realizadas con menos de 24 horas de antelación no serán reembolsables en ningún caso.

Edad mínima
El acceso está permitido únicamente a mayores de 14 años.

Mascotas
Las mascotas son bienvenidas en nuestra terraza, siempre que permanezcan limpias, tranquilas, atadas y bajo la supervisión de su responsable, evitando en todo momento molestias al resto de los clientes. Nos reservamos el derecho de asignar la mesa que consideremos más adecuada. Durante la temporada de invierno, te rogamos que nos consultes antes de realizar la reserva.

Código de vestimenta
Agradecemos una vestimenta smart casual: elegante, cómoda e informal.

Convivencia
Te rogamos mantener un ambiente tranquilo, evitando alzar la voz y mostrando respeto tanto hacia los demás clientes como hacia nuestro equipo. Está permitido fumar únicamente en la zona habilitada para ello.

Restricciones alimentarias
Si tienes alguna alergia, intolerancia o restricción alimentaria, te agradeceremos que nos lo comuniques antes de tu llegada. Una vez iniciado el servicio, no podemos garantizar que sea posible realizar modificaciones en el menú.

Con el fin de preservar el ambiente y el bienestar de todos los presentes, nos reservamos el derecho de solicitar el abandono del establecimiento en caso de comportamientos que alteren la convivencia o el normal desarrollo del servicio.

Procesando...

Gestionar reserva

← Volver a reservar

Selección del menú

Entrantes
Principales
Postres

Modificar reserva

Selección del menú

'; }).join(''); var offset = (new Date(flavorCalYear, flavorCalMonth, 1).getDay() + 6) % 7; var dim = new Date(flavorCalYear, flavorCalMonth + 1, 0).getDate(); for (var i = 0; i < offset; i++) h += '
'; var minStr = formatDateLocal(flavorMinDate); var maxStr = formatDateLocal(flavorMaxDate); for (var d = 1; d <= dim; d++) { var date = new Date(flavorCalYear, flavorCalMonth, d); var dateStr = formatDateLocal(date); var isSel = flavorData.date === dateStr; var isToday = date.toDateString() === today.toDateString(); var isPast = dateStr < minStr || dateStr > maxStr; var isClosed = !isPast && flavorOpenDays.indexOf(date.getDay()) === -1; var isBlocked = !isPast && !isClosed && flavorBlockedDates[dateStr]; var cls = 'flavor-cal-day'; if (isPast) cls += ' past'; else if (isClosed || isBlocked) cls += ' closed'; else if (isSel) cls += ' on'; else if (isToday) cls += ' today'; if (isPast || isClosed || isBlocked) { h += '
' + d + '
'; } else { h += ''; } } grid.innerHTML = h; grid.querySelectorAll('.flavor-cal-day[data-date]').forEach(function(b) { b.addEventListener('click', function() { flavorData.date = b.dataset.date; flavorData.time = null; flavorRenderCalendar(); flavorLoadTimeSlots(flavorData.date); }); }); } document.getElementById('flavorCalPrev').addEventListener('click', function() { var now = new Date(); if (flavorCalMonth <= now.getMonth() && flavorCalYear <= now.getFullYear()) return; flavorCalMonth--; if (flavorCalMonth < 0) { flavorCalMonth = 11; flavorCalYear--; } flavorRenderCalendar(); }); document.getElementById('flavorCalNext').addEventListener('click', function() { flavorCalMonth++; if (flavorCalMonth > 11) { flavorCalMonth = 0; flavorCalYear++; } flavorRenderCalendar(); }); // ===================== // TIME SLOTS // ===================== function flavorLoadTimeSlots(dateStr) { var container = document.getElementById('flavorSlots'); var guests = flavorData.guests || 2; container.innerHTML = '

' + flavorI18n.loadingTimes + '

'; fetch(flavorRestUrl + 'restaurants/' + flavorRestaurantId + '/available-slots?date=' + dateStr + '&guests=' + guests) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success && data.data && data.data.length > 0) { var html = ''; data.data.forEach(function(slot) { var timeShort = slot.time.substring(0, 5); if (slot.available) { var cls = 'flavor-slot' + (flavorData.time === timeShort ? ' on' : ''); html += ''; } else { var reasonText = flavorI18n.notAvailable; if (slot.reason === 'capacity') reasonText = flavorI18n.capacityFull; else if (slot.reason === 'max_reservations') reasonText = flavorI18n.maxReservations; else if (slot.reason === 'min_advance') reasonText = flavorI18n.minAdvance; html += ''; } }); container.innerHTML = html; container.querySelectorAll('.flavor-slot:not(.busy)').forEach(function(b) { b.addEventListener('click', function() { flavorData.time = b.dataset.t; container.querySelectorAll('.flavor-slot').forEach(function(s) { s.classList.remove('on'); }); b.classList.add('on'); flavorCheckStep1(); }); }); } else { container.innerHTML = '

' + flavorI18n.noTimesAvailable + '

'; } }) .catch(function() { container.innerHTML = '

' + flavorI18n.errorLoadingTimes + '

'; }); } // ===================== // CHIPS (Guests) // ===================== document.getElementById('flavorChips').addEventListener('click', function(e) { var c = e.target.closest('.flavor-chip'); if (!c) return; flavorData.guests = parseInt(c.dataset.val); document.querySelectorAll('.flavor-chip').forEach(function(x) { x.classList.remove('on'); }); c.classList.add('on'); if (flavorData.date) { flavorData.time = null; flavorLoadTimeSlots(flavorData.date); } flavorCheckStep1(); }); // ===================== // SHARED TABLE TOGGLE // ===================== var sharedToggle = document.getElementById('flavorSharedToggle'); if (sharedToggle) { sharedToggle.addEventListener('click', function() { flavorData.shared = !flavorData.shared; sharedToggle.classList.toggle('on', flavorData.shared); }); } // ===================== // VALIDATIONS // ===================== function flavorCheckStep1() { var hasBooking = !!(flavorData.date && flavorData.time); if (flavorTotalSteps === 2) { // In 2-step mode, step 1 also contains data fields var hasData = ['flavor-first-name','flavor-last-name','flavor-email','flavor-phone'].every(function(id) { return document.getElementById(id).value.trim() !== ''; }); document.getElementById('flavorBtn1Next').disabled = !(hasBooking && hasData); } else { document.getElementById('flavorBtn1Next').disabled = !hasBooking; } } function flavorCheckStep2() { var ok = ['flavor-first-name','flavor-last-name','flavor-email','flavor-phone'].every(function(id) { return document.getElementById(id).value.trim() !== ''; }); var btn2 = document.getElementById('flavorBtn2Next'); if (btn2) btn2.disabled = !ok; } function flavorCheckStep3() { var chk = document.getElementById('flavor-accept-terms'); // El checkbox solo existe si procede; si no está, el botón queda disponible document.getElementById('flavorBtn3Confirm').disabled = chk ? !chk.checked : false; } ['flavor-first-name','flavor-last-name','flavor-email','flavor-phone'].forEach(function(id) { document.getElementById(id).addEventListener('input', function() { flavorCheckStep2(); if (flavorTotalSteps === 2) flavorCheckStep1(); }); }); (function(){ var chk = document.getElementById('flavor-accept-terms'); if (chk) chk.addEventListener('change', flavorCheckStep3); })(); function flavorValidateStep2() { var fn = document.getElementById('flavor-first-name').value.trim(); var ln = document.getElementById('flavor-last-name').value.trim(); var em = document.getElementById('flavor-email').value.trim(); var ph = document.getElementById('flavor-phone').value.trim(); if (!fn) { flavorToast(flavorI18n.nameRequired); return false; } if (!ln) { flavorToast(flavorI18n.lastNameRequired); return false; } if (!em) { flavorToast(flavorI18n.emailRequired); return false; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em)) { flavorToast(flavorI18n.emailInvalid); return false; } if (!ph) { flavorToast(flavorI18n.phoneRequired); return false; } return true; } // ===================== // SUMMARY // ===================== function flavorBuildSummary() { document.getElementById('sumGuests').textContent = flavorData.guests + ' ' + (flavorData.guests === 1 ? flavorI18n.person : flavorI18n.people); if (flavorData.date) { var parts = flavorData.date.split('-'); var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); var dn = flavorI18n.dayNames[d.getDay()].toLowerCase(); document.getElementById('sumDate').textContent = dn + ', ' + parseInt(parts[2]) + ' de ' + flavorI18n.months[parseInt(parts[1]) - 1].toLowerCase() + ' ' + parts[0]; } document.getElementById('sumTime').textContent = flavorData.time ? flavorData.time + 'h' : '\u2014'; var fn = document.getElementById('flavor-first-name').value.trim(); var ln = document.getElementById('flavor-last-name').value.trim(); document.getElementById('sumName').textContent = (fn + ' ' + ln).trim() || '\u2014'; document.getElementById('sumEmail').textContent = document.getElementById('flavor-email').value.trim() || '\u2014'; var pre = document.getElementById('flavor-prefix').value; var ph = document.getElementById('flavor-phone').value.trim(); document.getElementById('sumPhone').textContent = ph ? pre + ph : '\u2014'; document.getElementById('sumShared').textContent = flavorData.shared ? flavorI18n.yes : flavorI18n.no; document.getElementById('sumNotes').textContent = document.getElementById('flavor-notes').value.trim() || '\u2014'; var depositAmount = flavorCalculateDeposit(); var depositBox = document.getElementById('flavorDepositBox'); var cardBox = document.getElementById('flavorCardInputBox'); if (depositAmount > 0 && flavorStripeKey) { depositBox.style.display = 'block'; cardBox.style.display = 'block'; document.getElementById('flavorDepAmount').textContent = depositAmount.toFixed(2) + ' \u20AC'; var perPerson = flavorDepositPerPerson > 0 ? (flavorData.guests + ' \u00D7 ' + flavorDepositPerPerson.toFixed(2) + ' \u20AC ' + flavorI18n.perPerson) : ''; document.getElementById('flavorDepSub').textContent = perPerson; document.getElementById('flavor-submit-text').style.display = 'none'; document.getElementById('flavor-submit-text-pay').style.display = 'inline'; } else { depositBox.style.display = 'none'; cardBox.style.display = 'none'; document.getElementById('flavor-submit-text').style.display = 'inline'; document.getElementById('flavor-submit-text-pay').style.display = 'none'; } flavorGuaranteeRender(); } // ===================== // NAVIGATION BUTTONS // ===================== document.getElementById('flavorBtn1Next').addEventListener('click', function() { if (!flavorData.guests) { flavorToast(flavorI18n.selectGuests); return; } if (!flavorData.date) { flavorToast(flavorI18n.selectDate); return; } if (!flavorData.time) { flavorToast(flavorI18n.selectTime); return; } if (flavorTotalSteps === 2 && !flavorValidateStep2()) return; flavorGoStep(2); }); var btn2Back = document.getElementById('flavorBtn2Back'); if (btn2Back) btn2Back.addEventListener('click', function() { flavorGoStep(1); }); var btn2Next = document.getElementById('flavorBtn2Next'); if (btn2Next) btn2Next.addEventListener('click', function() { if (!flavorValidateStep2()) return; flavorGoStep(3); }); document.getElementById('flavorBtn3Back').addEventListener('click', function() { flavorGoStep(flavorTotalSteps === 2 ? 1 : 2); }); document.getElementById('flavorBtn3Confirm').addEventListener('click', function() { flavorSubmit(); }); // Stepper click document.querySelectorAll('.flavor-step-ind').forEach(function(el) { el.addEventListener('click', function() { var target = parseInt(el.dataset.step); if (target < flavorStep) flavorGoStep(target); }); }); // ===================== // DEPOSIT & STRIPE // ===================== function flavorCalculateDeposit() { if (!flavorRequireDeposit) return 0; return flavorDepositPerReservation + (flavorDepositPerPerson * (flavorData.guests || 0)); } function flavorGuaranteeTotal() { if (!flavorGuarantee || flavorRequireDeposit || !flavorStripeKey) return 0; var g = flavorData.guests || 0; return g >= flavorGuaranteeMin ? flavorGuaranteePP * g : 0; } function flavorMoney(v) { var s = v.toFixed(2); return (flavorGI18n.lang === 'en' ? s : s.replace('.', ',')) + ' \u20AC'; } function flavorGuaranteeRender() { var box = document.getElementById('flavorGuaranteeBox'); if (!box) return; var total = flavorGuaranteeTotal(); box.parentNode.classList.toggle('has-guarantee', total > 0); if (total <= 0) { box.style.display = 'none'; return; } var tpl = flavorGuaranteeHours > 0 ? flavorGI18n.terms : flavorGI18n.termsNoHours; document.getElementById('flavorGTerms').textContent = tpl.replace('{hours}', flavorGuaranteeHours).replace('{pp}', flavorMoney(flavorGuaranteePP)).replace('{total}', flavorMoney(total)); box.style.display = 'block'; document.getElementById('flavorCardInputBox').style.display = 'block'; } function flavorSubmitWithGuarantee() { var acc = document.getElementById('flavor-guarantee-accept'); if (acc && !acc.checked) { flavorToast(flavorGI18n.acceptRequired); return; } if (!flavorStripe || !flavorCardElement) { flavorToast(flavorGI18n.error); return; } var submitBtn = document.getElementById('flavorBtn3Confirm'); var oldHtml = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = '' + flavorGI18n.processing + ''; var postData = flavorGetPostData(); var customerName = (postData.first_name + ' ' + postData.last_name).trim(); var fail = function(msg) { submitBtn.disabled = false; submitBtn.innerHTML = oldHtml; var e = document.getElementById('flavor-card-errors'); if (e) e.textContent = msg || flavorGI18n.error; flavorToast(msg || flavorGI18n.error); }; fetch(flavorRestUrl + 'guarantee/setup-intent', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ shortcode_key: flavorShortcodeKey, guests: flavorData.guests, name: customerName, email: postData.email }) }) .then(function(r) { return r.json(); }) .then(function(d) { if (!d || !d.success) throw new Error((d && d.message) || flavorGI18n.error); return flavorStripe.confirmCardSetup(d.client_secret, { payment_method: { card: flavorCardElement, billing_details: { name: customerName, email: postData.email } } }); }) .then(function(res) { if (res.error) throw new Error(res.error.message); postData.guarantee_setup_intent = res.setupIntent.id; postData.guarantee_accepted = 1; document.querySelector('.flavor-loader').style.display = 'flex'; return fetch(flavorRestUrl + 'reservations', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(postData) }); }) .then(function(r) { return r.json(); }) .then(function(r) { document.querySelector('.flavor-loader').style.display = 'none'; if (!r.success) throw new Error(r.message || flavorI18n.errorCreate); submitBtn.disabled = false; submitBtn.innerHTML = oldHtml; flavorShowSuccess(r.reservation.reservation || r.reservation); }) .catch(function(err) { document.querySelector('.flavor-loader').style.display = 'none'; fail(err && err.message); }); } function flavorInitStripe() { var depositAmount = flavorCalculateDeposit(); if ((depositAmount > 0 || flavorGuaranteeTotal() > 0) && flavorStripeKey && !flavorStripe) { flavorStripe = Stripe(flavorStripeKey); var elements = flavorStripe.elements({locale: "es"}); flavorCardElement = elements.create('card', { style: { base: { fontSize: '14px', color: '#1a1a1a', fontFamily: 'Inter, -apple-system, sans-serif', '::placeholder': { color: '#c0c0c0' } }, invalid: { color: '#dc2626' } } }); flavorCardElement.mount('#flavor-card-element'); flavorCardElement.on('change', function(event) { flavorCardComplete = !!event.complete; flavorCardError = event.error ? event.error.message : ''; document.getElementById('flavor-card-errors').textContent = flavorCardError; }); } } // ===================== // SUBMIT // ===================== function flavorSubmit() { var _chk = document.getElementById('flavor-accept-terms'); if (_chk && !_chk.checked) { flavorToast(flavorI18n.acceptTerms); return; } // In 2-step mode, validate step2 fields before submit if (flavorTotalSteps === 2 && !flavorValidateStep2()) return; if (flavorGuaranteeTotal() > 0) { flavorSubmitWithGuarantee(); return; } var depositAmount = flavorCalculateDeposit(); if (depositAmount > 0 && flavorStripeKey && flavorCardElement) { flavorSubmitWithPayment(); } else { flavorSubmitWithoutPayment(); } } function flavorGetPostData() { var prefix = document.getElementById('flavor-prefix').value; var phone = document.getElementById('flavor-phone').value.trim(); return { restaurant_id: flavorRestaurantId, shortcode_key: flavorShortcodeKey, guests: flavorData.guests, reservation_date: flavorData.date, reservation_time: flavorData.time, first_name: document.getElementById('flavor-first-name').value, last_name: document.getElementById('flavor-last-name').value, email: document.getElementById('flavor-email').value, phone: prefix + phone, allergies: document.getElementById('flavor-allergies').value, dietary_restrictions: '', notes: document.getElementById('flavor-notes').value, shared_table: flavorData.shared ? 1 : 0, // Idioma del formulario: decide el idioma de los emails de la reserva y del cliente lang: "es" }; } function flavorSubmitWithoutPayment() { document.querySelector('.flavor-loader').style.display = 'flex'; fetch(flavorRestUrl + 'reservations', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(flavorGetPostData()) }) .then(function(r) { return r.json(); }) .then(function(r) { document.querySelector('.flavor-loader').style.display = 'none'; if (r.success) { flavorShowSuccess(r.reservation.reservation || r.reservation); } else { flavorShowError(r.message || flavorI18n.errorCreate); } }) .catch(function() { document.querySelector('.flavor-loader').style.display = 'none'; flavorShowError(flavorI18n.error); }); } // Tarjeta completa (lo dice Stripe Elements) y cobro pendiente reutilizable: cada intento fallido // (número incompleto, tarjeta rechazada…) ya no crea un pago nuevo «Incompleto» en Stripe. var flavorCardComplete = false, flavorCardError = '', flavorPendingIntent = null; function flavorSubmitWithPayment() { if (!flavorCardComplete) { var msg = flavorCardError || "Completa los datos de la tarjeta"; document.getElementById('flavor-card-errors').textContent = msg; flavorToast(msg); return; } var submitBtn = document.getElementById('flavorBtn3Confirm'); submitBtn.disabled = true; submitBtn.innerHTML = '' + flavorI18n.paymentProcessing + ''; var postData = flavorGetPostData(); var customerName = postData.first_name + ' ' + postData.last_name; var customerEmail = postData.email; var intentSig = [flavorRestaurantId, flavorData.guests, customerEmail].join('|'); var intentReq = (flavorPendingIntent && flavorPendingIntent.sig === intentSig) ? Promise.resolve({ success: true, data: { client_secret: flavorPendingIntent.secret } }) : fetch(flavorRestUrl + 'payment/create-intent', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ restaurant_id: flavorRestaurantId, guests: flavorData.guests, name: customerName, email: customerEmail }) }).then(function(resp) { return resp.json(); }); intentReq .then(function(paymentData) { if (!paymentData.success) throw new Error(paymentData.message || flavorI18n.errorPayment); flavorPendingIntent = { sig: intentSig, secret: paymentData.data.client_secret }; return flavorStripe.confirmCardPayment(paymentData.data.client_secret, { payment_method: { card: flavorCardElement, billing_details: { name: customerName, email: customerEmail } } }).then(function(result) { if (result.error) { // Ya cobrado (p. ej. doble clic): se sigue con la reserva con ese mismo pago if (result.error.payment_intent && result.error.payment_intent.status === 'succeeded') return result.error.payment_intent.id; throw new Error(result.error.message); } flavorPendingIntent = null; return result.paymentIntent.id; }); }) .then(function(paymentIntentId) { postData.payment_intent_id = paymentIntentId; postData.deposit_paid = 1; return fetch(flavorRestUrl + 'reservations', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(postData) }); }) .then(function(response) { return response.json(); }) .then(function(r) { if (!r.success) throw new Error(r.message || flavorI18n.errorCreate); submitBtn.disabled = false; flavorShowSuccess(r.reservation.reservation || r.reservation); }) .catch(function(error) { submitBtn.disabled = false; submitBtn.innerHTML = '' + flavorI18n.paymentProcessing + ''; document.getElementById('flavor-card-errors').textContent = error.message; flavorToast(error.message); }); } // ===================== // SUCCESS / ERROR // ===================== // Ejecuta un snippet de tracking pegado por el restaurante desde el backoffice. // El snippet se sirve como
'; }; var rows = row(flavorI18n.name, esc(postData.first_name) + ' ' + esc(postData.last_name)) + row(flavorI18n.email, esc(postData.email)) + row(flavorI18n.phone, esc(postData.phone)) + row(flavorI18n.sharedTable, flavorData.shared ? flavorI18n.yes : flavorI18n.no); if (postData.allergies && postData.allergies.trim()) rows += '
' + String(flavorI18n.allergies).replace(/:\s*$/, '') + '' + esc(postData.allergies) + '
'; if (postData.notes && postData.notes.trim()) rows += row(flavorI18n.notes, esc(postData.notes)); var depositAmount = flavorCalculateDeposit(); if (depositAmount > 0) rows += row(flavorI18n.depositPaid, depositAmount.toFixed(2) + ' €'); if (flavorGuaranteeTotal() > 0) rows += row(flavorGI18n.row, esc(flavorGI18n.saved)); // Calendario: .ics (Apple, Outlook, móvil) y Google Calendar. Duración orientativa de 2 h. var calHtml = ''; if (d && flavorData.time) { var hm = String(flavorData.time).split(':'), start = new Date(d.getTime()); start.setHours(+hm[0], +hm[1] || 0, 0, 0); var end = new Date(start.getTime() + 2 * 3600000); var f = function(x) { var p = function(n) { return (n < 10 ? '0' : '') + n; }; return x.getFullYear() + p(x.getMonth() + 1) + p(x.getDate()) + 'T' + p(x.getHours()) + p(x.getMinutes()) + '00'; }; var title = (flavorI18n.okAt || '%s').replace('%s', R.name || ''); var descr = flavorI18n.okCode + ': ' + code + ' · ' + flavorData.guests + ' ' + flavorI18n.people; var icsEsc = function(s) { return String(s || '').replace(/([,;\\])/g, '\\$1').replace(/\n/g, '\\n'); }; var ics = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//bookings.rest//ES', 'BEGIN:VEVENT', 'UID:' + code + '@bookings.rest', 'DTSTAMP:' + f(new Date()), 'DTSTART:' + f(start), 'DTEND:' + f(end), 'SUMMARY:' + icsEsc(title), 'LOCATION:' + icsEsc(R.address || ''), 'DESCRIPTION:' + icsEsc(descr), 'END:VEVENT', 'END:VCALENDAR'].join('\r\n'); var icsUrl = 'data:text/calendar;charset=utf-8,' + encodeURIComponent(ics); var gUrl = 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=' + encodeURIComponent(title) + '&dates=' + f(start) + '/' + f(end) + '&details=' + encodeURIComponent(descr) + '&location=' + encodeURIComponent(R.address || ''); calHtml = '' + flavorI18n.okCalendar + '' + 'Google Calendar'; } var place = ''; if (R.name || R.address) { place = '

' + esc(R.name || '') + '' + (R.address ? '
' + esc(R.address) : '') + (R.maps ? ' · ' + flavorI18n.okDirections + '' : '') + '

'; } flavorContainer.innerHTML = '
' + '
' + '

' + flavorI18n.reservationConfirmed + '

' + place + '
' + '
' + flavorI18n.date.replace(/:$/, '') + '' + esc(dateStr) + '
' + '
' + flavorI18n.time.replace(/:$/, '') + '' + esc(flavorData.time || '') + '
' + '
' + flavorI18n.guests.replace(/:$/, '') + '' + esc(flavorData.guests) + '
' + '
' + '
' + flavorI18n.okCode + '' + esc(code) + '' + '
' + '
' + rows + '
' + '

' + (flavorI18n.okMail || '').replace('%s', '' + esc(postData.email) + '') + '

' + '
' + calHtml + '' + '
' + '
'; var cp = document.getElementById('flavorOkCopy'); if (cp) cp.addEventListener('click', function() { var done = function() { cp.textContent = flavorI18n.okCopied; setTimeout(function() { cp.textContent = flavorI18n.okCopy; }, 2000); }; if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code).then(done, function() {}); }); try { flavorContainer.scrollIntoView({behavior: 'smooth', block: 'start'}); } catch (e) {} if (typeof flavorRunTrackingSnippet === 'function') { // Snippet de tracking "reserva creada" (Meta Pixel, GA, etc. según lo que haya configurado el restaurante) flavorRunTrackingSnippet('flavor-tracking-reservation'); // Si el depósito ya se ha cobrado en el mismo flujo, el evento de pago también corresponde ahora if (depositAmount > 0) flavorRunTrackingSnippet('flavor-tracking-payment'); } } function flavorShowError(message) { flavorContainer.innerHTML = '
' + '
\u2717
' + '

' + flavorI18n.error + '

' + '

' + message + '

' + '' + '
'; } // ===================== // TOAST // ===================== function flavorToast(message, type) { type = type || 'warning'; var existing = document.querySelector('.flavor-toast-modal'); var existingOverlay = document.querySelector('.flavor-toast-overlay'); if (existing) existing.remove(); if (existingOverlay) existingOverlay.remove(); var overlay = document.createElement('div'); overlay.className = 'flavor-toast-overlay'; document.body.appendChild(overlay); var toast = document.createElement('div'); toast.className = 'flavor-toast-modal'; toast.innerHTML = '
' + message + '
'; document.body.appendChild(toast); function closeToast() { toast.classList.remove('show'); overlay.classList.remove('show'); setTimeout(function() { toast.remove(); overlay.remove(); }, 250); } overlay.onclick = closeToast; setTimeout(function() { overlay.classList.add('show'); toast.classList.add('show'); }, 10); setTimeout(closeToast, 2500); } // ===================== // BLOCKED DATES & INIT // ===================== fetch(flavorRestUrl + 'restaurants/' + flavorRestaurantId + '/blocked-dates') .then(function(r) { return r.json(); }) .then(function(data) { if (data.success && data.data) { flavorBlockedDates = data.data; flavorRenderCalendar(); flavorSelectFirstAvailableDate(); } }) .catch(function() { flavorSelectFirstAvailableDate(); }); // Día pedido por URL (?date=AAAA-MM-DD, p. ej. desde el botón del email del menú del día). // Solo se usa si ese día se puede reservar; si no, se sigue con el primer día disponible. function flavorTryUrlDate() { var q; try { q = new URLSearchParams(window.location.search).get('date'); } catch (e) { return false; } if (!q || !/^\d{4}-\d{2}-\d{2}$/.test(q)) return false; var today = (typeof flavorTodayInTz === 'function') ? flavorTodayInTz() : new Date(); today.setHours(0, 0, 0, 0); var p = q.split('-'), d = new Date(+p[0], +p[1] - 1, +p[2]); var diff = Math.round((d - today) / 86400000); if (diff < 0 || diff > flavorMaxAdvanceDays) return false; if (flavorOpenDays.indexOf(d.getDay()) === -1 || flavorBlockedDates[q]) return false; if (diff === 0) { var hour = (typeof flavorGetTzParts === 'function') ? flavorGetTzParts(new Date()).hour : new Date().getHours(); if (24 - hour < flavorMinAdvanceHours) return false; } flavorCalMonth = d.getMonth(); flavorCalYear = d.getFullYear(); flavorSelectDate(q); return true; } function flavorSelectFirstAvailableDate() { if (flavorTryUrlDate()) return; var today = new Date(); today.setHours(0,0,0,0); for (var i = 0; i <= flavorMaxAdvanceDays; i++) { var check = new Date(today); check.setDate(today.getDate() + i); var dateStr = formatDateLocal(check); if (flavorOpenDays.indexOf(check.getDay()) === -1) continue; if (flavorBlockedDates[dateStr]) continue; if (i === 0) { var hoursLeft = 24 - new Date().getHours(); if (hoursLeft < flavorMinAdvanceHours) continue; } flavorSelectDate(dateStr); return; } } function flavorSelectDate(dateStr) { flavorData.date = dateStr; flavorData.time = null; flavorRenderCalendar(); flavorLoadTimeSlots(dateStr); } // ===================== // GESTION DE RESERVAS // ===================== var flavorCurrentReservation = null; var flavorManagementToken = ''; function flavorShowManage() { document.querySelectorAll('.flavor-panel').forEach(function(s) { s.style.display = 'none'; }); document.querySelector('.flavor-stepper').style.display = 'none'; document.querySelector('.flavor-manage-view').style.display = 'block'; document.querySelector('.flavor-manage-search').style.display = 'block'; document.querySelector('.flavor-manage-result').style.display = 'none'; document.querySelector('.flavor-manage-modify').style.display = 'none'; } function flavorHideManage() { document.querySelector('.flavor-manage-view').style.display = 'none'; document.querySelectorAll('.flavor-panel').forEach(function(s) { s.style.display = 'none'; }); document.getElementById('flavorPanel1').style.display = 'block'; document.getElementById('flavorPanel1').classList.add('active'); document.querySelector('.flavor-stepper').style.display = 'flex'; flavorStep = 1; flavorUpdateStepper(); flavorCurrentReservation = null; } function flavorManageTab(tab) { document.querySelectorAll('.flavor-manage-tab').forEach(function(t) { t.classList.remove('active'); }); document.querySelectorAll('.flavor-manage-tab-content').forEach(function(c) { c.classList.remove('active'); }); document.querySelector('.flavor-manage-tab-content[data-tab="' + tab + '"]').classList.add('active'); event.target.classList.add('active'); } function flavorSearchByCode() { var code = document.getElementById('flavor-manage-code').value.trim(); if (!code) { flavorToast(flavorI18n.enterCode); return; } flavorSearchReservation({ code: code }); } function flavorSearchByData() { var firstName = document.getElementById('flavor-manage-firstname').value.trim(); var lastName = document.getElementById('flavor-manage-lastname').value.trim(); var phone = document.getElementById('flavor-manage-phone').value.trim(); var date = document.getElementById('flavor-manage-date').value; var time = document.getElementById('flavor-manage-time').value; if (!firstName) { flavorToast(flavorI18n.enterName); return; } if (!lastName) { flavorToast(flavorI18n.enterLastName); return; } if (!phone) { flavorToast(flavorI18n.enterPhone); return; } if (!date) { flavorToast(flavorI18n.enterDate); return; } if (!time) { flavorToast(flavorI18n.enterTime); return; } flavorSearchReservation({ first_name: firstName, last_name: lastName, phone: phone, date: date, time: time + ':00' }); } function flavorSearchReservation(params) { document.querySelector('.flavor-loader').style.display = 'flex'; var url = flavorRestUrl + 'reservations/search?restaurant_id=' + flavorRestaurantId; if (params.code) { url += '&code=' + encodeURIComponent(params.code); } else { url += '&first_name=' + encodeURIComponent(params.first_name); url += '&last_name=' + encodeURIComponent(params.last_name); url += '&phone=' + encodeURIComponent(params.phone); url += '&date=' + encodeURIComponent(params.date); url += '&time=' + encodeURIComponent(params.time); } fetch(url) .then(function(r) { return r.json(); }) .then(function(data) { document.querySelector('.flavor-loader').style.display = 'none'; if (data.success && data.data) { flavorCurrentReservation = data.data; flavorManagementToken = data.management_token || ''; flavorShowReservationResult(data.data); } else { flavorToast(data.message || flavorI18n.reservationNotFound, 'error'); } }) .catch(function() { document.querySelector('.flavor-loader').style.display = 'none'; flavorToast(flavorI18n.searchError, 'error'); }); } function flavorShowReservationResult(res) { document.querySelector('.flavor-manage-search').style.display = 'none'; document.querySelector('.flavor-manage-result').style.display = 'block'; document.querySelector('.flavor-manage-modify').style.display = 'none'; var dateParts = res.reservation_date.split('-'); var dateStr = dateParts[2] + '/' + dateParts[1] + '/' + dateParts[0]; var statusText = flavorI18n.confirmed, statusClass = 'confirmed'; if (res.status === 'pending') { statusText = flavorI18n.pending; statusClass = 'pending'; } if (res.status === 'cancelled') { statusText = flavorI18n.cancelled; statusClass = 'cancelled'; } document.querySelector('.flavor-reservation-code').textContent = res.reservation_code; document.querySelector('.flavor-reservation-status').textContent = statusText; document.querySelector('.flavor-reservation-status').className = 'flavor-reservation-status ' + statusClass; document.querySelector('.flavor-reservation-date').textContent = dateStr; document.querySelector('.flavor-reservation-time').textContent = res.reservation_time.substring(0, 5); document.querySelector('.flavor-reservation-guests').textContent = res.guests + ' ' + flavorI18n.people; document.querySelector('.flavor-reservation-name').textContent = res.first_name + ' ' + res.last_name; document.querySelector('.flavor-reservation-email').textContent = res.email || ''; document.querySelector('.flavor-reservation-phone').textContent = res.phone || ''; var allergiesRow = document.querySelector('.flavor-reservation-allergies'); if (res.allergies && res.allergies.trim()) { allergiesRow.style.display = 'flex'; document.querySelector('.flavor-reservation-allergies-text').textContent = res.allergies; } else { allergiesRow.style.display = 'none'; } var dietaryRow = document.querySelector('.flavor-reservation-dietary'); if (res.dietary_restrictions && res.dietary_restrictions.trim()) { dietaryRow.style.display = 'flex'; document.querySelector('.flavor-reservation-dietary-text').textContent = res.dietary_restrictions; } else { dietaryRow.style.display = 'none'; } var notesRow = document.querySelector('.flavor-reservation-notes'); if (res.notes && res.notes.trim()) { notesRow.style.display = 'flex'; document.querySelector('.flavor-reservation-notes-text').textContent = res.notes; } else { notesRow.style.display = 'none'; } var menuSection = document.querySelector('.flavor-reservation-menu'); var hasMenu = (res.menu_starters && res.menu_starters.trim()) || (res.menu_mains && res.menu_mains.trim()) || (res.menu_desserts && res.menu_desserts.trim()); if (hasMenu) { menuSection.style.display = 'block'; var st = document.querySelector('.flavor-reservation-starters'); if (res.menu_starters && res.menu_starters.trim()) { st.style.display = 'block'; document.querySelector('.flavor-reservation-starters-text').textContent = res.menu_starters; } else { st.style.display = 'none'; } var mn = document.querySelector('.flavor-reservation-mains'); if (res.menu_mains && res.menu_mains.trim()) { mn.style.display = 'block'; document.querySelector('.flavor-reservation-mains-text').textContent = res.menu_mains; } else { mn.style.display = 'none'; } var ds = document.querySelector('.flavor-reservation-desserts'); if (res.menu_desserts && res.menu_desserts.trim()) { ds.style.display = 'block'; document.querySelector('.flavor-reservation-desserts-text').textContent = res.menu_desserts; } else { ds.style.display = 'none'; } } else { menuSection.style.display = 'none'; } var actionsDiv = document.querySelector('.flavor-manage-actions'); if (res.status === 'cancelled') { actionsDiv.style.display = 'none'; } else { actionsDiv.style.display = 'flex'; } } function flavorShowModify() { document.querySelector('.flavor-manage-result').style.display = 'none'; document.querySelector('.flavor-manage-modify').style.display = 'block'; var res = flavorCurrentReservation; var select = document.getElementById('flavor-modify-guests'); select.innerHTML = ''; for (var i = flavorMinGuests; i <= Math.min(flavorMaxGuests, 12); i++) { var opt = document.createElement('option'); opt.value = i; opt.textContent = i + ' ' + (i === 1 ? flavorI18n.person : flavorI18n.people); if (i === parseInt(res.guests)) opt.selected = true; select.appendChild(opt); } document.getElementById('flavor-modify-date').value = res.reservation_date; document.getElementById('flavor-modify-first-name').value = res.first_name || ''; document.getElementById('flavor-modify-last-name').value = res.last_name || ''; document.getElementById('flavor-modify-email').value = res.email || ''; document.getElementById('flavor-modify-phone').value = res.phone || ''; document.getElementById('flavor-modify-allergies').value = res.allergies || ''; document.getElementById('flavor-modify-dietary').value = res.dietary_restrictions || ''; document.getElementById('flavor-modify-notes').value = res.notes || ''; document.getElementById('flavor-modify-shared-table').checked = parseInt(res.shared_table) === 1; document.getElementById('flavor-modify-starters').value = res.menu_starters || ''; document.getElementById('flavor-modify-mains').value = res.menu_mains || ''; document.getElementById('flavor-modify-desserts').value = res.menu_desserts || ''; flavorLoadModifyTimeSlots(res.reservation_date); } function flavorLoadModifyTimeSlots(date) { var select = document.getElementById('flavor-modify-time'); select.innerHTML = ''; var guests = document.getElementById('flavor-modify-guests').value; fetch(flavorRestUrl + 'restaurants/' + flavorRestaurantId + '/available-slots?date=' + date + '&guests=' + guests) .then(function(r) { return r.json(); }) .then(function(data) { select.innerHTML = ''; if (data.success && data.data && data.data.length > 0) { data.data.forEach(function(slot) { if (slot.available) { var opt = document.createElement('option'); opt.value = slot.time.substring(0, 5); opt.textContent = slot.time.substring(0, 5); select.appendChild(opt); } }); var currentTime = flavorCurrentReservation.reservation_time; var hasCurrentTime = false; for (var i = 0; i < select.options.length; i++) { if (select.options[i].value === currentTime) { hasCurrentTime = true; break; } } if (!hasCurrentTime && date === flavorCurrentReservation.reservation_date) { var opt = document.createElement('option'); opt.value = currentTime; opt.textContent = currentTime.substring(0, 5) + ' ' + flavorI18n.current; opt.selected = true; select.insertBefore(opt, select.firstChild); } } else { select.innerHTML = ''; } }) .catch(function() { select.innerHTML = ''; }); } document.getElementById('flavor-modify-date').addEventListener('change', function() { flavorLoadModifyTimeSlots(this.value); }); function flavorBackToResult() { document.querySelector('.flavor-manage-modify').style.display = 'none'; document.querySelector('.flavor-manage-result').style.display = 'block'; } function flavorSaveModification() { if (!flavorCurrentReservation) return; var newTime = document.getElementById('flavor-modify-time').value; if (!newTime) { flavorToast(flavorI18n.selectTimeFirst); return; } document.querySelector('.flavor-loader').style.display = 'flex'; fetch(flavorRestUrl + 'reservations/' + flavorCurrentReservation.id + '/modify', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ management_token: flavorManagementToken, guests: parseInt(document.getElementById('flavor-modify-guests').value), reservation_date: document.getElementById('flavor-modify-date').value, reservation_time: newTime, first_name: document.getElementById('flavor-modify-first-name').value, last_name: document.getElementById('flavor-modify-last-name').value, email: document.getElementById('flavor-modify-email').value, phone: document.getElementById('flavor-modify-phone').value, allergies: document.getElementById('flavor-modify-allergies').value, dietary_restrictions: document.getElementById('flavor-modify-dietary').value, notes: document.getElementById('flavor-modify-notes').value, shared_table: document.getElementById('flavor-modify-shared-table').checked ? 1 : 0, menu_starters: document.getElementById('flavor-modify-starters').value, menu_mains: document.getElementById('flavor-modify-mains').value, menu_desserts: document.getElementById('flavor-modify-desserts').value }) }) .then(function(r) { return r.json(); }) .then(function(data) { document.querySelector('.flavor-loader').style.display = 'none'; if (data.success) { flavorCurrentReservation = data.data; flavorShowReservationResult(data.data); flavorToast(flavorI18n.modifySuccess, 'success'); } else { flavorToast(data.message || flavorI18n.modifyError, 'error'); } }) .catch(function() { document.querySelector('.flavor-loader').style.display = 'none'; flavorToast(flavorI18n.modifyError, 'error'); }); } function flavorCancelReservation() { if (!flavorCurrentReservation) return; var _bl = String(window.BR_DIALOG_LANG || 'es'); BRDialog.confirm(flavorI18n.cancelConfirm, {danger: true, ok: ({es: 'Sí, cancelar', ca: 'Sí, cancel·lar', en: 'Yes, cancel', de: 'Ja, stornieren'})[_bl] || 'Sí, cancelar', cancel: ({es: 'No', ca: 'No', en: 'No', de: 'Nein'})[_bl] || 'No'}) .then(function(ok){ if (ok) flavorCancelReservationGo(); }); } function flavorCancelReservationGo() { document.querySelector('.flavor-loader').style.display = 'flex'; fetch(flavorRestUrl + 'reservations/' + flavorCurrentReservation.id + '/cancel-public', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ management_token: flavorManagementToken }) }) .then(function(r) { return r.json(); }) .then(function(data) { document.querySelector('.flavor-loader').style.display = 'none'; if (data.success) { flavorCurrentReservation.status = 'cancelled'; flavorShowReservationResult(flavorCurrentReservation); flavorToast(flavorI18n.cancelSuccess, 'success'); } else { flavorToast(data.message || flavorI18n.cancelError, 'error'); } }) .catch(function() { document.querySelector('.flavor-loader').style.display = 'none'; flavorToast(flavorI18n.cancelError, 'error'); }); } // == Init == flavorRenderCalendar(); flavorCheckStep1(); // Datos del cliente desde el botón del email del menú (?bk=código cifrado). El servidor lo canjea // por email, nombre y teléfono; solo se rellenan los campos vacíos. (function() { var k; try { k = new URLSearchParams(window.location.search).get('bk'); } catch (e) { return; } if (!k) return; fetch(flavorRestUrl + 'menu-prefill?k=' + encodeURIComponent(k) + '&r=' + encodeURIComponent(flavorRestaurantId)) .then(function(r) { return r.ok ? r.json() : null; }) .then(function(d) { if (!d || !d.success) return; function put(id, v) { var el = document.getElementById(id); if (!el || !v || el.value) return; el.value = v; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } put('flavor-email', d.email); put('flavor-first-name', d.first_name); put('flavor-last-name', d.last_name); var ph = String(d.phone || '').replace(/\s+/g, ''); if (ph) { var sel = document.getElementById('flavor-prefix'); if (sel && ph.charAt(0) === '+') { for (var i = 0; i < sel.options.length; i++) { var pre = sel.options[i].value || sel.options[i].text; if (ph.indexOf(pre) === 0) { sel.value = pre; ph = ph.slice(pre.length); break; } } } put('flavor-phone', ph); } }) .catch(function() {}); })(); // Detect manage_code in URL (function() { var urlParams = new URLSearchParams(window.location.search); var manageCode = urlParams.get('manage_code'); if (manageCode || window.location.hash === '#manage') { flavorShowManage(); setTimeout(function() { var el = document.getElementById('manage'); if (el) { window.scrollTo({ top: window.pageYOffset + el.getBoundingClientRect().top - 20, behavior: 'smooth' }); } }, 500); if (manageCode) { document.getElementById('flavor-manage-code').value = manageCode; setTimeout(function() { flavorSearchReservation({ code: manageCode }); }, 300); } } })(); // Detectar retorno de Stripe Checkout externo con ?payment=success (link de pago enviado por email). // Dispara el snippet de tracking de pago del depósito. Se ejecuta una sola vez y limpiamos el // parámetro de la URL para que un F5 no vuelva a disparar el evento. (function() { try { var params = new URLSearchParams(window.location.search); if (params.get('payment') === 'success') { flavorRunTrackingSnippet('flavor-tracking-payment'); // Limpiar los parámetros payment=success&code=... de la URL sin recargar if (window.history && window.history.replaceState) { params.delete('payment'); params.delete('code'); var qs = params.toString(); var cleanUrl = window.location.pathname + (qs ? '?' + qs : '') + window.location.hash; window.history.replaceState({}, document.title, cleanUrl); } } } catch (e) {} })();
Miguel Miralles

Miquel Miralles, Treurer's CEO, is part of the owner family, being already the second generation involved in the project. He has a background in economics and law, and has taken numerous courses related to foreign trade, olive agronomy and extra virgin olive oil production techniques. His training and experience of more than 15 years in the world of olive oil are her guarantee to lead this project.

Entradas recientes

  • Atractivos turísticos
  • Blog
  • Turismo

Cuevas de Porto Cristo: guía completa para una visita inolvidable

Descubre las cuevas de Porto Cristo: formación geológica, el lago Martel, horarios, precios y consejos…

7 días hace
  • Blog

Cala Bona: guía completa de la playa más tranquila del noreste de Mallorca

Todo sobre Cala Bona: playa, servicios, accesibilidad, precios, cómo llegar, buceo y qué ver en…

2 semanas hace
  • Blog
  • Turismo

Pollença marinera: puerto, pesca tradicional y gastronomía del norte de Mallorca

Descubre la Pollença marinera: pesca de bajura, platos con historia, restaurantes del muelle y el…

3 semanas hace
  • Atractivos turísticos
  • Blog
  • Turismo

Cómo llegar al Torrent de Pareis desde Palma: rutas, transporte y consejos

Cómo llegar al Torrent de Pareis desde Palma: rutas en coche, barco y transporte público,…

4 semanas hace
  • Sin categorizar

Diferencias entre aceite de oliva virgen extra, virgen y refinado y sus usos

Descubre las diferencias entre aceite de oliva virgen extra, virgen y refinado: clasificación oficial UE,…

1 mes hace
  • Blog
  • Gourmet
  • Sin categorizar

Cata de aceite de oliva virgen extra: guía sensorial para principiantes

Aprende a realizar una cata de aceite de oliva virgen extra paso a paso: fases…

1 mes hace