mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 11:24:26 +00:00
feat: implement customer registration flow and business hours system
Major changes: - Add customer registration with email/phone lookup (app/booking/registro) - Add customers API endpoint (app/api/customers/route) - Implement business hours for locations (mon-fri 10-7, sat 10-6, sun closed) - Fix availability function type casting issues - Add business hours utilities (lib/utils/business-hours.ts) - Update Location type to include business_hours JSONB - Add mock payment component for testing - Remove Supabase auth from booking flow - Fix /cita redirect path in booking flow Database migrations: - Add category column to services table - Add business_hours JSONB column to locations table - Fix availability functions with proper type casting - Update get_detailed_availability to use business_hours Features: - Customer lookup by email or phone - Auto-redirect to registration if customer not found - Pre-fill customer data if exists - Business hours per day of week - Location-specific opening/closing times
This commit is contained in:
33
scripts/add_business_hours.sql
Normal file
33
scripts/add_business_hours.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Ejecutar este SQL en Supabase Dashboard: Database > SQL Editor
|
||||
-- Agrega horarios de atención a la tabla locations
|
||||
|
||||
-- Agregar columna business_hours
|
||||
ALTER TABLE locations
|
||||
ADD COLUMN IF NOT EXISTS business_hours JSONB DEFAULT '{
|
||||
"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;
|
||||
|
||||
-- Agregar comentario
|
||||
COMMENT ON COLUMN locations.business_hours IS 'Horarios de atención por día. Formato JSONB con claves: monday-sunday, cada una con open/close en formato HH:MM e is_closed boolean';
|
||||
|
||||
-- Actualizar locations existentes con horarios por defecto
|
||||
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 resultados
|
||||
SELECT id, name, business_hours FROM locations;
|
||||
41
scripts/debug_business_hours.sql
Normal file
41
scripts/debug_business_hours.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
-- Debug and fix business_hours JSONB extraction
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- First, check what's actually stored in business_hours
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
business_hours,
|
||||
business_hours->>'monday' as monday_raw,
|
||||
business_hours->'monday' as monday_object
|
||||
FROM locations
|
||||
LIMIT 1;
|
||||
|
||||
-- Test extraction logic
|
||||
SELECT
|
||||
'2026-01-20'::DATE as test_date,
|
||||
EXTRACT(DOW FROM '2026-01-20'::DATE) as day_of_week_number,
|
||||
ARRAY['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][EXTRACT(DOW FROM '2026-01-20'::DATE) + 1] as day_name;
|
||||
|
||||
-- Fix: Ensure business_hours is properly formatted
|
||||
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;
|
||||
|
||||
-- Verify the update
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
business_hours->>'monday' as monday,
|
||||
(business_hours->'monday'->>'open')::TIME as monday_open,
|
||||
(business_hours->'monday'->>'close')::TIME as monday_close
|
||||
FROM locations
|
||||
LIMIT 1;
|
||||
81
scripts/seed_booking_data.sql
Normal file
81
scripts/seed_booking_data.sql
Normal file
@@ -0,0 +1,81 @@
|
||||
-- Seed data for testing booking flow
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- Get active locations
|
||||
DO $$
|
||||
DECLARE
|
||||
v_location_id UUID;
|
||||
v_staff_id UUID;
|
||||
v_resource_id UUID;
|
||||
v_service_id UUID;
|
||||
BEGIN
|
||||
-- Get first active location
|
||||
SELECT id INTO v_location_id FROM locations WHERE is_active = true LIMIT 1;
|
||||
|
||||
IF v_location_id IS NULL THEN
|
||||
RAISE NOTICE 'No active locations found';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'Using location: %', v_location_id;
|
||||
|
||||
-- Insert sample staff if none exists
|
||||
INSERT INTO staff (user_id, location_id, role, display_name, phone, is_active, is_available_for_booking, work_hours_start, work_hours_end, work_days)
|
||||
SELECT
|
||||
gen_random_uuid(),
|
||||
v_location_id,
|
||||
'artist',
|
||||
'Artista Demo',
|
||||
'+52 844 123 4567',
|
||||
true,
|
||||
true,
|
||||
'10:00'::TIME,
|
||||
'19:00'::TIME,
|
||||
'MON,TUE,WED,THU,FRI,SAT'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM staff WHERE display_name = 'Artista Demo' AND location_id = v_location_id)
|
||||
RETURNING id INTO v_staff_id;
|
||||
|
||||
IF v_staff_id IS NOT NULL THEN
|
||||
RAISE NOTICE 'Created staff: %', v_staff_id;
|
||||
ELSE
|
||||
SELECT id INTO v_staff_id FROM staff WHERE display_name = 'Artista Demo' AND location_id = v_location_id;
|
||||
RAISE NOTICE 'Using existing staff: %', v_staff_id;
|
||||
END IF;
|
||||
|
||||
-- Insert sample resources if none exists
|
||||
INSERT INTO resources (location_id, name, type, capacity, is_active)
|
||||
SELECT
|
||||
v_location_id,
|
||||
'Estación Demo',
|
||||
'station',
|
||||
1,
|
||||
true
|
||||
WHERE NOT EXISTS (SELECT 1 FROM resources WHERE name = 'Estación Demo' AND location_id = v_location_id)
|
||||
RETURNING id INTO v_resource_id;
|
||||
|
||||
IF v_resource_id IS NOT NULL THEN
|
||||
RAISE NOTICE 'Created resource: %', v_resource_id;
|
||||
ELSE
|
||||
SELECT id INTO v_resource_id FROM resources WHERE name = 'Estación Demo' AND location_id = v_location_id;
|
||||
RAISE NOTICE 'Using existing resource: %', v_resource_id;
|
||||
END IF;
|
||||
|
||||
-- Check if we have services
|
||||
SELECT id INTO v_service_id FROM services WHERE is_active = true LIMIT 1;
|
||||
|
||||
IF v_service_id IS NOT NULL THEN
|
||||
RAISE NOTICE 'Using service: %', v_service_id;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'Seed data completed';
|
||||
END $$;
|
||||
|
||||
-- Verify results
|
||||
SELECT
|
||||
'Locations' as type, COUNT(*)::text as count FROM locations WHERE is_active = true
|
||||
UNION ALL
|
||||
SELECT 'Staff', COUNT(*)::text FROM staff WHERE is_active = true
|
||||
UNION ALL
|
||||
SELECT 'Resources', COUNT(*)::text FROM resources WHERE is_active = true
|
||||
UNION ALL
|
||||
SELECT 'Services', COUNT(*)::text FROM services WHERE is_active = true;
|
||||
46
scripts/seed_test_data.sql
Normal file
46
scripts/seed_test_data.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- Seed data for testing availability
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- Insert sample staff if none exists
|
||||
INSERT INTO staff (user_id, location_id, role, display_name, phone, is_active, is_available_for_booking, work_hours_start, work_hours_end, work_days)
|
||||
SELECT
|
||||
gen_random_uuid() as user_id,
|
||||
id as location_id,
|
||||
'artist' as role,
|
||||
'Artista Demo' as display_name,
|
||||
'+52 844 123 4567' as phone,
|
||||
true as is_active,
|
||||
true as is_available_for_booking,
|
||||
'10:00'::TIME as work_hours_start,
|
||||
'19:00'::TIME as work_hours_end,
|
||||
'MON,TUE,WED,THU,FRI,SAT' as work_days
|
||||
FROM locations
|
||||
WHERE is_active = true
|
||||
AND NOT EXISTS (SELECT 1 FROM staff WHERE location_id = locations.id AND display_name = 'Artista Demo')
|
||||
LIMIT 1;
|
||||
|
||||
-- Insert sample resources if none exists
|
||||
INSERT INTO resources (location_id, name, type, capacity, is_active)
|
||||
SELECT
|
||||
id as location_id,
|
||||
'Estación Demo' as name,
|
||||
'station' as type,
|
||||
1 as capacity,
|
||||
true as is_active
|
||||
FROM locations
|
||||
WHERE is_active = true
|
||||
AND NOT EXISTS (SELECT 1 FROM resources WHERE location_id = locations.id AND name = 'Estación Demo')
|
||||
LIMIT 1;
|
||||
|
||||
-- Verify results
|
||||
SELECT
|
||||
'Staff added' as info,
|
||||
COUNT(*)::text as count
|
||||
FROM staff
|
||||
WHERE display_name = 'Artista Demo'
|
||||
UNION ALL
|
||||
SELECT
|
||||
'Resources added',
|
||||
COUNT(*)::text
|
||||
FROM resources
|
||||
WHERE name = 'Estación Demo';
|
||||
24
scripts/test_availability_functions.sql
Normal file
24
scripts/test_availability_functions.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- Test the availability functions
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- Test check_staff_availability with fixed type casting
|
||||
SELECT
|
||||
check_staff_availability(
|
||||
(SELECT id FROM staff LIMIT 1),
|
||||
NOW() + INTERVAL '1 hour',
|
||||
NOW() + INTERVAL '2 hours'
|
||||
) as is_available;
|
||||
|
||||
-- Test get_detailed_availability with business hours
|
||||
SELECT * FROM get_detailed_availability(
|
||||
(SELECT id FROM locations LIMIT 1),
|
||||
(SELECT id FROM services LIMIT 1),
|
||||
CURRENT_DATE + INTERVAL '1 day',
|
||||
60
|
||||
);
|
||||
|
||||
-- Check business hours structure
|
||||
SELECT name, business_hours FROM locations LIMIT 1;
|
||||
|
||||
-- Check services with category
|
||||
SELECT id, name, category, is_active FROM services LIMIT 5;
|
||||
20
scripts/test_data_check.sql
Normal file
20
scripts/test_data_check.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Test script to check database data
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- Check counts
|
||||
SELECT
|
||||
'Locations' as table_name, COUNT(*)::text as count FROM locations
|
||||
UNION ALL
|
||||
SELECT 'Services', COUNT(*)::text FROM services
|
||||
UNION ALL
|
||||
SELECT 'Staff', COUNT(*)::text FROM staff
|
||||
UNION ALL
|
||||
SELECT 'Resources', COUNT(*)::text FROM resources
|
||||
UNION ALL
|
||||
SELECT 'Bookings', COUNT(*)::text FROM bookings;
|
||||
|
||||
-- Show sample data
|
||||
SELECT id, name, timezone, is_active FROM locations LIMIT 5;
|
||||
SELECT id, name, duration_minutes, base_price, is_active FROM services LIMIT 5;
|
||||
SELECT id, display_name, role, is_active, is_available_for_booking FROM staff LIMIT 5;
|
||||
SELECT id, name, type, capacity, is_active FROM resources LIMIT 5;
|
||||
102
scripts/update_availability_function.sql
Normal file
102
scripts/update_availability_function.sql
Normal file
@@ -0,0 +1,102 @@
|
||||
-- Update get_detailed_availability to use business_hours from locations table
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
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_start_time TIME := '09:00'::TIME;
|
||||
v_end_time TIME := '21:00'::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,
|
||||
business_hours
|
||||
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
|
||||
v_day_hours := v_business_hours -> v_day_of_week;
|
||||
|
||||
-- Verificar si el lugar está cerrado este día
|
||||
IF v_day_hours->>'is_closed' = 'true' THEN
|
||||
RETURN '[]'::JSONB;
|
||||
END IF;
|
||||
|
||||
-- Extraer horas de apertura y cierre
|
||||
v_start_time := (v_day_hours->>'open')::TIME;
|
||||
v_end_time := (v_day_hours->>'close')::TIME;
|
||||
|
||||
-- Generar slots de tiempo para el día
|
||||
v_slot_start := (p_date || ' ' || v_start_time::TEXT)::TIMESTAMPTZ
|
||||
AT TIME ZONE v_location_timezone;
|
||||
|
||||
v_slot_end := (p_date || ' ' || v_end_time::TEXT)::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 s.is_available_for_booking = 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;
|
||||
63
scripts/update_business_hours.sql
Normal file
63
scripts/update_business_hours.sql
Normal file
@@ -0,0 +1,63 @@
|
||||
-- Update business_hours with correct structure and values
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- First, check current state
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
business_hours->>'monday' as monday_check,
|
||||
(business_hours->'monday'->>'open') as monday_open_check,
|
||||
(business_hours->'monday'->>'close') as monday_close_check
|
||||
FROM locations
|
||||
LIMIT 1;
|
||||
|
||||
-- Update with correct structure - Monday to Friday 10-7, Saturday 10-6, Sunday closed
|
||||
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;
|
||||
|
||||
-- Verify the update
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
timezone,
|
||||
business_hours
|
||||
FROM locations
|
||||
LIMIT 1;
|
||||
|
||||
-- Test extraction for different days
|
||||
SELECT
|
||||
'Monday' as day,
|
||||
(business_hours->'monday'->>'open')::TIME as open_time,
|
||||
(business_hours->'monday'->>'close')::TIME as close_time,
|
||||
business_hours->'monday'->>'is_closed' as is_closed
|
||||
FROM locations
|
||||
UNION ALL
|
||||
SELECT
|
||||
'Saturday' as day,
|
||||
(business_hours->'saturday'->>'open')::TIME as open_time,
|
||||
(business_hours->'saturday'->>'close')::TIME as close_time,
|
||||
business_hours->'saturday'->>'is_closed' as is_closed
|
||||
FROM locations
|
||||
UNION ALL
|
||||
SELECT
|
||||
'Sunday' as day,
|
||||
(business_hours->'sunday'->>'open')::TIME as open_time,
|
||||
(business_hours->'sunday'->>'close')::TIME as close_time,
|
||||
business_hours->'sunday'->>'is_closed' as is_closed
|
||||
FROM locations;
|
||||
|
||||
-- Test the get_detailed_availability function
|
||||
SELECT * FROM get_detailed_availability(
|
||||
(SELECT id FROM locations LIMIT 1),
|
||||
(SELECT id FROM services WHERE is_active = true LIMIT 1),
|
||||
CURRENT_DATE,
|
||||
60
|
||||
);
|
||||
25
scripts/update_business_hours_final.sql
Normal file
25
scripts/update_business_hours_final.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- Update business_hours with correct structure and values
|
||||
-- Execute in Supabase Dashboard: Database > SQL Editor
|
||||
|
||||
-- Update with correct structure - Monday to Friday 10-7, Saturday 10-6, Sunday closed
|
||||
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;
|
||||
|
||||
-- Verify update
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
business_hours->>'monday' as monday_check,
|
||||
(business_hours->'monday'->>'open')::TIME as monday_open,
|
||||
(business_hours->'monday'->>'close')::TIME as monday_close
|
||||
FROM locations
|
||||
LIMIT 1;
|
||||
Reference in New Issue
Block a user