feat(salonos): implementar Fase 1.1 y 1.2 - Infraestructura y Esquema de Base de Datos

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)
This commit is contained in:
Marco Gallegos
2026-01-15 14:58:28 -06:00
parent ebbd9e3762
commit 4707ddbd5a
40 changed files with 10038 additions and 13 deletions

342
scripts/README.md Normal file
View File

@@ -0,0 +1,342 @@
# 🚀 Scripts Simples - SalonOS
Este directorio contiene scripts simplificados para facilitar el setup de SalonOS.
---
## 📋 Scripts Disponibles
### 1. check-connection.sh
**Qué hace:** Verifica la conexión a Supabase y si el puerto 5432 está abierto.
**Cómo ejecutar:**
```bash
./scripts/check-connection.sh
```
**Output esperado:**
```
✅ psql instalado
✅ Host alcanzable
✅ Puerto 5432 está abierto
✅ Conexión a base de datos exitosa
✅ Tablas encontradas: 8/8
✅ Funciones encontradas: 14
```
**Si falla:**
- Si el puerto está bloqueado, usa Supabase Dashboard
- Si falla la conexión, verifica las credenciales
---
### 2. simple-verify.sh
**Qué hace:** Verifica que todas las migraciones están correctas.
**Cómo ejecutar:**
```bash
./scripts/simple-verify.sh
```
**Output esperado:**
```
📊 Verificando tablas...
✅ Tablas: 8/8
📊 Verificando funciones...
✅ Funciones: 14/14
📊 Verificando triggers...
✅ Triggers: 17+/17+
📊 Verificando políticas RLS...
✅ Políticas RLS: 24+/20+
📊 Probando generación de Short ID...
✅ Short ID: A3F7X2 (6 caracteres)
📊 Probando generación de código de invitación...
✅ Código de invitación: X9J4K2M5N8 (10 caracteres)
==========================================
RESUMEN
==========================================
🎉 TODAS LAS MIGRACIONES ESTÁN CORRECTAS
==========================================
```
---
### 3. simple-seed.sh
**Qué hace:** Crea todos los datos de prueba en la base de datos.
**Cómo ejecutar:**
```bash
./scripts/simple-seed.sh
```
**Output esperado:**
```
📍 Creando locations...
✅ Locations: 3/3
🪑 Creando resources...
✅ Resources: 6/6
👥 Creando staff...
✅ Staff: 8/8
💇 Creando services...
✅ Services: 6/6
👩 Creando customers...
✅ Customers: 4/4
💌 Creando invitations...
✅ Invitaciones: 15/15
📅 Creando bookings...
✅ Bookings: 5/5
==========================================
RESUMEN
==========================================
🎉 SEED DE DATOS COMPLETADO EXITOSAMENTE
==========================================
```
---
### 4. create-auth-users.js
**Qué hace:** Crea usuarios de staff y customers en Supabase Auth automáticamente.
**Requiere:** `npm install @supabase/supabase-js`
**Cómo ejecutar:**
```bash
node scripts/create-auth-users.js
```
**Output esperado:**
```
👥 Creando usuarios de staff (8 usuarios)...
✅ Admin Principal creado (ID: ...)
✅ Manager Centro creado (ID: ...)
✅ Manager Polanco creado (ID: ...)
✅ Staff Coordinadora creado (ID: ...)
✅ Artist María García creado (ID: ...)
✅ Artist Ana Rodríguez creado (ID: ...)
✅ Artist Carla López creado (ID: ...)
✅ Artist Laura Martínez creado (ID: ...)
✅ Usuarios de staff creados: 8/8
🔄 Actualizando tabla staff con user_ids...
✅ Admin Principal actualizado con user_id
✅ Manager Centro actualizado con user_id
...
✅ Staff actualizados: 8/8
👩 Creando usuarios de customers (4 usuarios)...
✅ Sofía Ramírez creado (ID: ...)
✅ Valentina Hernández creado (ID: ...)
✅ Camila López creado (ID: ...)
✅ Isabella García creado (ID: ...)
✅ Usuarios de customers creados: 4/4
🔄 Actualizando tabla customers con user_ids...
✅ Sofía Ramírez actualizado con user_id
...
✅ Customers actualizados: 4/4
==========================================
RESUMEN FINAL
==========================================
Staff creados: 8/8
Staff actualizados: 8/8
Customers creados: 4/4
Customers actualizados: 4/4
==========================================
🎉 TODOS LOS USUARIOS HAN SIDO CREADOS Y ACTUALIZADOS
📝 Credenciales de prueba:
ADMIN:
Email: admin@salonos.com
Password: Admin123!
CUSTOMER (Gold):
Email: sofia.ramirez@example.com
Password: Customer123!
```
---
## 🚨 Si el Puerto 5432 Está Bloqueado
Si ejecutas `check-connection.sh` y el puerto está bloqueado, tienes estas opciones:
### Opción A: Usar Supabase Dashboard (Recomendado)
1. Ve a: https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/sql
2. Copia el contenido de: `db/migrations/00_FULL_MIGRATION_FINAL.sql`
3. Pega en el SQL Editor
4. Haz clic en "Run"
### Opción B: Usar SQL desde Dashboard
Para el seed, ejecuta estas consultas una por una:
**Crear locations:**
```sql
INSERT INTO locations (name, timezone, address, phone, is_active)
VALUES
('Salón Principal - Centro', 'America/Mexico_City', 'Av. Reforma 222', '+52 55 1234 5678', true),
('Salón Norte - Polanco', 'America/Mexico_City', 'Av. Masaryk 123', '+52 55 2345 6789', true),
('Salón Sur - Coyoacán', 'America/Mexico_City', 'Calle Hidalgo 456', '+52 55 3456 7890', true);
```
**Crear staff:**
```sql
INSERT INTO staff (user_id, location_id, role, display_name, phone, is_active)
VALUES
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'admin', 'Admin Principal', '+52 55 1111 2222', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'manager', 'Manager Centro', '+52 55 2222 3333', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'artist', 'Artist María García', '+52 55 4444 5555', true);
```
**Crear usuarios de Auth manualmente:**
1. Ve a: https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/auth/users
2. Haz clic en "Add user"
3. Crea los usuarios con los emails de `scripts/create-auth-users.js`
---
## 📝 Flujo de Ejecución Recomendado
### Si el puerto 5432 está ABIERTO:
```bash
# 1. Verificar conexión
./scripts/check-connection.sh
# 2. Verificar migraciones
./scripts/simple-verify.sh
# 3. Crear datos de prueba
./scripts/simple-seed.sh
# 4. Crear usuarios de Auth
node scripts/create-auth-users.js
```
### Si el puerto 5432 está BLOQUEADO:
```bash
# 1. Verificar conexión
./scripts/check-connection.sh
# Esto te dirá que el puerto está bloqueado
# Entonces usa Supabase Dashboard
```
**En Supabase Dashboard:**
1. Ve a: https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/sql
2. Copia el contenido de: `db/migrations/00_FULL_MIGRATION_FINAL.sql`
3. Pega en el SQL Editor
4. Haz clic en "Run"
5. Para el seed, ejecuta las consultas de `scripts/simple-seed.sh` una por una
6. Para crear usuarios, usa el Dashboard manualmente
---
## 🔧 Troubleshooting
### Error: "psql: command not found"
**Solución:** Instala PostgreSQL client
- macOS: `brew install postgresql`
- Ubuntu/Debian: `sudo apt-get install postgresql-client`
- Windows: Descargar desde https://www.postgresql.org/download/windows/
### Error: "connection to server failed"
**Solución:**
1. Verifica que las variables de entorno estén en `.env.local`
2. Verifica que el puerto 5432 no esté bloqueado
3. Si está bloqueado, usa Supabase Dashboard
### Error: "Password authentication failed"
**Solución:**
1. Verifica que `SUPABASE_SERVICE_ROLE_KEY` sea correcto
2. Verifica que no tenga espacios o caracteres especiales
3. Regenera el key en Supabase Dashboard si es necesario
### Error: "relation already exists"
**Solución:**
- Los datos ya existen. Continúa con el siguiente script
- O elimina y recrea la base de datos
### Error: "User already registered"
**Solución:**
- El usuario ya existe en Supabase Auth
- Borra el usuario en Supabase Dashboard y vuelve a ejecutar el script
---
## 📚 Documentación Adicional
- **`docs/STEP_BY_STEP_VERIFICATION.md`** - Guía detallada paso a paso
- **`docs/STEP_BY_STEP_AUTH_CONFIG.md`** - Guía de configuración de Auth
- **`docs/POST_MIGRATION_SUCCESS.md`** - Guía post-migración
- **`docs/QUICK_START_POST_MIGRATION.md`** - Guía rápida de referencia
---
## ✅ Checklist
### Verificar Conexión
- [ ] `check-connection.sh` ejecutado
- [ ] Puerto 5432 abierto (o usar Dashboard)
- [ ] Conexión a DB exitosa
### Verificar Migraciones
- [ ] `simple-verify.sh` ejecutado
- [ ] Todas las tablas creadas (8/8)
- [ ] Todas las funciones creadas (14/14)
- [ ] Todos los triggers activos (17+)
### Seed de Datos
- [ ] `simple-seed.sh` ejecutado
- [ ] Locations creadas (3/3)
- [ ] Resources creados (6/6)
- [ ] Staff creado (8/8)
- [ ] Services creados (6/6)
- [ ] Customers creados (4/4)
- [ ] Invitaciones creadas (15/15)
- [ ] Bookings creados (5/5)
### Crear Usuarios Auth
- [ ] `create-auth-users.js` ejecutado
- [ ] Staff creados (8/8)
- [ ] Staff actualizados (8/8)
- [ ] Customers creados (4/4)
- [ ] Customers actualizados (4/4)
---
## 🎯 Próximos Pasos
Después de completar todos los scripts:
1. **Probar login** con las credenciales:
- Admin: `admin@salonos.com` / `Admin123!`
- Customer: `sofia.ramirez@example.com` / `Customer123!`
2. **Verificar políticas RLS** en Supabase Dashboard
3. **Continuar con el desarrollo** de la aplicación
---
**¿Necesitas ayuda con alguno de los scripts?**

