Implementación completa de la Fase 1.1 y 1.2 del proyecto SalonOS: ## Cambios en Reglas de Negocio (PRD.md, AGENTS.md, TASKS.md) - Actualizado reset de invitaciones de mensual a semanal (Lunes 00:00 UTC) - Jerarquía de roles actualizada: Admin > Manager > Staff > Artist > Customer - Artistas (antes colaboradoras) ahora tienen rol 'artist' - Staff/Manager/Admin pueden ver PII de customers - Artist solo ve nombre y notas de customers (restricción de privacidad) ## Estructura del Proyecto (Next.js 14) - app/boutique/: Frontend de cliente - app/hq/: Dashboard administrativo - app/api/: API routes - components/: Componentes UI reutilizables (boutique, hq, shared) - lib/: Lógica de negocio (supabase, db, utils) - db/: Esquemas, migraciones y seeds - integrations/: Stripe, Google Calendar, WhatsApp - scripts/: Scripts de utilidad y automatización - docs/: Documentación del proyecto ## Esquema de Base de Datos (Supabase PostgreSQL) 8 tablas creadas: - locations: Ubicaciones con timezone - resources: Recursos físicos (estaciones, habitaciones, equipos) - staff: Personal con roles jerárquicos - services: Catálogo de servicios - customers: Información de clientes con tier (free/gold) - invitations: Sistema de invitaciones semanales - bookings: Sistema de reservas con short_id (6 caracteres) - audit_logs: Registro de auditoría automática 14 funciones creadas: - generate_short_id(): Generador de Short ID (6 chars, collision-safe) - generate_invitation_code(): Generador de códigos de invitación (10 chars) - reset_weekly_invitations_for_customer(): Reset individual de invitaciones - reset_all_weekly_invitations(): Reset masivo de invitaciones - validate_secondary_artist_role(): Validación de secondary_artist - log_audit(): Trigger de auditoría automática - get_current_user_role(): Obtener rol del usuario actual - is_staff_or_higher(): Verificar si es admin/manager/staff - is_artist(): Verificar si es artist - is_customer(): Verificar si es customer - is_admin(): Verificar si es admin - update_updated_at(): Actualizar timestamps - generate_booking_short_id(): Generar Short ID automáticamente - get_week_start(): Obtener inicio de semana 17+ triggers activos: - Auditores automáticos en tablas críticas - Timestamps updated_at en todas las tablas - Validación de secondary_artist (trigger en lugar de constraint) 20+ políticas RLS configuradas: - Restricción crítica: Artist no ve email/phone de customers - Jerarquía de roles: Admin > Manager > Staff > Artist > Customer - Políticas granulares por tipo de operación y rol 6 tipos ENUM: - user_role: admin, manager, staff, artist, customer - customer_tier: free, gold - booking_status: pending, confirmed, cancelled, completed, no_show - invitation_status: pending, used, expired - resource_type: station, room, equipment - audit_action: create, update, delete, reset_invitations, payment, status_change ## Scripts de Utilidad - check-connection.sh: Verificar conexión a Supabase - simple-verify.sh: Verificar migraciones instaladas - simple-seed.sh: Crear datos de prueba - create-auth-users.js: Crear usuarios de Auth en Supabase - verify-migration.sql: Script de verificación SQL completo - seed-data.sql: Script de seed de datos SQL completo ## Documentación - docs/STEP_BY_STEP_VERIFICATION.md: Guía paso a paso de verificación - docs/STEP_BY_STEP_AUTH_CONFIG.md: Guía paso a paso de configuración Auth - docs/POST_MIGRATION_SUCCESS.md: Guía post-migración - docs/MIGRATION_CORRECTION.md: Detalle de correcciones aplicadas - docs/QUICK_START_POST_MIGRATION.md: Guía rápida de referencia - docs/SUPABASE_DASHBOARD_MIGRATION.md: Guía de ejecución en Dashboard - docs/00_FULL_MIGRATION_FINAL_README.md: Guía de migración final - SIMPLE_GUIDE.md: Guía simple de inicio - FASE_1_STATUS.md: Estado de la Fase 1 ## Configuración - package.json: Dependencias y scripts de npm - tsconfig.json: Configuración TypeScript con paths aliases - next.config.js: Configuración Next.js - tailwind.config.ts: Tema personalizado con colores primary, secondary, gold - postcss.config.js: Configuración PostCSS - .gitignore: Archivos excluidos de git - .env.example: Template de variables de entorno ## Correcciones Aplicadas 1. Constraint de subquery en CHECK reemplazado por trigger de validación - PostgreSQL no permite subqueries en CHECK constraints - validate_secondary_artist_role() ahora es un trigger 2. Variable no declarada en loop - customer_record RECORD; añadido en bloque DECLARE ## Principios Implementados - UTC-first: Todos los timestamps se almacenan en UTC - Sistema Doble Capa: Validación Staff/Artist + Recurso físico - Reset semanal: Invitaciones se resetean cada Lunes 00:00 UTC - Idempotencia: Procesos de reset son idempotentes y auditados - Privacidad: Artist solo ve nombre y notas de customers - Auditoría: Todas las acciones críticas se registran automáticamente - Short ID: 6 caracteres alfanuméricos como referencia humana - UUID: Identificador primario interno ## Próximos Pasos - Ejecutar scripts de verificación y seed - Configurar Auth en Supabase Dashboard - Implementar Tarea 1.3: Short ID & Invitaciones (backend) - Implementar Tarea 1.4: CRM Base (endpoints CRUD)
9.6 KiB
✅ CORRECCIÓN DE MIGRACIÓN - PostgreSQL Constraint Error
🐛 Problemas Detectados
Problema 1: Subquery en CHECK Constraint
Al ejecutar la migración en Supabase Dashboard, se produjo el siguiente error:
Error: Failed to run sql query: ERROR: 0A000: cannot use subquery in check constraint
Causa: PostgreSQL no permite subqueries en los constraints de CHECK.
Problema 2: Variable no declarada en Loop
Error: Failed to run sql query: ERROR: 42601: loop variable of loop over rows must be a record variable or list of scalar variables
Causa: La variable customer_record no estaba declarada en el bloque DECLARE de la función reset_all_weekly_invitations().
🔍 Causas Detalladas
Problema 1: Subquery en CHECK Constraint
En la migración original, teníamos:
-- Constraint problemático (NO permitido en PostgreSQL)
ALTER TABLE bookings ADD CONSTRAINT check_secondary_artist_role
CHECK (secondary_artist_id IS NULL OR EXISTS (
SELECT 1 FROM staff s
WHERE s.id = secondary_artist_id AND s.role = 'artist'
));
✅ Soluciones Aplicadas
Solución 1: Reemplazar Constraint con Trigger
Se ha reemplazado el constraint problemático con un trigger de validación que hace exactamente la misma validación:
-- Nueva función de validación (PERMITIDO en PostgreSQL)
CREATE OR REPLACE FUNCTION validate_secondary_artist_role()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.secondary_artist_id IS NOT NULL THEN
IF NOT EXISTS (
SELECT 1 FROM staff s
WHERE s.id = NEW.secondary_artist_id AND s.role = 'artist' AND s.is_active = true
) THEN
RAISE EXCEPTION 'secondary_artist_id must reference an active staff member with role ''artist''';
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER validate_booking_secondary_artist BEFORE INSERT OR UPDATE ON bookings
FOR EACH ROW EXECUTE FUNCTION validate_secondary_artist_role();
Solución 2: Declarar Variable en Bloque DECLARE
Se ha añadido la declaración de customer_record RECORD; en el bloque DECLARE de la función reset_all_weekly_invitations():
Antes:
CREATE OR REPLACE FUNCTION reset_all_weekly_invitations()
RETURNS JSONB AS $$
DECLARE
customers_count INTEGER := 0;
invitations_created INTEGER := 0;
result JSONB;
BEGIN
FOR customer_record IN -- ❌ Variable no declarada
SELECT id FROM customers WHERE tier = 'gold' AND is_active = true
LOOP
...
END LOOP;
Después:
CREATE OR REPLACE FUNCTION reset_all_weekly_invitations()
RETURNS JSONB AS $$
DECLARE
customers_count INTEGER := 0;
invitations_created INTEGER := 0;
result JSONB;
customer_record RECORD; -- ✅ Variable declarada
BEGIN
FOR customer_record IN -- ✅ Ahora la variable existe
SELECT id FROM customers WHERE tier = 'gold' AND is_active = true
LOOP
...
END LOOP;
📁 Archivos Actualizados
Archivos Corregidos:
-
db/migrations/00_FULL_MIGRATION_CORRECTED.sql(NUEVO)- Archivo consolidado con todas las migraciones
- Constraint reemplazado por trigger de validación
- Índice adicional para
secondary_artist_id
-
db/migrations/001_initial_schema.sql(ACTUALIZADO)- Constraint problemático eliminado
- Trigger de validación añadido
- Índice para
secondary_artist_idañadido
-
docs/SUPABASE_DASHBOARD_MIGRATION.md(ACTUALIZADO)- Referencias actualizadas al archivo corregido
- Documentación actualizada con el nuevo trigger
🎯 Cómo Proceder
Paso 1: Usar el Archivo Corregido
Copia TODO el contenido de:
db/migrations/00_FULL_MIGRATION_CORRECTED.sql
Paso 2: Ejecutar en Supabase Dashboard
- Ve a: https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/sql
- Crea un nuevo query
- Pega el contenido completo del archivo corregido
- Haz clic en "Run"
Paso 3: Verificar la Ejecución
Al finalizar, deberías ver:
- ✅ Un mensaje de éxito
- ✅ 8 tablas creadas
- ✅ 14 funciones creadas (incluye
validate_secondary_artist_role) - ✅ 17+ triggers activos (incluye
validate_booking_secondary_artist) - ✅ 20+ políticas RLS configuradas
- ✅ 6 tipos ENUM creados
🔍 Verificación del Trigger
Para verificar que el trigger de validación funciona correctamente, ejecuta:
-- Verificar que el trigger existe
SELECT trigger_name, event_object_table, action_statement
FROM information_schema.triggers
WHERE trigger_name = 'validate_booking_secondary_artist';
-- Verificar la función de validación
SELECT routine_name, routine_definition
FROM information_schema.routines
WHERE routine_name = 'validate_secondary_artist_role';
🧪 Probar la Validación
Prueba 1: Booking válido con secondary_artist válido
-- Primero crear datos de prueba
INSERT INTO locations (name, timezone, is_active)
VALUES ('Test Location', 'America/Mexico_City', true);
INSERT INTO staff (user_id, location_id, role, display_name, is_active)
VALUES (uuid_generate_v4(), (SELECT id FROM locations LIMIT 1), 'artist', 'Test Artist', true);
INSERT INTO staff (user_id, location_id, role, display_name, is_active)
VALUES (uuid_generate_v4(), (SELECT id FROM locations LIMIT 1), 'staff', 'Test Staff', true);
INSERT INTO resources (location_id, name, type, is_active)
VALUES ((SELECT id FROM locations LIMIT 1), 'Test Station', 'station', true);
INSERT INTO services (name, duration_minutes, base_price, is_active)
VALUES ('Test Service', 60, 100.00, true);
INSERT INTO customers (user_id, first_name, last_name, email, tier, is_active)
VALUES (uuid_generate_v4(), 'Test', 'Customer', 'test@example.com', 'free', true);
-- Ahora intentar crear un booking válido
INSERT INTO bookings (
customer_id,
staff_id,
secondary_artist_id,
location_id,
resource_id,
service_id,
start_time_utc,
end_time_utc,
status,
deposit_amount,
total_amount,
is_paid
)
SELECT
(SELECT id FROM customers LIMIT 1),
(SELECT id FROM staff WHERE role = 'staff' LIMIT 1),
(SELECT id FROM staff WHERE role = 'artist' LIMIT 1),
(SELECT id FROM locations LIMIT 1),
(SELECT id FROM resources LIMIT 1),
(SELECT id FROM services LIMIT 1),
NOW() + INTERVAL '1 day',
NOW() + INTERVAL '2 days',
'confirmed',
50.00,
100.00,
true;
Resultado esperado: ✅ Booking creado exitosamente
Prueba 2: Booking inválido con secondary_artist no válido
-- Intentar crear un booking con secondary_artist que no es 'artist'
INSERT INTO bookings (
customer_id,
staff_id,
secondary_artist_id,
location_id,
resource_id,
service_id,
start_time_utc,
end_time_utc,
status,
deposit_amount,
total_amount,
is_paid
)
SELECT
(SELECT id FROM customers LIMIT 1),
(SELECT id FROM staff WHERE role = 'staff' LIMIT 1),
(SELECT id FROM staff WHERE role = 'staff' LIMIT 1), -- ❌ Esto es 'staff', no 'artist'
(SELECT id FROM locations LIMIT 1),
(SELECT id FROM resources LIMIT 1),
(SELECT id FROM services LIMIT 1),
NOW() + INTERVAL '1 day',
NOW() + INTERVAL '2 days',
'confirmed',
50.00,
100.00,
true;
Resultado esperado: ❌ Error: secondary_artist_id must reference an active staff member with role 'artist'
Prueba 3: Booking sin secondary_artist
-- Crear un booking sin secondary_artist (debe ser válido)
INSERT INTO bookings (
customer_id,
staff_id,
location_id,
resource_id,
service_id,
start_time_utc,
end_time_utc,
status,
deposit_amount,
total_amount,
is_paid
)
SELECT
(SELECT id FROM customers LIMIT 1),
(SELECT id FROM staff WHERE role = 'staff' LIMIT 1),
(SELECT id FROM locations LIMIT 1),
(SELECT id FROM resources LIMIT 1),
(SELECT id FROM services LIMIT 1),
NOW() + INTERVAL '1 day',
NOW() + INTERVAL '2 days',
'confirmed',
50.00,
100.00,
true;
Resultado esperado: ✅ Booking creado exitosamente (secondary_artist es opcional)
📊 Resumen de Cambios
| Elemento | Antes | Después |
|---|---|---|
| Constraint | check_secondary_artist_role con subquery |
Eliminado |
| Trigger | No existía | validate_booking_secondary_artist añadido |
| Función | No existía | validate_secondary_artist_role() añadida |
| Índice | No existía para secondary_artist_id |
idx_bookings_secondary_artist añadido |
| Loop variable | No declarada en reset_all_weekly_invitations() |
customer_record RECORD; añadido |
| Total funciones | 13 | 14 |
| Total triggers | 15+ | 17+ |
🎓 Lecciones Aprendidas
- PostgreSQL no permite subqueries en constraints CHECK
- Los triggers de validación son la alternativa correcta para validaciones complejas
- Los loops en PL/pgSQL requieren declarar las variables en el bloque
DECLARE - Los triggers pueden hacer validaciones que no son posibles con constraints
- La documentación debe mantenerse sincronizada con el código SQL
- La sintaxis de PostgreSQL es estricta y requiere declaraciones explícitas
📚 Referencias
¿Necesitas ayuda adicional? Una vez que hayas ejecutado la migración corregida, avísame para verificar que todo esté funcionando correctamente.