diff --git a/README.md b/README.md index 26d82b7..153bbd2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# May Hair Care — Landing Page + EasyAppointments +# MAY Studio — Landing Page + EasyAppointments -Landing page bonita para May HairCare (Horta, Barcelona) con reservas gestionadas por **EasyAppointments**. +Landing page bonita para MAY Studio (Horta, Barcelona) con reservas gestionadas por **EasyAppointments**. El frontend (HTML/JS puro) sigue siendo el de la landing, pero **las reservas, clientes y servicios se gestionan completamente con EasyAppointments**. @@ -64,11 +64,13 @@ El formulario permite seleccionar **varios servicios** (multi-checkbox). Cómo funciona actualmente con EasyAppointments: - **UI**: Puedes seleccionar múltiples servicios. Se muestra el resumen con nombres unidos por "+" y la duración total sumada. -- **Comprobación de disponibilidad**: Se consulta el endpoint de EA usando el servicio de **mayor duración** de los seleccionados. EA devuelve los slots libres según ese servicio + el horario del proveedor. +- **Comprobación de disponibilidad**: Se consulta el endpoint de EA usando el servicio de **mayor duración** de los seleccionados. Luego se filtra client-side para exigir un bloque contiguo de la **duración total** (suma de todos). + - Ejemplo: servicios de 45min + 30min → total 75 min. Solo se ofrecen horas de inicio desde las que quepan 75 min seguidos sin solape. - **Al crear la reserva**: - Se crea **una sola cita** en EasyAppointments. - Se usa como `serviceId` el servicio de mayor duración (para que coincida con la disponibilidad consultada). - - Se calcula la hora de fin sumando la **duración total** de todos los servicios seleccionados. + - Se calcula la hora de fin sumando la **duración total** de todos los servicios seleccionados y se envía explícitamente el campo `end`. Así EA bloquea el tiempo completo en el calendario de la empleada. + - Solo una empleada (May) por ahora → el bloque queda reservado y no se puede reservar otra cosa en ese intervalo. - En el campo `notes` se guarda el detalle completo: ``` Nombre: ... diff --git a/docker-compose.yml b/docker-compose.yml index a722144..9a6b657 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,9 +6,9 @@ services: ports: - "8234:8080" environment: - - EA_API_USERNAME=admin # Usuario admin de EA (o mejor usa EA_API_KEY) - - EA_API_PASSWORD=yourpassword - # - EA_API_KEY=tu-api-key # Recomendado: crea API Key en Settings de EA + - EA_API_USERNAME=may # Usuario de EA (ver ea_user_settings.username del admin). Usa tu password real. + # EA_API_PASSWORD=yourpassword + - EA_API_KEY=***REMOVED*** # Recomendado: en el panel EA ve a Settings y configura "API Token", luego usa esta variable (mejor que user/pass) depends_on: - easyappointments diff --git a/main.go b/main.go index e9dd53e..d624cb9 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "embed" "flag" "fmt" + "io" "io/fs" "log" "net/http" @@ -37,6 +38,29 @@ func main() { } eaProxy := httputil.NewSingleHostReverseProxy(eaURL) + // Evita que el navegador muestre el popup de credenciales. + // Nunca exponemos WWW-Authenticate ni mensajes de auth del backend. + eaProxy.ModifyResponse = func(resp *http.Response) error { + resp.Header.Del("Www-Authenticate") + // Evitamos que cookies de sesión de EA lleguen al navegador del usuario público + resp.Header.Del("Set-Cookie") + // Limpiamos cabeceras que revelan el backend + resp.Header.Del("Server") + resp.Header.Del("X-Powered-By") + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + resp.StatusCode = http.StatusBadGateway + resp.Status = http.StatusText(http.StatusBadGateway) + // Reemplazamos el body para no filtrar detalles del backend + resp.Body.Close() + resp.Body = io.NopCloser(strings.NewReader(`{"error": "service_unavailable"}`)) + resp.ContentLength = -1 + resp.Header.Set("Content-Type", "application/json") + resp.Header.Del("Content-Length") + } + return nil + } + http.HandleFunc("/ea-api/", func(w http.ResponseWriter, r *http.Request) { // /ea-api/v1/services → /index.php/api/v1/services r.URL.Path = strings.Replace(r.URL.Path, "/ea-api", "/index.php/api", 1) diff --git a/media/work/IMG-20260304-WA0001.jpg b/media/work/IMG-20260304-WA0001.jpg new file mode 100644 index 0000000..76d690b Binary files /dev/null and b/media/work/IMG-20260304-WA0001.jpg differ diff --git a/media/work/IMG-20260304-WA0002.jpg b/media/work/IMG-20260304-WA0002.jpg new file mode 100644 index 0000000..1b185f8 Binary files /dev/null and b/media/work/IMG-20260304-WA0002.jpg differ diff --git a/media/work/IMG-20260304-WA0003.jpg b/media/work/IMG-20260304-WA0003.jpg new file mode 100644 index 0000000..db3e08a Binary files /dev/null and b/media/work/IMG-20260304-WA0003.jpg differ diff --git a/media/work/IMG-20260304-WA0004.jpg b/media/work/IMG-20260304-WA0004.jpg new file mode 100644 index 0000000..121b379 Binary files /dev/null and b/media/work/IMG-20260304-WA0004.jpg differ diff --git a/media/work/IMG-20260304-WA0005.jpg b/media/work/IMG-20260304-WA0005.jpg new file mode 100644 index 0000000..f3722ac Binary files /dev/null and b/media/work/IMG-20260304-WA0005.jpg differ diff --git a/media/work/IMG-20260304-WA0006.jpg b/media/work/IMG-20260304-WA0006.jpg new file mode 100644 index 0000000..53b1cfa Binary files /dev/null and b/media/work/IMG-20260304-WA0006.jpg differ diff --git a/static/assets/galeria-1.jpg b/static/assets/galeria-1.jpg index 2695d34..76d690b 100644 Binary files a/static/assets/galeria-1.jpg and b/static/assets/galeria-1.jpg differ diff --git a/static/assets/galeria-2.jpg b/static/assets/galeria-2.jpg index 57a5c9a..1b185f8 100644 Binary files a/static/assets/galeria-2.jpg and b/static/assets/galeria-2.jpg differ diff --git a/static/assets/galeria-3.jpg b/static/assets/galeria-3.jpg index 87cddc9..db3e08a 100644 Binary files a/static/assets/galeria-3.jpg and b/static/assets/galeria-3.jpg differ diff --git a/static/assets/galeria-4.jpg b/static/assets/galeria-4.jpg index bbeb182..121b379 100644 Binary files a/static/assets/galeria-4.jpg and b/static/assets/galeria-4.jpg differ diff --git a/static/assets/galeria-5.jpg b/static/assets/galeria-5.jpg index b61f82c..f3722ac 100644 Binary files a/static/assets/galeria-5.jpg and b/static/assets/galeria-5.jpg differ diff --git a/static/assets/galeria-6.jpg b/static/assets/galeria-6.jpg index 1fdddb5..53b1cfa 100644 Binary files a/static/assets/galeria-6.jpg and b/static/assets/galeria-6.jpg differ diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png new file mode 100644 index 0000000..a3e5d31 Binary files /dev/null and b/static/favicon-16x16.png differ diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png new file mode 100644 index 0000000..75c9b2a Binary files /dev/null and b/static/favicon-32x32.png differ diff --git a/static/favicon-48x48.png b/static/favicon-48x48.png new file mode 100644 index 0000000..64d9e90 Binary files /dev/null and b/static/favicon-48x48.png differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..7549a98 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..7f47bb0 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/index.html b/static/index.html index e01a7f8..56ab546 100644 --- a/static/index.html +++ b/static/index.html @@ -3,8 +3,8 @@ - May HairCare — Peluquería en Barcelona - + MAY Studio — Peluquería en Barcelona + @@ -14,12 +14,10 @@