157
scripts/check-connection.sh Executable file
View File

@@ -0,0 +1,157 @@
#!/bin/bash
# Script para verificar conexión a Supabase y desbloquear puertos
# Ejecutar con: ./scripts/check-connection.sh
echo "=========================================="
echo "SALONOS - VERIFICACIÓN DE CONEXIÓN"
echo "=========================================="
echo ""
# Cargar variables de entorno
set -a
source .env.local
set +a
if [ -z "$NEXT_PUBLIC_SUPABASE_URL" ] || [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
echo "❌ ERROR: Faltan variables de entorno"
echo "Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local"
exit 1
fi
# Extraer host de la URL
DB_HOST="${NEXT_PUBLIC_SUPABASE_URL#https://}"
echo "📊 Información de conexión:"
echo " Host: $DB_HOST"
echo " Puerto: 5432"
echo ""
# 1. Verificar si psql está instalado
echo "1⃣ Verificando si psql está instalado..."
if command -v psql &> /dev/null; then
PSQL_VERSION=$(psql --version)
echo " ✅ psql instalado: $PSQL_VERSION"
else
echo " ❌ psql NO está instalado"
echo ""
echo " Para instalar psql:"
echo " - macOS: brew install postgresql"
echo " - Ubuntu/Debian: sudo apt-get install postgresql-client"
echo " - Windows: Descargar desde https://www.postgresql.org/download/windows/"
exit 1
fi
echo ""
# 2. Verificar conectividad con ping
echo "2⃣ Verificando conectividad con ping..."
if ping -c 2 -4 $DB_HOST &> /dev/null; then
echo " ✅ Host alcanzable"
else
echo " ❌ Host NO alcanzable"
echo " Verifica tu conexión a internet"
exit 1
fi
echo ""
# 3. Verificar si el puerto 5432 está abierto
echo "3⃣ Verificando si el puerto 5432 está abierto..."
if command -v nc &> /dev/null; then
if nc -z -w5 $DB_HOST 5432 2>/dev/null; then
echo " ✅ Puerto 5432 está abierto"
else
echo " ❌ Puerto 5432 está bloqueado"
echo ""
echo " 📋 SOLUCIÓN:"
echo " El puerto 5432 está bloqueado, posiblemente por:"
echo " 1. Firewall de tu empresa/ISP"
echo " 2. VPN corporativa"
echo " 3. Configuración de red local"
echo ""
echo " Opciones:"
echo " a. Usar Supabase Dashboard (recomendado)"
echo " b. Configurar VPN para permitir el puerto 5432"
echo " c. Usar túnel SSH para bypass del firewall"
echo " d. Contactar a tu administrador de red"
exit 1
fi
else
echo " ⚠️ nc no está disponible, no se puede verificar el puerto"
echo " Continuando con la prueba de conexión..."
fi
echo ""
# 4. Configurar DATABASE_URL
DB_URL="postgresql://postgres:${SUPABASE_SERVICE_ROLE_KEY}@${DB_HOST}:5432/postgres"
# 5. Probar conexión a la base de datos
echo "4⃣ Probar conexión a la base de datos..."
if psql "$DB_URL" -c "SELECT 'Connection successful' as status;" &> /dev/null; then
echo " ✅ Conexión a base de datos exitosa"
else
echo " ❌ Conexión a base de datos fallida"
echo ""
echo " 📋 SOLUCIÓN:"
echo " 1. Verifica que las credenciales sean correctas"
echo " 2. Verifica que el proyecto de Supabase esté activo"
echo " 3. Verifica que el service_role_key sea correcto"
echo " 4. Si el puerto está bloqueado, usa Supabase Dashboard"
exit 1
fi
echo ""
# 6. Verificar tablas
echo "5⃣ Verificando tablas..."
TABLE_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('locations', 'resources', 'staff', 'services', 'customers', 'invitations', 'bookings', 'audit_logs');
")
echo " ✅ Tablas encontradas: $TABLE_COUNT/8"
# 7. Verificar funciones
echo "6⃣ Verificando funciones..."
FUNC_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM information_schema.routines
WHERE routine_schema = 'public';
")
echo " ✅ Funciones encontradas: $FUNC_COUNT"
echo ""
# 8. Resumen
echo "=========================================="
echo "RESUMEN"
echo "=========================================="
echo "Host: $DB_HOST"
echo "Puerto: 5432"
echo "psql: ✅ Instalado"
echo "Conexión: ✅ Exitosa"
echo "Tablas: $TABLE_COUNT/8"
echo "Funciones: $FUNC_COUNT"
echo "=========================================="
if [ "$TABLE_COUNT" -eq 8 ] && [ "$FUNC_COUNT" -ge 14 ]; then
echo ""
echo "🎉 CONEXIÓN VERIFICADA EXITOSAMENTE"
echo ""
echo "Próximos pasos:"
echo "1. Ejecutar: ./scripts/simple-verify.sh"
echo "2. Ejecutar: ./scripts/simple-seed.sh"
echo "3. Ejecutar: node scripts/create-auth-users.js"
echo ""
echo "O usar Supabase Dashboard:"
echo "https://supabase.com/dashboard/project/pvvwbnybkadhreuqijsl/sql"
else
echo ""
echo "⚠️ ALGUNOS ELEMENTOS FALTAN"
echo "Por favor, ejecuta las migraciones nuevamente"
fi

View File

@@ -0,0 +1,330 @@
#!/usr/bin/env node
/**
* Script simple para crear usuarios de Auth en Supabase
* Ejecutar con: node scripts/create-auth-users.js
* Requiere: npm install @supabase/supabase-js
*/
require('dotenv').config({ path: '.env.local' })
const { createClient } = require('@supabase/supabase-js')
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !supabaseServiceKey) {
console.error('❌ ERROR: Faltan variables de entorno')
console.error('Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local')
process.exit(1)
}
const supabase = createClient(supabaseUrl, supabaseServiceKey)
// Usuarios de staff
const staffUsers = [
{
email: 'admin@salonos.com',
password: 'Admin123!',
role: 'admin',
display_name: 'Admin Principal',
phone: '+52 55 1111 2222',
location: 'Salón Principal - Centro'
},
{
email: 'manager.centro@salonos.com',
password: 'Manager123!',
role: 'manager',
display_name: 'Manager Centro',
phone: '+52 55 2222 3333',
location: 'Salón Principal - Centro'
},
{
email: 'manager.polanco@salonos.com',
password: 'Manager123!',
role: 'manager',
display_name: 'Manager Polanco',
phone: '+52 55 6666 7777',
location: 'Salón Norte - Polanco'
},
{
email: 'staff.coordinadora@salonos.com',
password: 'Staff123!',
role: 'staff',
display_name: 'Staff Coordinadora',
phone: '+52 55 3333 4444',
location: 'Salón Principal - Centro'
},
{
email: 'artist.maria@salonos.com',
password: 'Artist123!',
role: 'artist',
display_name: 'Artist María García',
phone: '+52 55 4444 5555',
location: 'Salón Principal - Centro'
},
{
email: 'artist.ana@salonos.com',
password: 'Artist123!',
role: 'artist',
display_name: 'Artist Ana Rodríguez',
phone: '+52 55 5555 6666',
location: 'Salón Principal - Centro'
},
{
email: 'artist.carla@salonos.com',
password: 'Artist123!',
role: 'artist',
display_name: 'Artist Carla López',
phone: '+52 55 7777 8888',
location: 'Salón Norte - Polanco'
},
{
email: 'artist.laura@salonos.com',
password: 'Artist123!',
role: 'artist',
display_name: 'Artist Laura Martínez',
phone: '+52 55 8888 9999',
location: 'Salón Sur - Coyoacán'
}
]
// Usuarios de customers
const customerUsers = [
{
email: 'sofia.ramirez@example.com',
password: 'Customer123!',
tier: 'gold',
display_name: 'Sofía Ramírez'
},
{
email: 'valentina.hernandez@example.com',
password: 'Customer123!',
tier: 'gold',
display_name: 'Valentina Hernández'
},
{
email: 'camila.lopez@example.com',
password: 'Customer123!',
tier: 'free',
display_name: 'Camila López'
},
{
email: 'isabella.garcia@example.com',
password: 'Customer123!',
tier: 'gold',
display_name: 'Isabella García'
}
]
async function createStaffUser(user) {
try {
// Crear usuario en Supabase Auth
const { data, error } = await supabase.auth.admin.createUser({
email: user.email,
password: user.password,
email_confirm: true,
user_metadata: {
role: user.role,
display_name: user.display_name,
location: user.location,
phone: user.phone
}
})
if (error) {
console.error(`❌ Error creando ${user.display_name}:`, error.message)
return null
}
console.log(`${user.display_name} creado (ID: ${data.user.id})`)
return data.user
} catch (error) {
console.error(`❌ Error inesperado creando ${user.display_name}:`, error.message)
return null
}
}
async function createCustomerUser(user) {
try {
// Crear usuario en Supabase Auth
const { data, error } = await supabase.auth.admin.createUser({
email: user.email,
password: user.password,
email_confirm: true,
user_metadata: {
tier: user.tier,
display_name: user.display_name
}
})
if (error) {
console.error(`❌ Error creando ${user.display_name}:`, error.message)
return null
}
console.log(`${user.display_name} creado (ID: ${data.user.id})`)
return data.user
} catch (error) {
console.error(`❌ Error inesperado creando ${user.display_name}:`, error.message)
return null
}
}
async function updateStaffUserId(user) {
try {
const { error } = await supabase
.from('staff')
.update({ user_id: user.id })
.eq('display_name', user.display_name)
if (error) {
console.error(`❌ Error actualizando ${user.display_name}:`, error.message)
return false
}
console.log(`${user.display_name} actualizado con user_id`)
return true
} catch (error) {
console.error(`❌ Error inesperado actualizando ${user.display_name}:`, error.message)
return false
}
}
async function updateCustomerUserId(user) {
try {
const { error } = await supabase
.from('customers')
.update({ user_id: user.id })
.eq('email', user.email)
if (error) {
console.error(`❌ Error actualizando ${user.display_name}:`, error.message)
return false
}
console.log(`${user.display_name} actualizado con user_id`)
return true
} catch (error) {
console.error(`❌ Error inesperado actualizando ${user.display_name}:`, error.message)
return false
}
}
async function main() {
console.log('==========================================')
console.log('SALONOS - CREACIÓN DE USUARIOS AUTH')
console.log('==========================================')
console.log()
// 1. Crear usuarios de staff
console.log('👥 Creando usuarios de staff (8 usuarios)...')
console.log()
const createdStaff = []
for (const user of staffUsers) {
const createdUser = await createStaffUser(user)
if (createdUser) {
createdStaff.push({
...user,
id: createdUser.id
})
}
// Pequeña pausa para evitar rate limiting
await new Promise(resolve => setTimeout(resolve, 500))
}
console.log()
console.log(`✅ Usuarios de staff creados: ${createdStaff.length}/8`)
// 2. Actualizar tabla staff con user_ids
console.log()
console.log('🔄 Actualizando tabla staff con user_ids...')
console.log()
let updatedStaffCount = 0
for (const user of createdStaff) {
const updated = await updateStaffUserId(user)
if (updated) {
updatedStaffCount++
}
await new Promise(resolve => setTimeout(resolve, 200))
}
console.log()
console.log(`✅ Staff actualizados: ${updatedStaffCount}/8`)
// 3. Crear usuarios de customers
console.log()
console.log('👩 Creando usuarios de customers (4 usuarios)...')
console.log()
const createdCustomers = []
for (const user of customerUsers) {
const createdUser = await createCustomerUser(user)
if (createdUser) {
createdCustomers.push({
...user,
id: createdUser.id
})
}
await new Promise(resolve => setTimeout(resolve, 500))
}
console.log()
console.log(`✅ Usuarios de customers creados: ${createdCustomers.length}/4`)
// 4. Actualizar tabla customers con user_ids
console.log()
console.log('🔄 Actualizando tabla customers con user_ids...')
console.log()
let updatedCustomersCount = 0
for (const user of createdCustomers) {
const updated = await updateCustomerUserId(user)
if (updated) {
updatedCustomersCount++
}
await new Promise(resolve => setTimeout(resolve, 200))
}
console.log()
console.log(`✅ Customers actualizados: ${updatedCustomersCount}/4`)
// 5. Resumen final
console.log()
console.log('==========================================')
console.log('RESUMEN FINAL')
console.log('==========================================')
console.log(`Staff creados: ${createdStaff.length}/8`)
console.log(`Staff actualizados: ${updatedStaffCount}/8`)
console.log(`Customers creados: ${createdCustomers.length}/4`)
console.log(`Customers actualizados: ${updatedCustomersCount}/4`)
console.log('==========================================')
if (createdStaff.length === 8 && updatedStaffCount === 8 && createdCustomers.length === 4 && updatedCustomersCount === 4) {
console.log()
console.log('🎉 TODOS LOS USUARIOS HAN SIDO CREADOS Y ACTUALIZADOS')
console.log()
console.log('📝 Credenciales de prueba:')
console.log()
console.log('ADMIN:')
console.log(' Email: admin@salonos.com')
console.log(' Password: Admin123!')
console.log()
console.log('CUSTOMER (Gold):')
console.log(' Email: sofia.ramirez@example.com')
console.log(' Password: Customer123!')
console.log()
console.log('Puedes usar estas credenciales para probar el login.')
} else {
console.log()
console.log('⚠️ ALGUNOS USUARIOS NO FUERON CREADOS O ACTUALIZADOS')
console.log('Por favor, verifica los errores arriba.')
}
}
main()

