-- ============================================ -- 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';