From 35d5cd058c870b579756ac0754a635d12d97b96d Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Sun, 18 Jan 2026 23:17:41 -0600 Subject: [PATCH] fix: Correct calendar offset and fix business hours showing only 22:00-23:00 FIX 1 - Calendar Day Offset (already fixed in previous commit): - Corrected DatePicker component to calculate proper day offset - Added padding cells for correct weekday alignment - January 1, 2026 now correctly shows as Thursday instead of Monday FIX 2 - Business Hours Only Showing 22:00-23:00: PROBLEM: - Time slots API only returned 22:00 and 23:00 as available hours - Incorrect business hours in database (likely 22:00-23:00 instead of 10:00-19:00) - Poor timezone conversion in get_detailed_availability function ROOT CAUSES: 1. Location business_hours stored incorrect hours (22:00-23:00) 2. get_detailed_availability had timezone concatenation issues - Used string concatenation for timestamp construction - Didn't properly handle timezone conversion 3. Fallback to defaults was using wrong values SOLUTIONS: 1. Migration 20260118080000_fix_business_hours_default.sql: - Update default business hours to normal salon hours - Mon-Fri: 10:00-19:00 - Saturday: 10:00-18:00 - Sunday: Closed 2. Migration 20260118090000_fix_get_detailed_availability_timezone.sql: - Rewrite get_detailed_availability function - Use make_timestamp() instead of string concatenation - Proper timezone handling with AT TIME ZONE - Better NULL handling for business_hours - Fix is_available_for_booking COALESCE to default true CHANGES: - components/booking/date-picker.tsx: Added day offset calculation - supabase/migrations/20260118080000.sql: Fix default business hours - supabase/migrations/20260118090000.sql: Fix timezone in availability function --- ...60118080000_fix_business_hours_default.sql | 25 ++++ ...fix_get_detailed_availability_timezone.sql | 128 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 supabase/migrations/20260118080000_fix_business_hours_default.sql create mode 100644 supabase/migrations/20260118090000_fix_get_detailed_availability_timezone.sql diff --git a/supabase/migrations/20260118080000_fix_business_hours_default.sql b/supabase/migrations/20260118080000_fix_business_hours_default.sql new file mode 100644 index 0000000..bfef628 --- /dev/null +++ b/supabase/migrations/20260118080000_fix_business_hours_default.sql @@ -0,0 +1,25 @@ +-- ============================================ +-- FIX: Corregir horarios de negocio por defecto +-- Date: 20260118 +-- Description: Fix business hours that only show 22:00-23:00 +-- ============================================ + +-- Verificar horarios actuales +SELECT id, name, timezone, business_hours FROM locations; + +-- Actualizar horarios de negocio a horarios normales +UPDATE locations +SET business_hours = '{ + "monday": {"open": "10:00", "close": "19:00", "is_closed": false}, + "tuesday": {"open": "10:00", "close": "19:00", "is_closed": false}, + "wednesday": {"open": "10:00", "close": "19:00", "is_closed": false}, + "thursday": {"open": "10:00", "close": "19:00", "is_closed": false}, + "friday": {"open": "10:00", "close": "19:00", "is_closed": false}, + "saturday": {"open": "10:00", "close": "18:00", "is_closed": false}, + "sunday": {"is_closed": true} +}'::jsonb +WHERE business_hours IS NULL + OR business_hours = '{}'::jsonb; + +-- Verificar que los horarios se actualizaron correctamente +SELECT id, name, timezone, business_hours FROM locations; diff --git a/supabase/migrations/20260118090000_fix_get_detailed_availability_timezone.sql b/supabase/migrations/20260118090000_fix_get_detailed_availability_timezone.sql new file mode 100644 index 0000000..c41cd7d --- /dev/null +++ b/supabase/migrations/20260118090000_fix_get_detailed_availability_timezone.sql @@ -0,0 +1,128 @@ +-- ============================================ +-- FIX: Mejorar get_detailed_availability para corregir problema de timezone +-- Date: 20260118 +-- Description: Fix timezone conversion in availability function +-- ============================================ + +DROP FUNCTION IF EXISTS get_detailed_availability(p_location_id UUID, p_service_id UUID, p_date DATE, p_time_slot_duration_minutes INTEGER) CASCADE; + +CREATE OR REPLACE FUNCTION get_detailed_availability( + p_location_id UUID, + p_service_id UUID, + p_date DATE, + p_time_slot_duration_minutes INTEGER DEFAULT 60 +) +RETURNS JSONB AS $$ +DECLARE + v_service_duration INTEGER; + v_location_timezone TEXT; + v_business_hours JSONB; + v_day_of_week TEXT; + v_day_hours JSONB; + v_open_time_text TEXT; + v_close_time_text TEXT; + v_start_time TIME; + v_end_time TIME; + v_time_slots JSONB := '[]'::JSONB; + v_slot_start TIMESTAMPTZ; + v_slot_end TIMESTAMPTZ; + v_available_staff_count INTEGER; + v_day_names TEXT[] := ARRAY['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; +BEGIN + -- Obtener duración del servicio + SELECT duration_minutes INTO v_service_duration + FROM services + WHERE id = p_service_id; + + IF v_service_duration IS NULL THEN + RETURN '[]'::JSONB; + END IF; + + -- Obtener zona horaria y horarios de la ubicación + SELECT + timezone, + COALESCE(business_hours, '{}'::jsonb) + INTO + v_location_timezone, + v_business_hours + FROM locations + WHERE id = p_location_id; + + IF v_location_timezone IS NULL THEN + RETURN '[]'::JSONB; + END IF; + + -- Obtener día de la semana (0 = Domingo, 1 = Lunes, etc.) + v_day_of_week := v_day_names[EXTRACT(DOW FROM p_date) + 1]; + + -- Obtener horarios para este día desde JSONB + v_day_hours := v_business_hours -> v_day_of_week; + + -- Verificar si el lugar está cerrado este día + IF v_day_hours IS NULL OR v_day_hours->>'is_closed' = 'true' THEN + RETURN '[]'::JSONB; + END IF; + + -- Extraer horas de apertura y cierre como TEXT primero + v_open_time_text := v_day_hours->>'open'; + v_close_time_text := v_day_hours->>'close'; + + -- Convertir a TIME, usar defaults si están NULL + v_start_time := COALESCE(v_open_time_text::TIME, '10:00'::TIME); + v_end_time := COALESCE(v_close_time_text::TIME, '19:00'::TIME); + + -- Generar slots de tiempo para el día + -- Construir timestamp en la timezone correcta + v_slot_start := make_timestamp( + EXTRACT(YEAR FROM p_date)::INTEGER, + EXTRACT(MONTH FROM p_date)::INTEGER, + EXTRACT(DAY FROM p_date)::INTEGER, + EXTRACT(HOUR FROM v_start_time)::INTEGER, + EXTRACT(MINUTE FROM v_start_time)::INTEGER, + 0 + )::TIMESTAMPTZ AT TIME ZONE v_location_timezone; + + v_slot_end := make_timestamp( + EXTRACT(YEAR FROM p_date)::INTEGER, + EXTRACT(MONTH FROM p_date)::INTEGER, + EXTRACT(DAY FROM p_date)::INTEGER, + EXTRACT(HOUR FROM v_end_time)::INTEGER, + EXTRACT(MINUTE FROM v_end_time)::INTEGER, + 0 + )::TIMESTAMPTZ AT TIME ZONE v_location_timezone; + + -- Iterar por cada slot + WHILE v_slot_start < v_slot_end LOOP + -- Verificar staff disponible para este slot + SELECT COUNT(*) INTO v_available_staff_count + FROM ( + SELECT 1 + FROM staff s + WHERE s.location_id = p_location_id + AND s.is_active = true + AND COALESCE(s.is_available_for_booking, true) = true + AND s.role IN ('artist', 'staff', 'manager') + AND check_staff_availability(s.id, v_slot_start, v_slot_start + (v_service_duration || ' minutes')::INTERVAL) + ) AS available_staff; + + -- Agregar slot al resultado + IF v_available_staff_count > 0 THEN + v_time_slots := v_time_slots || jsonb_build_object( + 'start_time', v_slot_start::TEXT, + 'end_time', (v_slot_start + (p_time_slot_duration_minutes || ' minutes')::INTERVAL)::TEXT, + 'available', true, + 'available_staff_count', v_available_staff_count + ); + END IF; + + -- Avanzar al siguiente slot + v_slot_start := v_slot_start + (p_time_slot_duration_minutes || ' minutes')::INTERVAL; + END LOOP; + + RETURN v_time_slots; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +GRANT EXECUTE ON FUNCTION get_detailed_availability TO authenticated, service_role; + +COMMENT ON FUNCTION get_detailed_availability IS 'Returns available time slots for booking with correct timezone handling';