mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 19:24:32 +00:00
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)
315 lines
9.6 KiB
Markdown
315 lines
9.6 KiB
Markdown
# ✅ 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:
|
|
|
|
```sql
|
|
-- 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:
|
|
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:
|
|
1. **`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`
|
|
|
|
2. **`db/migrations/001_initial_schema.sql`** (ACTUALIZADO)
|
|
- Constraint problemático eliminado
|
|
- Trigger de validación añadido
|
|
- Índice para `secondary_artist_id` añadido
|
|
|
|
3. **`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
|
|
|
|
1. Ve a: **https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/sql**
|
|
2. Crea un nuevo query
|
|
3. Pega el contenido completo del archivo corregido
|
|
4. 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:
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
1. **PostgreSQL no permite subqueries** en constraints CHECK
|
|
2. **Los triggers de validación** son la alternativa correcta para validaciones complejas
|
|
3. **Los loops en PL/pgSQL** requieren declarar las variables en el bloque `DECLARE`
|
|
4. **Los triggers** pueden hacer validaciones que no son posibles con constraints
|
|
5. **La documentación** debe mantenerse sincronizada con el código SQL
|
|
6. **La sintaxis de PostgreSQL** es estricta y requiere declaraciones explícitas
|
|
|
|
## 📚 Referencias
|
|
|
|
- [PostgreSQL Constraints Documentation](https://www.postgresql.org/docs/current/ddl-constraints.html)
|
|
- [PostgreSQL Triggers Documentation](https://www.postgresql.org/docs/current/sql-createtrigger.html)
|
|
- [PostgreSQL ERROR: 0A000](https://www.postgresql.org/docs/current/errcodes-appendix.html)
|
|
|
|
---
|
|
|
|
**¿Necesitas ayuda adicional?** Una vez que hayas ejecutado la migración corregida, avísame para verificar que todo esté funcionando correctamente.
|