mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 19:24:32 +00:00
Compare commits
4 Commits
66e20d25a7
...
2c19c49f14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c19c49f14 | ||
|
|
1ca7a2cfbc | ||
|
|
d1735878ef | ||
|
|
bedf1c028a |
@@ -34,8 +34,9 @@ deploy.sh
|
|||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
*.md
|
*.md
|
||||||
API_TESTING_GUIDE.md
|
# Keep deployment guides in production image
|
||||||
DEPLOYMENT_README.md
|
!DEPLOYMENT_README.md
|
||||||
|
!API_TESTING_GUIDE.md
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
coverage
|
coverage
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|||||||
# App
|
# App
|
||||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Formbricks (Surveys - Optional)
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=your-environment-id
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_API_HOST=https://app.formbricks.com
|
||||||
|
|
||||||
# Optional: Redis para caching
|
# Optional: Redis para caching
|
||||||
REDIS_URL=redis://redis:6379
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,74 @@
|
|||||||
- `POST /api/cron/reset-invitations` - Reset diario
|
- `POST /api/cron/reset-invitations` - Reset diario
|
||||||
- Buscar: Invitaciones expiradas reseteadas
|
- Buscar: Invitaciones expiradas reseteadas
|
||||||
|
|
||||||
|
### **📧 Webhooks (Formularios Públicos)**
|
||||||
|
- `POST https://flows.soul23.cloud/webhook-test/4YZ7RPfo1GT` - Webhook test
|
||||||
|
- Body: Payload completo con form type
|
||||||
|
- Buscar: 200 OK + acknowledgment
|
||||||
|
- `POST https://flows.soul23.cloud/webhook/4YZ7RPfo1GT` - Webhook prod
|
||||||
|
- Body: Payload completo con form type
|
||||||
|
- Buscar: 200 OK + acknowledgment
|
||||||
|
|
||||||
|
**Form Types disponibles:**
|
||||||
|
- `contact` - Formulario de contacto
|
||||||
|
- `franchise` - Solicitud de franquicia
|
||||||
|
- `membership` - Solicitud de membresía
|
||||||
|
|
||||||
|
**Payload Base:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "contact|franchise|membership",
|
||||||
|
"timestamp_utc": "2026-01-18T04:26:30.187Z",
|
||||||
|
"device_type": "mobile|desktop|unknown"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Contact Payload:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "contact",
|
||||||
|
"nombre": "Nombre Completo",
|
||||||
|
"email": "email@example.com",
|
||||||
|
"telefono": "+52 844 123 4567",
|
||||||
|
"motivo": "cita|membresia|franquicia|servicios|pago|resena|otro",
|
||||||
|
"mensaje": "Texto del mensaje",
|
||||||
|
"timestamp_utc": "2026-01-18T04:26:30.187Z",
|
||||||
|
"device_type": "mobile"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Franchise Payload:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "franchise",
|
||||||
|
"nombre": "Nombre Completo",
|
||||||
|
"email": "email@example.com",
|
||||||
|
"telefono": "+52 844 123 4567",
|
||||||
|
"ciudad": "Monterrey",
|
||||||
|
"estado": "Nuevo León",
|
||||||
|
"socios": 2,
|
||||||
|
"experiencia_sector": "1-3-anos",
|
||||||
|
"experiencia_belleza": true,
|
||||||
|
"mensaje": "Mensaje adicional",
|
||||||
|
"timestamp_utc": "2026-01-18T04:26:30.187Z",
|
||||||
|
"device_type": "desktop"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Membership Payload:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "membership",
|
||||||
|
"membership_id": "vip",
|
||||||
|
"nombre": "Nombre Completo",
|
||||||
|
"email": "email@example.com",
|
||||||
|
"telefono": "+52 844 123 4567",
|
||||||
|
"mensaje": "Pregunta específica",
|
||||||
|
"timestamp_utc": "2026-01-18T04:26:30.187Z",
|
||||||
|
"device_type": "mobile"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 🔍 **Qué Buscar en Cada Respuesta**
|
## 🔍 **Qué Buscar en Cada Respuesta**
|
||||||
|
|
||||||
### **✅ Éxito**
|
### **✅ Éxito**
|
||||||
|
|||||||
@@ -154,7 +154,30 @@ public/images/gallery/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Logo SVG Original (@src/logo.svg)
|
## 8. Nuevos Componentes (@src/components/)
|
||||||
|
|
||||||
|
**Ubicación sugerida:** `components/`
|
||||||
|
|
||||||
|
**Componentes agregados:**
|
||||||
|
- `animated-logo.tsx` - Logo SVG animado con fade-in
|
||||||
|
- `rolling-phrases.tsx` - Frases rotativas para hero sections
|
||||||
|
- `formbricks-provider.tsx` - Provider para encuestas Formbricks
|
||||||
|
- `webhook-form.tsx` - Formulario unificado para webhooks
|
||||||
|
- `app-wrapper.tsx` - Wrapper de aplicación con contexto
|
||||||
|
- `loading-screen.tsx` - Pantalla de carga con animación
|
||||||
|
- `pattern-overlay.tsx` - Overlay de patrones decorativos
|
||||||
|
- `responsive-nav.tsx` - Navegación responsiva con menú móvil
|
||||||
|
|
||||||
|
**Iconos adicionales:**
|
||||||
|
- Diamond (check, success states)
|
||||||
|
- Crown (VIP tier)
|
||||||
|
|
||||||
|
**Colores actualizados:**
|
||||||
|
- `--charcoal-brown`: #3f362e (marrón oscuro elegante)
|
||||||
|
- `--deep-earth`: #6f5e4f (marrón medio)
|
||||||
|
- `--mocha-taupe`: #b8a89a (beige cálido)
|
||||||
|
|
||||||
|
## 9. Logo SVG Original (@src/logo.svg)
|
||||||
|
|
||||||
**Ruta:** `src/logo.svg`
|
**Ruta:** `src/logo.svg`
|
||||||
|
|
||||||
@@ -332,6 +355,30 @@ public/images/gallery/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 📋 21. Formbricks Integration
|
||||||
|
|
||||||
|
**Ubicación:** `components/formbricks-provider.tsx`
|
||||||
|
|
||||||
|
**Configuración:**
|
||||||
|
- Environment ID para surveys
|
||||||
|
- API Host URL
|
||||||
|
- Device detection (mobile/desktop)
|
||||||
|
- Route change tracking
|
||||||
|
|
||||||
|
**Variables de entorno:**
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=your-id
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_API_HOST=https://app.formbricks.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Uso previsto:**
|
||||||
|
- Encuestas post-experiencia
|
||||||
|
- Feedback de clientes
|
||||||
|
- NPS (Net Promoter Score)
|
||||||
|
- Estudios de satisfacción
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📋 Checklist de Implementación
|
## 📋 Checklist de Implementación
|
||||||
|
|
||||||
| Tarea | Estado | Prioridad |
|
| Tarea | Estado | Prioridad |
|
||||||
@@ -340,6 +387,17 @@ public/images/gallery/
|
|||||||
| Optimizar imágenes A23_VIA_* | pending | alta |
|
| Optimizar imágenes A23_VIA_* | pending | alta |
|
||||||
| Implementar logo SVG en Hero sin animación | completed | alta |
|
| Implementar logo SVG en Hero sin animación | completed | alta |
|
||||||
| Implementar logo SVG en Loading sin fade-in| completed | alta |
|
| Implementar logo SVG en Loading sin fade-in| completed | alta |
|
||||||
|
| Crear componente animated-logo.tsx | completed | alta |
|
||||||
|
| Crear componente rolling-phrases.tsx | completed | alta |
|
||||||
|
| Crear componente webhook-form.tsx | completed | alta |
|
||||||
|
| Crear componente formbricks-provider.tsx | completed | media |
|
||||||
|
| Crear componente responsive-nav.tsx | completed | alta |
|
||||||
|
| Actualizar colores a #3E352E | completed | alta |
|
||||||
|
| Agregar campo motivo en contacto | completed | alta |
|
||||||
|
| Agregar campos estado/ciudad/socios en franchise | pending | alta |
|
||||||
|
| Agregar check experiencia belleza en franchise | pending | alta |
|
||||||
|
| Actualizar info franchise a $100k | completed | alta |
|
||||||
|
| Agregar link Contacto en nav/footer | completed | alta |
|
||||||
| Agregar imágenes Hero/Fundamento | pending | media |
|
| Agregar imágenes Hero/Fundamento | pending | media |
|
||||||
| Agregar imágenes Historia | pending | media |
|
| Agregar imágenes Historia | pending | media |
|
||||||
| Agregar testimonios | pending | media |
|
| Agregar testimonios | pending | media |
|
||||||
@@ -360,6 +418,12 @@ public/images/gallery/
|
|||||||
- **Background Loading:** #3F362E (Marrón oscuro elegante)
|
- **Background Loading:** #3F362E (Marrón oscuro elegante)
|
||||||
- **Gradient (alternativo):** #6f5e4f → #8B4513 → #5a4a3a
|
- **Gradient (alternativo):** #6f5e4f → #8B4513 → #5a4a3a
|
||||||
|
|
||||||
|
### Colores de Botones
|
||||||
|
- **Botón primario:** #3E352E (Marrón elegante) - reemplaza --deep-earth
|
||||||
|
- **Botón secundario:** Gradiente --bone-white → --soft-cream
|
||||||
|
- **Tarjetas featured:** #3E352E (Marrón elegante)
|
||||||
|
- **Hover effects:** #3E352E/90 (90% opacidad)
|
||||||
|
|
||||||
### Fondos de Secciones
|
### Fondos de Secciones
|
||||||
- **Hero:** #F5F5DC (Bone White)
|
- **Hero:** #F5F5DC (Bone White)
|
||||||
- **Services:** #F5F5DC
|
- **Services:** #F5F5DC
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxxxx
|
|||||||
SUPABASE_SERVICE_ROLE_KEY=eyJxxxxx
|
SUPABASE_SERVICE_ROLE_KEY=eyJxxxxx
|
||||||
RESEND_API_KEY=re_xxxxx
|
RESEND_API_KEY=re_xxxxx
|
||||||
NEXT_PUBLIC_APP_URL=https://tu-dominio.com
|
NEXT_PUBLIC_APP_URL=https://tu-dominio.com
|
||||||
|
|
||||||
|
# Formbricks (opcional - encuestas)
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=your-environment-id
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_API_HOST=https://app.formbricks.com
|
||||||
|
|
||||||
|
# Optional: Redis para caching
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
|
# Optional: Analytics
|
||||||
|
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. **SSL Certificates**
|
### 3. **SSL Certificates**
|
||||||
@@ -165,6 +175,83 @@ docker-compose -f docker-compose.prod.yml restart
|
|||||||
- Query optimization
|
- Query optimization
|
||||||
- Redis caching (opcional)
|
- Redis caching (opcional)
|
||||||
|
|
||||||
|
## 📝 **Formbricks Integration**
|
||||||
|
|
||||||
|
### **Configuración de Encuestas**
|
||||||
|
```bash
|
||||||
|
# Activar Formbricks para recolección de feedback
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clxxxxxxxx
|
||||||
|
NEXT_PUBLIC_FORMBRICKS_API_HOST=https://app.formbricks.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Webhooks**
|
||||||
|
```bash
|
||||||
|
# Endpoints de webhook para formularios
|
||||||
|
# Test: https://flows.soul23.cloud/webhook-test/4YZ7RPfo1GT
|
||||||
|
# Prod: https://flows.soul23.cloud/webhook/4YZ7RPfo1GT
|
||||||
|
|
||||||
|
# Formularios que envían a webhooks:
|
||||||
|
# - contact (Contacto)
|
||||||
|
# - franchise (Franquicias)
|
||||||
|
# - membership (Membresías)
|
||||||
|
|
||||||
|
# Payload structure:
|
||||||
|
{
|
||||||
|
"form": "contact|franchise|membership",
|
||||||
|
"timestamp_utc": "ISO-8601",
|
||||||
|
"device_type": "mobile|desktop|unknown",
|
||||||
|
"...": "campos específicos del formulario"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Form Types y Campos**
|
||||||
|
|
||||||
|
**Contact (contacto)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "contact",
|
||||||
|
"nombre": "string",
|
||||||
|
"email": "string",
|
||||||
|
"telefono": "string",
|
||||||
|
"motivo": "cita|membresia|franquicia|servicios|pago|resena|otro",
|
||||||
|
"mensaje": "string",
|
||||||
|
"timestamp_utc": "string",
|
||||||
|
"device_type": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Franchise (franquicias)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "franchise",
|
||||||
|
"nombre": "string",
|
||||||
|
"email": "string",
|
||||||
|
"telefono": "string",
|
||||||
|
"ciudad": "string",
|
||||||
|
"estado": "string",
|
||||||
|
"socios": "number",
|
||||||
|
"experiencia_sector": "string",
|
||||||
|
"experiencia_belleza": "boolean",
|
||||||
|
"mensaje": "string",
|
||||||
|
"timestamp_utc": "string",
|
||||||
|
"device_type": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Membership (membresías)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"form": "membership",
|
||||||
|
"membership_id": "gold|black|vip",
|
||||||
|
"nombre": "string",
|
||||||
|
"email": "string",
|
||||||
|
"telefono": "string",
|
||||||
|
"mensaje": "string",
|
||||||
|
"timestamp_utc": "string",
|
||||||
|
"device_type": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 🔒 **Seguridad**
|
## 🔒 **Seguridad**
|
||||||
|
|
||||||
- SSL/TLS 1.2+
|
- SSL/TLS 1.2+
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Dockerfile optimizado para Next.js production
|
# Dockerfile optimizado para Next.js production
|
||||||
FROM node:18-alpine AS base
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
# Instalar dependencias solo para producción
|
# Instalar dependencias para build
|
||||||
FROM base AS deps
|
FROM base AS deps
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copiar archivos de dependencias
|
# Copiar archivos de dependencias
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --only=production --ignore-scripts && npm cache clean --force
|
RUN npm ci --ignore-scripts && npm cache clean --force
|
||||||
|
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
@@ -19,6 +19,11 @@ COPY . .
|
|||||||
# Variables de entorno para build
|
# Variables de entorno para build
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
|
ENV NEXT_PUBLIC_SUPABASE_URL=https://placeholder.supabase.co
|
||||||
|
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=placeholder-anon-key
|
||||||
|
ENV SUPABASE_SERVICE_ROLE_KEY=placeholder-service-role-key
|
||||||
|
ENV STRIPE_SECRET_KEY=sk_test_placeholder_key
|
||||||
|
ENV RESEND_API_KEY=re_placeholder_key
|
||||||
|
|
||||||
# Build optimizado
|
# Build optimizado
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { createClient } from '@supabase/supabase-js';
|
|||||||
* @returns Statistics for dashboard display
|
* @returns Statistics for dashboard display
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://your-project.supabase.co'
|
||||||
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'your-service-role-key-here'
|
||||||
|
|
||||||
if (!supabaseUrl || !supabaseServiceKey) {
|
if (!supabaseUrl || !supabaseServiceKey) {
|
||||||
throw new Error('Missing Supabase environment variables');
|
throw new Error('Missing Supabase environment variables');
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { supabaseAdmin } from '@/lib/supabase/admin'
|
import { supabase } from '@/lib/supabase/client'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Retrieves all active locations
|
* @description Retrieves all active locations
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { data: locations, error } = await supabaseAdmin
|
const { data: locations, error } = await supabase
|
||||||
.from('locations')
|
.from('locations')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('is_active', true)
|
.eq('is_active', true)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { supabaseAdmin } from '@/lib/supabase/admin'
|
import { supabase } from '@/lib/supabase/client'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Retrieves active services, optionally filtered by location
|
* @description Retrieves active services, optionally filtered by location
|
||||||
@@ -9,7 +9,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const locationId = searchParams.get('location_id')
|
const locationId = searchParams.get('location_id')
|
||||||
|
|
||||||
let query = supabaseAdmin
|
let query = supabase
|
||||||
.from('services')
|
.from('services')
|
||||||
.select('id, name, description, duration_minutes, base_price, requires_dual_artist, premium_fee_enabled, category, is_active, created_at, updated_at')
|
.select('id, name, description, duration_minutes, base_price, requires_dual_artist, premium_fee_enabled, category, is_active, created_at, updated_at')
|
||||||
.eq('is_active', true)
|
.eq('is_active', true)
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { AnimatedLogo } from '@/components/animated-logo'
|
|
||||||
import { RollingPhrases } from '@/components/rolling-phrases'
|
|
||||||
|
|
||||||
/** @description Services page with home page style structure */
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
import { AnimatedLogo } from '@/components/animated-logo'
|
import { AnimatedLogo } from '@/components/animated-logo'
|
||||||
import { RollingPhrases } from '@/components/rolling-phrases'
|
import { RollingPhrases } from '@/components/rolling-phrases'
|
||||||
|
|
||||||
/** @description Services page with home page style structure */
|
/** @description Services page with home page style structure */
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
|
|
||||||
interface Service {
|
interface Service {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -247,7 +247,6 @@ export default function PayrollManagement() {
|
|||||||
<SelectValue placeholder="Todos los empleados" />
|
<SelectValue placeholder="Todos los empleados" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Todos los empleados</SelectItem>
|
|
||||||
{/* This would need to be populated with actual staff data */}
|
{/* This would need to be populated with actual staff data */}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -310,7 +310,6 @@ export default function POSSystem() {
|
|||||||
<SelectValue placeholder="Seleccionar cliente (opcional)" />
|
<SelectValue placeholder="Seleccionar cliente (opcional)" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Sin cliente especificado</SelectItem>
|
|
||||||
{customers.slice(0, 10).map(customer => (
|
{customers.slice(0, 10).map(customer => (
|
||||||
<SelectItem key={customer.id} value={customer.id}>
|
<SelectItem key={customer.id} value={customer.id}>
|
||||||
{customer.first_name} {customer.last_name}
|
{customer.first_name} {customer.last_name}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ services:
|
|||||||
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
|
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
|
||||||
- RESEND_API_KEY=${RESEND_API_KEY}
|
- RESEND_API_KEY=${RESEND_API_KEY}
|
||||||
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
|
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
|
||||||
|
- NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=${NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
|
||||||
|
- NEXT_PUBLIC_FORMBRICKS_API_HOST=${NEXT_PUBLIC_FORMBRICKS_API_HOST}
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
networks:
|
networks:
|
||||||
@@ -23,7 +25,6 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
# Recursos optimizados
|
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -48,10 +49,6 @@ services:
|
|||||||
- anchoros
|
- anchoros
|
||||||
networks:
|
networks:
|
||||||
- anchoros_network
|
- anchoros_network
|
||||||
# SSL termination y caching
|
|
||||||
environment:
|
|
||||||
- NGINX_ENVSUBST_TEMPLATE_DIR=/etc/nginx/templates
|
|
||||||
- NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx/conf.d
|
|
||||||
|
|
||||||
# Opcional: Redis para caching adicional
|
# Opcional: Redis para caching adicional
|
||||||
redis:
|
redis:
|
||||||
@@ -70,4 +67,4 @@ volumes:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
anchoros_network:
|
anchoros_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createClient } from '@supabase/supabase-js'
|
import { createClient } from '@supabase/supabase-js'
|
||||||
|
|
||||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://your-project.supabase.co'
|
||||||
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
|
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'your-service-role-key-here'
|
||||||
|
|
||||||
// Admin Supabase client for server-side operations with service role
|
// Admin Supabase client for server-side operations with service role
|
||||||
export const supabaseAdmin = createClient(
|
export const supabaseAdmin = createClient(
|
||||||
|
|||||||
42
scripts/load-admin-users.sh
Executable file
42
scripts/load-admin-users.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script para cargar usuarios admin en Supabase
|
||||||
|
# Uso: ./load-admin-users.sh
|
||||||
|
|
||||||
|
echo "Cargando usuarios admin en Supabase..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verificar que las variables de entorno están definidas
|
||||||
|
if [ -z "$NEXT_PUBLIC_SUPABASE_URL" ] || [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
|
||||||
|
echo "ERROR: Variables de entorno no definidas."
|
||||||
|
echo "Asegúrate de tener NEXT_PUBLIC_SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY definidas."
|
||||||
|
echo ""
|
||||||
|
echo "Ejemplo de uso:"
|
||||||
|
echo " export NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co"
|
||||||
|
echo " export SUPABASE_SERVICE_ROLE_KEY=your-service-role-key"
|
||||||
|
echo " ./load-admin-users.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ejecutar el script SQL
|
||||||
|
echo "Ejecutando scripts/seed-admin-users.sql..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
psql "$SUPABASE_URL?options=project%3Ddefault" <<EOF
|
||||||
|
$(cat scripts/seed-admin-users.sql)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "✓ Usuarios admin cargados exitosamente:"
|
||||||
|
echo " - frida.lara@example.com (Frida Lara) - Admin"
|
||||||
|
echo " - america.cruz@example.com (América de la Cruz) - Admin"
|
||||||
|
echo " - alejandra.ponce@example.com (Alejandra Ponce) - Admin"
|
||||||
|
echo ""
|
||||||
|
echo "Contraseña predeterminada: admin123"
|
||||||
|
echo "IMPORTANTE: Cambiar contraseñas en primer inicio de sesión."
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "✗ Error al cargar usuarios admin"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
111
scripts/seed-admin-users.sql
Normal file
111
scripts/seed-admin-users.sql
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
-- Agregar usuarios admin específicos
|
||||||
|
-- Script para insertar administradores iniciales
|
||||||
|
|
||||||
|
-- Primero, verificar que las tablas existen
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'users'
|
||||||
|
) THEN
|
||||||
|
RAISE EXCEPTION 'Tabla users no existe';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Insertar Frida Lara como admin
|
||||||
|
INSERT INTO users (email, password_hash, role, created_at, updated_at, email_verified)
|
||||||
|
VALUES (
|
||||||
|
'frida.lara@example.com',
|
||||||
|
crypt('admin123', gen_salt('bf')),
|
||||||
|
'admin',
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (email) DO NOTHING;
|
||||||
|
|
||||||
|
-- Insertar América de la Cruz como admin
|
||||||
|
INSERT INTO users (email, password_hash, role, created_at, updated_at, email_verified)
|
||||||
|
VALUES (
|
||||||
|
'america.cruz@example.com',
|
||||||
|
crypt('admin123', gen_salt('bf')),
|
||||||
|
'admin',
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (email) DO NOTHING;
|
||||||
|
|
||||||
|
-- Insertar Alejandra Ponce como admin
|
||||||
|
INSERT INTO users (email, password_hash, role, created_at, updated_at, email_verified)
|
||||||
|
VALUES (
|
||||||
|
'alejandra.ponce@example.com',
|
||||||
|
crypt('admin123', gen_salt('bf')),
|
||||||
|
'admin',
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (email) DO NOTHING;
|
||||||
|
|
||||||
|
-- Crear perfiles de staff para estos usuarios
|
||||||
|
INSERT INTO staff (user_id, display_name, first_name, last_name, role, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
u.id,
|
||||||
|
u.email,
|
||||||
|
CASE
|
||||||
|
WHEN u.email = 'frida.lara@example.com' THEN 'Frida'
|
||||||
|
WHEN u.email = 'america.cruz@example.com' THEN 'América'
|
||||||
|
WHEN u.email = 'alejandra.ponce@example.com' THEN 'Alejandra'
|
||||||
|
END,
|
||||||
|
CASE
|
||||||
|
WHEN u.email = 'frida.lara@example.com' THEN 'Lara'
|
||||||
|
WHEN u.email = 'america.cruz@example.com' THEN 'de la Cruz'
|
||||||
|
WHEN u.email = 'alejandra.ponce@example.com' THEN 'Ponce'
|
||||||
|
END,
|
||||||
|
'admin',
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
FROM users u
|
||||||
|
WHERE u.email IN (
|
||||||
|
'frida.lara@example.com',
|
||||||
|
'america.cruz@example.com',
|
||||||
|
'alejandra.ponce@example.com'
|
||||||
|
)
|
||||||
|
AND u.role = 'admin'
|
||||||
|
ON CONFLICT (user_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- Asignar ubicación principal (asumimos location_id = 1)
|
||||||
|
INSERT INTO staff_locations (staff_id, location_id, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
1,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
FROM staff s
|
||||||
|
JOIN users u ON s.user_id = u.id
|
||||||
|
WHERE u.email IN (
|
||||||
|
'frida.lara@example.com',
|
||||||
|
'america.cruz@example.com',
|
||||||
|
'alejandra.ponce@example.com'
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM staff_locations sl
|
||||||
|
WHERE sl.staff_id = s.id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Confirmación
|
||||||
|
SELECT
|
||||||
|
u.email,
|
||||||
|
u.role,
|
||||||
|
s.display_name,
|
||||||
|
sl.location_id
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN staff s ON s.user_id = u.id
|
||||||
|
LEFT JOIN staff_locations sl ON sl.staff_id = s.id
|
||||||
|
WHERE u.email IN (
|
||||||
|
'frida.lara@example.com',
|
||||||
|
'america.cruz@example.com',
|
||||||
|
'alejandra.ponce@example.com'
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user