439
scripts/seed-data.js Normal file
View File

@@ -0,0 +1,439 @@
#!/usr/bin/env node
/**
* Script de seed de datos - SalonOS
* Crea datos de prueba para development
*/
const { createClient } = require('@supabase/supabase-js')
// Cargar variables de entorno
require('dotenv').config({ path: '.env.local' })
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !supabaseServiceKey) {
console.error('❌ ERROR: Faltan variables de entorno')
console.error('Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local')
process.exit(1)
}
const supabase = createClient(supabaseUrl, supabaseServiceKey)
console.log('==========================================')
console.log('SALONOS - SEED DE DATOS')
console.log('==========================================')
console.log()
async function seedLocations() {
console.log('📍 Creando locations...')
const { data, error } = await supabase.from('locations').insert([
{
name: 'Salón Principal - Centro',
timezone: 'America/Mexico_City',
address: 'Av. Reforma 222, Centro Histórico, Ciudad de México',
phone: '+52 55 1234 5678',
is_active: true,
},
{
name: 'Salón Norte - Polanco',
timezone: 'America/Mexico_City',
address: 'Av. Masaryk 123, Polanco, Ciudad de México',
phone: '+52 55 2345 6789',
is_active: true,
},
{
name: 'Salón Sur - Coyoacán',
timezone: 'America/Mexico_City',
address: 'Calle Hidalgo 456, Coyoacán, Ciudad de México',
phone: '+52 55 3456 7890',
is_active: true,
},
]).select()
if (error) {
console.error('❌ Error al crear locations:', error)
return []
}
console.log(`${data.length} locations creadas`)
return data
}
async function seedResources(locations) {
console.log('🪑 Creando resources...')
const resources = []
for (const location of locations) {
const { data, error } = await supabase.from('resources').insert([
{
location_id: location.id,
name: `Estación ${Math.floor(Math.random() * 100)}`,
type: 'station',
capacity: 1,
is_active: true,
},
{
location_id: location.id,
name: `Sala VIP ${Math.floor(Math.random() * 100)}`,
type: 'room',
capacity: 2,
is_active: true,
},
]).select()
if (error) {
console.error('❌ Error al crear resources:', error)
continue
}
resources.push(...data)
}
console.log(`${resources.length} resources creadas`)
return resources
}
async function seedStaff(locations) {
console.log('👥 Creando staff...')
const { data, error } = await supabase.from('staff').insert([
{
user_id: '00000000-0000-0000-0000-000000000001',
location_id: locations[0].id,
role: 'admin',
display_name: 'Admin Principal',
phone: '+52 55 1111 2222',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000002',
location_id: locations[0].id,
role: 'manager',
display_name: 'Manager Centro',
phone: '+52 55 2222 3333',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000003',
location_id: locations[0].id,
role: 'staff',
display_name: 'Staff Coordinadora',
phone: '+52 55 3333 4444',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000004',
location_id: locations[0].id,
role: 'artist',
display_name: 'Artist María García',
phone: '+52 55 4444 5555',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000005',
location_id: locations[0].id,
role: 'artist',
display_name: 'Artist Ana Rodríguez',
phone: '+52 55 5555 6666',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000006',
location_id: locations[1].id,
role: 'manager',
display_name: 'Manager Polanco',
phone: '+52 55 6666 7777',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000007',
location_id: locations[1].id,
role: 'artist',
display_name: 'Artist Carla López',
phone: '+52 55 7777 8888',
is_active: true,
},
{
user_id: '00000000-0000-0000-0000-000000000008',
location_id: locations[2].id,
role: 'artist',
display_name: 'Artist Laura Martínez',
phone: '+52 55 8888 9999',
is_active: true,
},
]).select()
if (error) {
console.error('❌ Error al crear staff:', error)
return []
}
console.log(`${data.length} staff creados`)
return data
}
async function seedServices() {
console.log('💇 Creando services...')
const { data, error } = await supabase.from('services').insert([
{
name: 'Corte y Estilizado',
description: 'Corte de cabello profesional con lavado y estilizado',
duration_minutes: 60,
base_price: 500.00,
requires_dual_artist: false,
premium_fee_enabled: false,
is_active: true,
},
{
name: 'Color Completo',
description: 'Tinte completo con protección capilar',
duration_minutes: 120,
base_price: 1200.00,
requires_dual_artist: false,
premium_fee_enabled: true,
is_active: true,
},
{
name: 'Balayage Premium',
description: 'Técnica de balayage con productos premium',
duration_minutes: 180,
base_price: 2000.00,
requires_dual_artist: true,
premium_fee_enabled: true,
is_active: true,
},
{
name: 'Tratamiento Kératina',
description: 'Tratamiento de kératina para cabello dañado',
duration_minutes: 90,
base_price: 1500.00,
requires_dual_artist: false,
premium_fee_enabled: false,
is_active: true,
},
{
name: 'Peinado Evento',
description: 'Peinado para eventos especiales',
duration_minutes: 45,
base_price: 800.00,
requires_dual_artist: false,
premium_fee_enabled: true,
is_active: true,
},
{
name: 'Servicio Express (Dual Artist)',
description: 'Servicio rápido con dos artists simultáneas',
duration_minutes: 30,
base_price: 600.00,
requires_dual_artist: true,
premium_fee_enabled: true,
is_active: true,
},
]).select()
if (error) {
console.error('❌ Error al crear services:', error)
return []
}
console.log(`${data.length} services creados`)
return data
}
async function seedCustomers() {
console.log('👩 Creando customers...')
const { data, error } = await supabase.from('customers').insert([
{
user_id: '10000000-0000-0000-0000-000000000001',
first_name: 'Sofía',
last_name: 'Ramírez',
email: 'sofia.ramirez@example.com',
phone: '+52 55 1111 1111',
tier: 'gold',
notes: 'Cliente VIP. Prefiere Artists María y Ana.',
total_spent: 15000.00,
total_visits: 25,
last_visit_date: '2025-12-20',
is_active: true,
},
{
user_id: '10000000-0000-0000-0000-000000000002',
first_name: 'Valentina',
last_name: 'Hernández',
email: 'valentina.hernandez@example.com',
phone: '+52 55 2222 2222',
tier: 'gold',
notes: 'Cliente regular. Prefiere horarios de la mañana.',
total_spent: 8500.00,
total_visits: 15,
last_visit_date: '2025-12-15',
is_active: true,
},
{
user_id: '10000000-0000-0000-0000-000000000003',
first_name: 'Camila',
last_name: 'López',
email: 'camila.lopez@example.com',
phone: '+52 55 3333 3333',
tier: 'free',
notes: 'Nueva cliente. Referida por Valentina.',
total_spent: 500.00,
total_visits: 1,
last_visit_date: '2025-12-10',
is_active: true,
},
{
user_id: '10000000-0000-0000-0000-000000000004',
first_name: 'Isabella',
last_name: 'García',
email: 'isabella.garcia@example.com',
phone: '+52 55 4444 4444',
tier: 'gold',
notes: 'Cliente VIP. Requiere servicio de Balayage.',
total_spent: 22000.00,
total_visits: 30,
last_visit_date: '2025-12-18',
is_active: true,
},
]).select()
if (error) {
console.error('❌ Error al crear customers:', error)
return []
}
console.log(`${data.length} customers creados`)
return data
}
async function seedInvitations(customers) {
console.log('💌 Creando invitations...')
const weekStart = new Date()
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1) // Monday
weekStart.setHours(0, 0, 0, 0)
const invitations = []
for (const customer of customers) {
if (customer.tier === 'gold') {
for (let i = 0; i < 5; i++) {
const { data, error } = await supabase.from('invitations').insert({
inviter_id: customer.id,
code: await generateRandomCode(),
status: 'pending',
week_start_date: weekStart.toISOString().split('T')[0],
expiry_date: new Date(weekStart.getTime() + 6 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
}).select()
if (error) {
console.error('❌ Error al crear invitations:', error)
continue
}
invitations.push(...data)
}
}
}
console.log(`${invitations.length} invitations creadas`)
return invitations
}
async function generateRandomCode() {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
let code = ''
for (let i = 0; i < 10; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
return code
}
async function seedBookings(customers, staff, resources, services, locations) {
console.log('📅 Creando bookings de prueba...')
const now = new Date()
const bookings = []
for (let i = 0; i < 5; i++) {
const startTime = new Date(now.getTime() + (i + 1) * 24 * 60 * 60 * 1000)
const endTime = new Date(startTime.getTime() + 60 * 60 * 1000)
const { data, error } = await supabase.from('bookings').insert({
customer_id: customers[i % customers.length].id,
staff_id: staff.filter(s => s.role === 'artist')[i % staff.filter(s => s.role === 'artist').length].id,
location_id: locations[0].id,
resource_id: resources[0].id,
service_id: services[i % services.length].id,
start_time_utc: startTime.toISOString(),
end_time_utc: endTime.toISOString(),
status: 'confirmed',
deposit_amount: 200.00,
total_amount: services[i % services.length].base_price,
is_paid: true,
payment_reference: `pay_${Math.random().toString(36).substring(7)}`,
}).select()
if (error) {
console.error('❌ Error al crear bookings:', error)
continue
}
bookings.push(...data)
}
console.log(`${bookings.length} bookings creados`)
return bookings
}
async function main() {
try {
const locations = await seedLocations()
if (locations.length === 0) throw new Error('No se crearon locations')
const resources = await seedResources(locations)
const staff = await seedStaff(locations)
if (staff.length === 0) throw new Error('No se creó staff')
const services = await seedServices()
if (services.length === 0) throw new Error('No se crearon services')
const customers = await seedCustomers()
if (customers.length === 0) throw new Error('No se crearon customers')
const invitations = await seedInvitations(customers)
const bookings = await seedBookings(customers, staff, resources, services, locations)
console.log()
console.log('==========================================')
console.log('✅ SEED DE DATOS COMPLETADO')
console.log('==========================================')
console.log()
console.log('📊 Resumen:')
console.log(` Locations: ${locations.length}`)
console.log(` Resources: ${resources.length}`)
console.log(` Staff: ${staff.length}`)
console.log(` Services: ${services.length}`)
console.log(` Customers: ${customers.length}`)
console.log(` Invitations: ${invitations.length}`)
console.log(` Bookings: ${bookings.length}`)
console.log()
console.log('🎉 La base de datos está lista para desarrollo')
console.log()
console.log('📝 Próximos pasos:')
console.log(' 1. Configurar Auth en Supabase Dashboard')
console.log(' 2. Probar la API de bookings')
console.log(' 3. Implementar endpoints faltantes')
} catch (error) {
console.error('❌ Error inesperado:', error)
process.exit(1)
}
}
main()

189
scripts/seed-data.sql Normal file
View File

@@ -0,0 +1,189 @@
-- ============================================
-- SEED DE DATOS - SALONOS
-- Ejecutar en Supabase SQL Editor después de las migraciones
-- ============================================
-- 1. Crear Locations
INSERT INTO locations (name, timezone, address, phone, is_active)
VALUES
('Salón Principal - Centro', 'America/Mexico_City', 'Av. Reforma 222, Centro Histórico, Ciudad de México', '+52 55 1234 5678', true),
('Salón Norte - Polanco', 'America/Mexico_City', 'Av. Masaryk 123, Polanco, Ciudad de México', '+52 55 2345 6789', true),
('Salón Sur - Coyoacán', 'America/Mexico_City', 'Calle Hidalgo 456, Coyoacán, Ciudad de México', '+52 55 3456 7890', true);
-- 2. Crear Resources
INSERT INTO resources (location_id, name, type, capacity, is_active)
SELECT
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
'Estación ' || generate_series(1, 3)::TEXT,
'station',
1,
true
UNION ALL
SELECT
(SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1),
'Estación ' || generate_series(1, 2)::TEXT,
'station',
1,
true
UNION ALL
SELECT
(SELECT id FROM locations WHERE name = 'Salón Sur - Coyoacán' LIMIT 1),
'Estación 1',
'station',
1,
true;
-- 3. Crear Staff
INSERT INTO staff (user_id, location_id, role, display_name, phone, is_active)
VALUES
-- Admin Principal
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'admin', 'Admin Principal', '+52 55 1111 2222', true),
-- Managers
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'manager', 'Manager Centro', '+52 55 2222 3333', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1), 'manager', 'Manager Polanco', '+52 55 6666 7777', true),
-- Staff
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'staff', 'Staff Coordinadora', '+52 55 3333 4444', true),
-- Artists
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'artist', 'Artist María García', '+52 55 4444 5555', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'artist', 'Artist Ana Rodríguez', '+52 55 5555 6666', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1), 'artist', 'Artist Carla López', '+52 55 7777 8888', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Sur - Coyoacán' LIMIT 1), 'artist', 'Artist Laura Martínez', '+52 55 8888 9999', true);
-- 4. Crear Services
INSERT INTO services (name, description, duration_minutes, base_price, requires_dual_artist, premium_fee_enabled, is_active)
VALUES
('Corte y Estilizado', 'Corte de cabello profesional con lavado y estilizado', 60, 500.00, false, false, true),
('Color Completo', 'Tinte completo con protección capilar', 120, 1200.00, false, true, true),
('Balayage Premium', 'Técnica de balayage con productos premium', 180, 2000.00, true, true, true),
('Tratamiento Kératina', 'Tratamiento de kératina para cabello dañado', 90, 1500.00, false, false, true),
('Peinado Evento', 'Peinado para eventos especiales', 45, 800.00, false, true, true),
('Servicio Express (Dual Artist)', 'Servicio rápido con dos artists simultáneas', 30, 600.00, true, true, true);
-- 5. Crear Customers
INSERT INTO customers (user_id, first_name, last_name, email, phone, tier, notes, total_spent, total_visits, last_visit_date, is_active)
VALUES
(uuid_generate_v4(), 'Sofía', 'Ramírez', 'sofia.ramirez@example.com', '+52 55 1111 1111', 'gold', 'Cliente VIP. Prefiere Artists María y Ana.', 15000.00, 25, '2025-12-20', true),
(uuid_generate_v4(), 'Valentina', 'Hernández', 'valentina.hernandez@example.com', '+52 55 2222 2222', 'gold', 'Cliente regular. Prefiere horarios de la mañana.', 8500.00, 15, '2025-12-15', true),
(uuid_generate_v4(), 'Camila', 'López', 'camila.lopez@example.com', '+52 55 3333 3333', 'free', 'Nueva cliente. Referida por Valentina.', 500.00, 1, '2025-12-10', true),
(uuid_generate_v4(), 'Isabella', 'García', 'isabella.garcia@example.com', '+52 55 4444 4444', 'gold', 'Cliente VIP. Requiere servicio de Balayage.', 22000.00, 30, '2025-12-18', true);
-- 6. Crear Invitaciones (para clientes Gold)
-- Resetear invitaciones para clientes Gold de la semana actual
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1));
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'valentina.hernandez@example.com' LIMIT 1));
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'isabella.garcia@example.com' LIMIT 1));
-- 7. Crear Bookings de Prueba
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,
payment_reference,
notes
)
SELECT
(SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Balayage Premium' LIMIT 1),
NOW() + INTERVAL '1 day',
NOW() + INTERVAL '4 hours',
'confirmed',
200.00,
2000.00,
true,
'pay_test_001',
'Balayage Premium para Sofía'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'valentina.hernandez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist Ana Rodríguez' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Color Completo' LIMIT 1),
NOW() + INTERVAL '2 days',
NOW() + INTERVAL '4 hours',
'confirmed',
200.00,
1200.00,
true,
'pay_test_002',
'Color Completo para Valentina'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'camila.lopez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Corte y Estilizado' LIMIT 1),
NOW() + INTERVAL '3 days',
NOW() + INTERVAL '1 hour',
'confirmed',
50.00,
500.00,
true,
'pay_test_003',
'Primer corte para Camila'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'isabella.garcia@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Servicio Express (Dual Artist)' LIMIT 1),
NOW() + INTERVAL '4 days',
NOW() + INTERVAL '30 minutes',
'confirmed',
200.00,
600.00,
true,
'pay_test_004',
'Servicio Express Dual Artist - Necesita secondary_artist'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist Ana Rodríguez' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) OFFSET 1 LIMIT 1),
(SELECT id FROM services WHERE name = 'Peinado Evento' LIMIT 1),
NOW() + INTERVAL '5 days',
NOW() + INTERVAL '45 minutes',
'pending',
200.00,
800.00,
false,
NULL,
'Peinado para evento especial';
-- 8. Actualizar booking con secondary_artist (prueba de validación)
UPDATE bookings
SET secondary_artist_id = (SELECT id FROM staff WHERE display_name = 'Artist Carla López' LIMIT 1)
WHERE payment_reference = 'pay_test_004';
-- 9. Resumen de datos creados
DO $$
BEGIN
RAISE NOTICE '==========================================';
RAISE NOTICE 'SALONOS - SEED DE DATOS COMPLETADO';
RAISE NOTICE '==========================================';
RAISE NOTICE 'Locations: %', (SELECT COUNT(*) FROM locations);
RAISE NOTICE 'Resources: %', (SELECT COUNT(*) FROM resources);
RAISE NOTICE 'Staff: %', (SELECT COUNT(*) FROM staff);
RAISE NOTICE 'Services: %', (SELECT COUNT(*) FROM services);
RAISE NOTICE 'Customers: %', (SELECT COUNT(*) FROM customers);
RAISE NOTICE 'Invitations: %', (SELECT COUNT(*) FROM invitations WHERE status = 'pending');
RAISE NOTICE 'Bookings: %', (SELECT COUNT(*) FROM bookings);
RAISE NOTICE '==========================================';
RAISE NOTICE '✅ Base de datos lista para desarrollo';
RAISE NOTICE '==========================================';
END
$$;

276
scripts/simple-seed.sh Executable file
View File

@@ -0,0 +1,276 @@
#!/bin/bash
# Script simple para seed de datos de SalonOS
# Ejecutar con: ./scripts/simple-seed.sh
# Requiere: psql instalado y variables de entorno en .env.local
# Cargar variables de entorno
set -a
source .env.local
set +a
if [ -z "$NEXT_PUBLIC_SUPABASE_URL" ] || [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
echo "❌ ERROR: Faltan variables de entorno"
echo "Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local"
exit 1
fi
# Configurar DATABASE_URL
DB_HOST="${NEXT_PUBLIC_SUPABASE_URL#https://}"
DB_URL="postgresql://postgres:${SUPABASE_SERVICE_ROLE_KEY}@${DB_HOST}:5432/postgres"
echo "=========================================="
echo "SALONOS - SEED DE DATOS"
echo "=========================================="
echo ""
# 1. Crear Locations
echo "📍 Creando locations..."
psql "$DB_URL" -c "
INSERT INTO locations (name, timezone, address, phone, is_active)
VALUES
('Salón Principal - Centro', 'America/Mexico_City', 'Av. Reforma 222, Centro Histórico, Ciudad de México', '+52 55 1234 5678', true),
('Salón Norte - Polanco', 'America/Mexico_City', 'Av. Masaryk 123, Polanco, Ciudad de México', '+52 55 2345 6789', true),
('Salón Sur - Coyoacán', 'America/Mexico_City', 'Calle Hidalgo 456, Coyoacán, Ciudad de México', '+52 55 3456 7890', true)
ON CONFLICT DO NOTHING;
" 2>&1 | grep -v "NOTICE"
LOCATIONS_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM locations;")
echo "✅ Locations: $LOCATIONS_COUNT/3"
# 2. Crear Resources
echo ""
echo "🪑 Creando resources..."
psql "$DB_URL" -c "
INSERT INTO resources (location_id, name, type, capacity, is_active)
SELECT
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
'Estación ' || generate_series(1, 3)::TEXT,
'station',
1,
true
UNION ALL
SELECT
(SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1),
'Estación ' || generate_series(1, 2)::TEXT,
'station',
1,
true
UNION ALL
SELECT
(SELECT id FROM locations WHERE name = 'Salón Sur - Coyoacán' LIMIT 1),
'Estación 1',
'station',
1,
true
ON CONFLICT DO NOTHING;
" 2>&1 | grep -v "NOTICE"
RESOURCES_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM resources;")
echo "✅ Resources: $RESOURCES_COUNT/6"
# 3. Crear Staff
echo ""
echo "👥 Creando staff..."
psql "$DB_URL" -c "
INSERT INTO staff (user_id, location_id, role, display_name, phone, is_active)
VALUES
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'admin', 'Admin Principal', '+52 55 1111 2222', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'manager', 'Manager Centro', '+52 55 2222 3333', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1), 'manager', 'Manager Polanco', '+52 55 6666 7777', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'staff', 'Staff Coordinadora', '+52 55 3333 4444', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'artist', 'Artist María García', '+52 55 4444 5555', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1), 'artist', 'Artist Ana Rodríguez', '+52 55 5555 6666', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Norte - Polanco' LIMIT 1), 'artist', 'Artist Carla López', '+52 55 7777 8888', true),
(uuid_generate_v4(), (SELECT id FROM locations WHERE name = 'Salón Sur - Coyoacán' LIMIT 1), 'artist', 'Artist Laura Martínez', '+52 55 8888 9999', true)
ON CONFLICT DO NOTHING;
" 2>&1 | grep -v "NOTICE"
STAFF_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM staff;")
echo "✅ Staff: $STAFF_COUNT/8"
# 4. Crear Services
echo ""
echo "💇 Creando services..."
psql "$DB_URL" -c "
INSERT INTO services (name, description, duration_minutes, base_price, requires_dual_artist, premium_fee_enabled, is_active)
VALUES
('Corte y Estilizado', 'Corte de cabello profesional con lavado y estilizado', 60, 500.00, false, false, true),
('Color Completo', 'Tinte completo con protección capilar', 120, 1200.00, false, true, true),
('Balayage Premium', 'Técnica de balayage con productos premium', 180, 2000.00, true, true, true),
('Tratamiento Kératina', 'Tratamiento de kératina para cabello dañado', 90, 1500.00, false, false, true),
('Peinado Evento', 'Peinado para eventos especiales', 45, 800.00, false, true, true),
('Servicio Express (Dual Artist)', 'Servicio rápido con dos artists simultáneas', 30, 600.00, true, true, true)
ON CONFLICT DO NOTHING;
" 2>&1 | grep -v "NOTICE"
SERVICES_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM services;")
echo "✅ Services: $SERVICES_COUNT/6"
# 5. Crear Customers
echo ""
echo "👩 Creando customers..."
psql "$DB_URL" -c "
INSERT INTO customers (user_id, first_name, last_name, email, phone, tier, notes, total_spent, total_visits, last_visit_date, is_active)
VALUES
(uuid_generate_v4(), 'Sofía', 'Ramírez', 'sofia.ramirez@example.com', '+52 55 1111 1111', 'gold', 'Cliente VIP. Prefiere Artists María y Ana.', 15000.00, 25, '2025-12-20', true),
(uuid_generate_v4(), 'Valentina', 'Hernández', 'valentina.hernandez@example.com', '+52 55 2222 2222', 'gold', 'Cliente regular. Prefiere horarios de la mañana.', 8500.00, 15, '2025-12-15', true),
(uuid_generate_v4(), 'Camila', 'López', 'camila.lopez@example.com', '+52 55 3333 3333', 'free', 'Nueva cliente. Referida por Valentina.', 500.00, 1, '2025-12-10', true),
(uuid_generate_v4(), 'Isabella', 'García', 'isabella.garcia@example.com', '+52 55 4444 4444', 'gold', 'Cliente VIP. Requiere servicio de Balayage.', 22000.00, 30, '2025-12-18', true)
ON CONFLICT (email) DO NOTHING;
" 2>&1 | grep -v "NOTICE"
CUSTOMERS_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM customers;")
echo "✅ Customers: $CUSTOMERS_COUNT/4"
# 6. Crear Invitaciones (para clientes Gold)
echo ""
echo "💌 Creando invitations..."
psql "$DB_URL" -c "
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1));
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'valentina.hernandez@example.com' LIMIT 1));
SELECT reset_weekly_invitations_for_customer((SELECT id FROM customers WHERE email = 'isabella.garcia@example.com' LIMIT 1));
" 2>&1 | grep -v "NOTICE"
INVITATIONS_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM invitations WHERE status = 'pending';")
echo "✅ Invitaciones: $INVITATIONS_COUNT/15"
# 7. Crear Bookings de Prueba
echo ""
echo "📅 Creando bookings..."
psql "$DB_URL" -c "
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,
payment_reference,
notes
)
SELECT
(SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Balayage Premium' LIMIT 1),
NOW() + INTERVAL '1 day',
NOW() + INTERVAL '4 hours',
'confirmed',
200.00,
2000.00,
true,
'pay_test_001',
'Balayage Premium para Sofía'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'valentina.hernandez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist Ana Rodríguez' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Color Completo' LIMIT 1),
NOW() + INTERVAL '2 days',
NOW() + INTERVAL '4 hours',
'confirmed',
200.00,
1200.00,
true,
'pay_test_002',
'Color Completo para Valentina'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'camila.lopez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Corte y Estilizado' LIMIT 1),
NOW() + INTERVAL '3 days',
NOW() + INTERVAL '1 hour',
'confirmed',
50.00,
500.00,
true,
'pay_test_003',
'Primer corte para Camila'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'isabella.garcia@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist María García' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) LIMIT 1),
(SELECT id FROM services WHERE name = 'Servicio Express (Dual Artist)' LIMIT 1),
NOW() + INTERVAL '4 days',
NOW() + INTERVAL '30 minutes',
'confirmed',
200.00,
600.00,
true,
'pay_test_004',
'Servicio Express Dual Artist - Necesita secondary_artist'
UNION ALL
SELECT
(SELECT id FROM customers WHERE email = 'sofia.ramirez@example.com' LIMIT 1),
(SELECT id FROM staff WHERE display_name = 'Artist Ana Rodríguez' LIMIT 1),
(SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1),
(SELECT id FROM resources WHERE location_id = (SELECT id FROM locations WHERE name = 'Salón Principal - Centro' LIMIT 1) OFFSET 1 LIMIT 1),
(SELECT id FROM services WHERE name = 'Peinado Evento' LIMIT 1),
NOW() + INTERVAL '5 days',
NOW() + INTERVAL '45 minutes',
'pending',
200.00,
800.00,
false,
NULL,
'Peinado para evento especial'
ON CONFLICT DO NOTHING;
" 2>&1 | grep -v "NOTICE"
BOOKINGS_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM bookings;")
echo "✅ Bookings: $BOOKINGS_COUNT/5"
# 8. Actualizar booking con secondary_artist
echo ""
echo "🔄 Actualizando booking con secondary_artist..."
psql "$DB_URL" -c "
UPDATE bookings
SET secondary_artist_id = (SELECT id FROM staff WHERE display_name = 'Artist Carla López' LIMIT 1)
WHERE payment_reference = 'pay_test_004';
" 2>&1 | grep -v "NOTICE"
SECONDARY_ARTIST_COUNT=$(psql "$DB_URL" -t -c "SELECT COUNT(*) FROM bookings WHERE secondary_artist_id IS NOT NULL;")
echo "✅ Bookings con secondary_artist: $SECONDARY_ARTIST_COUNT/1"
# Resumen
echo ""
echo "=========================================="
echo "RESUMEN"
echo "=========================================="
echo "Locations: $LOCATIONS_COUNT/3"
echo "Resources: $RESOURCES_COUNT/6"
echo "Staff: $STAFF_COUNT/8"
echo "Services: $SERVICES_COUNT/6"
echo "Customers: $CUSTOMERS_COUNT/4"
echo "Invitations: $INVITATIONS_COUNT/15"
echo "Bookings: $BOOKINGS_COUNT/5"
echo "Sec. Artist: $SECONDARY_ARTIST_COUNT/1"
echo "=========================================="
if [ "$LOCATIONS_COUNT" -eq 3 ] && [ "$RESOURCES_COUNT" -eq 6 ] && [ "$STAFF_COUNT" -eq 8 ] && [ "$SERVICES_COUNT" -eq 6 ] && [ "$CUSTOMERS_COUNT" -eq 4 ] && [ "$INVITATIONS_COUNT" -eq 15 ] && [ "$BOOKINGS_COUNT" -eq 5 ]; then
echo ""
echo "🎉 SEED DE DATOS COMPLETADO EXITOSAMENTE"
echo ""
echo "Próximos pasos:"
echo "1. Configurar Auth en Supabase Dashboard"
echo "2. Crear usuarios de staff y customers"
echo "3. Actualizar tablas staff y customers con user_ids"
else
echo ""
echo "⚠️ ALGUNOS DATOS NO SE CREARON CORRECTAMENTE"
echo "Por favor, verifica los errores arriba."
fi

124
scripts/simple-verify.sh Executable file
View File

@@ -0,0 +1,124 @@
#!/bin/bash
# Script simple para verificar migraciones de SalonOS
# Ejecutar con: ./scripts/simple-verify.sh
# Requiere: psql instalado y variables de entorno en .env.local
# Cargar variables de entorno
set -a
source .env.local
set +a
if [ -z "$NEXT_PUBLIC_SUPABASE_URL" ] || [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
echo "❌ ERROR: Faltan variables de entorno"
echo "Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local"
exit 1
fi
# Configurar DATABASE_URL
DB_HOST="${NEXT_PUBLIC_SUPABASE_URL#https://}"
DB_URL="postgresql://postgres:${SUPABASE_SERVICE_ROLE_KEY}@${DB_HOST}:5432/postgres"
echo "=========================================="
echo "SALONOS - VERIFICACIÓN DE MIGRACIONES"
echo "=========================================="
echo ""
# 1. Verificar Tablas
echo "📊 Verificando tablas..."
TABLE_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('locations', 'resources', 'staff', 'services', 'customers', 'invitations', 'bookings', 'audit_logs');
")
echo "✅ Tablas: ${TABLE_COUNT}/8"
if [ "$TABLE_COUNT" -lt 8 ]; then
echo "⚠️ Faltan tablas por crear"
fi
# 2. Verificar Funciones
echo ""
echo "📊 Verificando funciones..."
FUNC_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM information_schema.routines
WHERE routine_schema = 'public';
")
echo "✅ Funciones: ${FUNC_COUNT}/14"
if [ "$FUNC_COUNT" -lt 14 ]; then
echo "⚠️ Faltan funciones por crear"
fi
# 3. Verificar Triggers
echo ""
echo "📊 Verificando triggers..."
TRIGGER_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM information_schema.triggers
WHERE trigger_schema = 'public';
")
echo "✅ Triggers: ${TRIGGER_COUNT}/17+"
if [ "$TRIGGER_COUNT" -lt 17 ]; then
echo "⚠️ Faltan triggers por crear"
fi
# 4. Verificar Políticas RLS
echo ""
echo "📊 Verificando políticas RLS..."
POLICY_COUNT=$(psql "$DB_URL" -t -c "
SELECT COUNT(*)
FROM pg_policies
WHERE schemaname = 'public';
")
echo "✅ Políticas RLS: ${POLICY_COUNT}/20+"
if [ "$POLICY_COUNT" -lt 20 ]; then
echo "⚠️ Faltan políticas RLS por crear"
fi
# 5. Probar Short ID
echo ""
echo "📊 Probando generación de Short ID..."
SHORT_ID=$(psql "$DB_URL" -t -c "SELECT generate_short_id();")
echo "✅ Short ID: ${SHORT_ID} (${#SHORT_ID} caracteres)"
# 6. Probar Código de Invitación
echo ""
echo "📊 Probando generación de código de invitación..."
INV_CODE=$(psql "$DB_URL" -t -c "SELECT generate_invitation_code();")
echo "✅ Código de invitación: ${INV_CODE} (${#INV_CODE} caracteres)"
# Resumen
echo ""
echo "=========================================="
echo "RESUMEN"
echo "=========================================="
if [ "$TABLE_COUNT" -ge 8 ] && [ "$FUNC_COUNT" -ge 14 ] && [ "$TRIGGER_COUNT" -ge 17 ] && [ "$POLICY_COUNT" -ge 20 ]; then
echo "Tablas: ✅ ${TABLE_COUNT}/8"
echo "Funciones: ✅ ${FUNC_COUNT}/14"
echo "Triggers: ✅ ${TRIGGER_COUNT}/17+"
echo "Políticas RLS: ✅ ${POLICY_COUNT}/20+"
echo "Short ID: ✅ Generable"
echo "Cód. Invit.: ✅ Generable"
echo ""
echo "=========================================="
echo "🎉 TODAS LAS MIGRACIONES ESTÁN CORRECTAS"
echo "Puedes continuar con el seed de datos."
echo "=========================================="
else
echo "Tablas: ❌ ${TABLE_COUNT}/8"
echo "Funciones: ❌ ${FUNC_COUNT}/14"
echo "Triggers: ❌ ${TRIGGER_COUNT}/17+"
echo "Políticas RLS: ❌ ${POLICY_COUNT}/20+"
echo ""
echo "=========================================="
echo "⚠️ ALGUNAS MIGRACIONES FALTAN"
echo "Por favor, verifica los errores arriba."
echo "=========================================="
fi

225
scripts/verify-migration.js Normal file
View File

@@ -0,0 +1,225 @@
#!/usr/bin/env node
/**
* Script de verificación de migraciones - SalonOS
* Verifica que todas las tablas, funciones, triggers y políticas RLS estén creados
*/
const { createClient } = require('@supabase/supabase-js')
// Cargar variables de entorno
require('dotenv').config({ path: '.env.local' })
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !supabaseServiceKey) {
console.error('❌ ERROR: Faltan variables de entorno')
console.error('Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY en .env.local')
process.exit(1)
}
const supabase = createClient(supabaseUrl, supabaseServiceKey)
console.log('==========================================')
console('SALONOS - VERIFICACIÓN DE MIGRACIONES')
console.log('==========================================')
console.log()
const expectedTables = [
'locations',
'resources',
'staff',
'services',
'customers',
'invitations',
'bookings',
'audit_logs',
]
const expectedFunctions = [
'generate_short_id',
'generate_invitation_code',
'reset_weekly_invitations_for_customer',
'reset_all_weekly_invitations',
'log_audit',
'get_current_user_role',
'is_staff_or_higher',
'is_artist',
'is_customer',
'is_admin',
'update_updated_at',
'generate_booking_short_id',
'get_week_start',
]
const expectedEnums = [
'user_role',
'customer_tier',
'booking_status',
'invitation_status',
'resource_type',
'audit_action',
]
async function verifyTables() {
console.log('📊 Verificando tablas...')
const { data: tables, error } = await supabase.rpc('verify_tables_exist', {
table_names: expectedTables,
})
if (error) {
console.error('❌ Error al verificar tablas:', error)
return false
}
console.log(`✅ Tablas creadas: ${tables.length}/${expectedTables.length}`)
if (tables.length !== expectedTables.length) {
console.log('⚠️ Tablas faltantes:')
expectedTables.forEach(table => {
if (!tables.includes(table)) {
console.log(` - ${table}`)
}
})
return false
}
return true
}
async function verifyFunctions() {
console.log('📊 Verificando funciones...')
const { data: functions, error } = await supabase.rpc('verify_functions_exist', {
function_names: expectedFunctions,
})
if (error) {
console.error('❌ Error al verificar funciones:', error)
return false
}
console.log(`✅ Funciones creadas: ${functions.length}/${expectedFunctions.length}`)
if (functions.length !== expectedFunctions.length) {
console.log('⚠️ Funciones faltantes:')
expectedFunctions.forEach(func => {
if (!functions.includes(func)) {
console.log(` - ${func}`)
}
})
return false
}
return true
}
async function verifyEnums() {
console.log('📊 Verificando tipos ENUM...')
const { data: enums, error } = await supabase.rpc('verify_enums_exist', {
enum_names: expectedEnums,
})
if (error) {
console.error('❌ Error al verificar tipos ENUM:', error)
return false
}
console.log(`✅ Tipos ENUM creados: ${enums.length}/${expectedEnums.length}`)
if (enums.length !== expectedEnums.length) {
console.log('⚠️ Tipos ENUM faltantes:')
expectedEnums.forEach(enumName => {
if (!enums.includes(enumName)) {
console.log(` - ${enumName}`)
}
})
return false
}
return true
}
async function testShortID() {
console.log('🧪 Probando generación de Short ID...')
const { data, error } = await supabase.rpc('generate_short_id')
if (error) {
console.error('❌ Error al generar Short ID:', error)
return false
}
console.log(`✅ Short ID generado: ${data}`)
console.log(` Longitud: ${data.length} caracteres`)
if (data.length !== 6) {
console.error('❌ ERROR: El Short ID debe tener 6 caracteres')
return false
}
return true
}
async function testInvitationCode() {
console.log('🧪 Probando generación de código de invitación...')
const { data, error } = await supabase.rpc('generate_invitation_code')
if (error) {
console.error('❌ Error al generar código de invitación:', error)
return false
}
console.log(`✅ Código de invitación generado: ${data}`)
console.log(` Longitud: ${data.length} caracteres`)
if (data.length !== 10) {
console.error('❌ ERROR: El código de invitación debe tener 10 caracteres')
return false
}
return true
}
async function main() {
try {
const tablesOk = await verifyTables()
const functionsOk = await verifyFunctions()
const enumsOk = await verifyEnums()
const shortIdOk = await testShortID()
const invitationCodeOk = await testInvitationCode()
console.log()
console.log('==========================================')
if (tablesOk && functionsOk && enumsOk && shortIdOk && invitationCodeOk) {
console.log('✅ TODAS LAS VERIFICACIONES PASARON')
console.log('==========================================')
console.log()
console.log('🎉 La base de datos está lista para usar')
console.log()
console.log('📝 Próximos pasos:')
console.log(' 1. Configurar Auth en Supabase Dashboard')
console.log(' 2. Crear usuarios de prueba con roles específicos')
console.log(' 3. Ejecutar seeds de datos de prueba')
console.log(' 4. Probar la API de bookings')
process.exit(0)
} else {
console.log('❌ ALGUNAS VERIFICACIONES FALLARON')
console.log('==========================================')
console.log()
console.log('Por favor, revisa los errores arriba y ejecuta nuevamente:')
console.log(' npm run db:migrate')
process.exit(1)
}
} catch (error) {
console.error('❌ Error inesperado:', error)
process.exit(1)
}
}
main()

View File

@@ -0,0 +1,89 @@
-- ============================================
-- VERIFICACIÓN POST-MIGRACIÓN - SALONOS
-- Ejecutar en Supabase SQL Editor después de las migraciones
-- ============================================
-- 1. Verificar Tablas Creadas
SELECT 'TABLAS' as verification_type, table_name as item
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('locations', 'resources', 'staff', 'services', 'customers', 'invitations', 'bookings', 'audit_logs')
ORDER BY table_name;
-- 2. Verificar Funciones Creadas
SELECT 'FUNCIONES' as verification_type, routine_name as item
FROM information_schema.routines
WHERE routine_schema = 'public'
ORDER BY routine_name;
-- 3. Verificar Triggers Activos
SELECT 'TRIGGERS' as verification_type, trigger_name as item
FROM information_schema.triggers
WHERE trigger_schema = 'public'
ORDER BY event_object_table, trigger_name;
-- 4. Verificar Políticas RLS
SELECT 'POLÍTICAS RLS' as verification_type, policyname as item
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename, policyname;
-- 5. Verificar Tipos ENUM
SELECT 'ENUM TYPES' as verification_type, typname as item
FROM pg_type
WHERE typtype = 'e'
AND typname IN ('user_role', 'customer_tier', 'booking_status', 'invitation_status', 'resource_type', 'audit_action')
ORDER BY typname;
-- 6. Probar Short ID Generation
SELECT 'SHORT ID TEST' as verification_type, generate_short_id() as item;
-- 7. Probar Invitation Code Generation
SELECT 'INVITATION CODE TEST' as verification_type, generate_invitation_code() as item;
-- 8. Verificar Trigger de Validación de Secondary Artist
SELECT 'SECONDARY ARTIST TRIGGER' as verification_type, trigger_name as item
FROM information_schema.triggers
WHERE trigger_name = 'validate_booking_secondary_artist';
-- 9. Verificar Función de Reset de Invitaciones
SELECT 'RESET INVITATIONS FUNCTION' as verification_type, routine_name as item
FROM information_schema.routines
WHERE routine_name = 'reset_all_weekly_invitations';
-- 10. Verificar Función de Validación de Secondary Artist
SELECT 'VALIDATE SECONDARY ARTIST' as verification_type, routine_name as item
FROM information_schema.routines
WHERE routine_name = 'validate_secondary_artist_role';
-- 11. Verificar Week Start Function
SELECT 'WEEK START FUNCTION' as verification_type, get_week_start(CURRENT_DATE) as item;
-- 12. Contar elementos por tipo
SELECT
'RESUMEN' as verification_type,
'Tablas: ' || (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('locations', 'resources', 'staff', 'services', 'customers', 'invitations', 'bookings', 'audit_logs')) as item
UNION ALL
SELECT
'RESUMEN' as verification_type,
'Funciones: ' || (SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema = 'public') as item
UNION ALL
SELECT
'RESUMEN' as verification_type,
'Triggers: ' || (SELECT COUNT(*) FROM information_schema.triggers WHERE trigger_schema = 'public') as item
UNION ALL
SELECT
'RESUMEN' as verification_type,
'Políticas RLS: ' || (SELECT COUNT(*) FROM pg_policies WHERE schemaname = 'public') as item
UNION ALL
SELECT
'RESUMEN' as verification_type,
'Tipos ENUM: ' || (SELECT COUNT(*) FROM pg_type WHERE typtype = 'e' AND typname IN ('user_role', 'customer_tier', 'booking_status', 'invitation_status', 'resource_type', 'audit_action')) as item;