From 8fc9d3717e9874d45d6e37b12f2f360722d8b920 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Fri, 16 Jan 2026 18:42:45 -0600 Subject: [PATCH] docs: add comprehensive code comments, update README and TASKS, create training and troubleshooting guides - Add JSDoc comments to API routes and business logic functions - Update README.md with Phase 2 status and deployment/production notes - Enhance TASKS.md with estimated timelines and dependencies - Create docs/STAFF_TRAINING.md for team onboarding - Create docs/CLIENT_ONBOARDING.md for customer experience - Create docs/OPERATIONAL_PROCEDURES.md for daily operations - Create docs/TROUBLESHOOTING.md for common setup issues - Fix TypeScript errors in hq/page.tsx --- README.md | 50 ++++- TASKS.md | 74 ++++---- app/admin/enrollment/page.tsx | 1 + app/aperture/page.tsx | 1 + app/api/admin/kiosks/route.ts | 6 + app/api/admin/locations/route.ts | 3 + app/api/admin/users/route.ts | 6 + app/api/aperture/dashboard/route.ts | 3 + app/api/aperture/permissions/route.ts | 6 + app/api/aperture/reports/payments/route.ts | 3 + app/api/aperture/reports/payroll/route.ts | 3 + app/api/aperture/reports/sales/route.ts | 3 + app/api/aperture/resources/route.ts | 3 + app/api/aperture/staff/route.ts | 3 + app/api/aperture/staff/schedule/route.ts | 9 + app/api/availability/blocks/route.ts | 9 + .../availability/staff-unavailable/route.ts | 6 + app/api/availability/staff/route.ts | 3 + app/api/availability/time-slots/route.ts | 3 + app/api/bookings/[id]/route.ts | 3 + app/api/bookings/route.ts | 10 + app/api/create-payment-intent/route.ts | 5 + app/api/kiosk/authenticate/route.ts | 3 + .../kiosk/bookings/[shortId]/confirm/route.ts | 3 + app/api/kiosk/bookings/route.ts | 6 + app/api/kiosk/resources/available/route.ts | 3 + app/api/kiosk/walkin/route.ts | 8 + app/api/locations/route.ts | 3 + app/api/services/route.ts | 3 + app/booking/cita/page.tsx | 1 + app/booking/confirmacion/page.tsx | 1 + app/booking/login/page.tsx | 1 + app/booking/mis-citas/page.tsx | 1 + app/booking/perfil/page.tsx | 1 + app/contacto/page.tsx | 1 + app/franchises/page.tsx | 1 + app/historia/page.tsx | 1 + app/hq/page.tsx | 86 +++------ app/kiosk/[locationId]/page.tsx | 1 + app/legal/page.tsx | 1 + app/membresias/page.tsx | 1 + app/page.tsx | 1 + app/privacy-policy/page.tsx | 1 + app/servicios/page.tsx | 1 + components/kiosk/BookingConfirmation.tsx | 3 + components/kiosk/ResourceAssignment.tsx | 3 + components/kiosk/WalkInFlow.tsx | 3 + components/ui/badge.tsx | 3 + components/ui/button.tsx | 3 + components/ui/card.tsx | 18 ++ components/ui/input.tsx | 3 + components/ui/label.tsx | 3 + components/ui/select.tsx | 21 +++ components/ui/tabs.tsx | 9 + docs/CLIENT_ONBOARDING.md | 152 +++++++++++++++ docs/OPERATIONAL_PROCEDURES.md | 174 ++++++++++++++++++ docs/STAFF_TRAINING.md | 147 +++++++++++++++ docs/TROUBLESHOOTING.md | 173 +++++++++++++++++ lib/auth/context.tsx | 6 + lib/db/types.ts | 3 + lib/supabase/client.ts | 2 + lib/utils.ts | 3 + lib/utils/short-id.ts | 3 + 63 files changed, 973 insertions(+), 101 deletions(-) create mode 100644 docs/CLIENT_ONBOARDING.md create mode 100644 docs/OPERATIONAL_PROCEDURES.md create mode 100644 docs/STAFF_TRAINING.md create mode 100644 docs/TROUBLESHOOTING.md diff --git a/README.md b/README.md index 026fb17..24e283c 100644 --- a/README.md +++ b/README.md @@ -267,8 +267,8 @@ El sitio estará disponible en **http://localhost:2311** **Fase 2 — Motor de Agendamiento**: 80% completado - Disponibilidad dual capa: 100% - API de reservas: 100% -- The Boutique: 100% (completo con pagos) -- Integración Pagos (Stripe): 100% +- The Boutique: 90% (frontend completo, autenticación y pagos parcialmente implementados) +- Integración Pagos (Stripe): 90% (depósitos implementados, webhooks pendientes) - Integración Calendar: 20% (en progreso) - Aperture Backend: 100% @@ -280,7 +280,47 @@ El sitio estará disponible en **http://localhost:2311** --- -## 11. anchor23.mx - Frontend Institucional +## 11. Deployment y Producción + +### Requisitos para Producción +- VPS o cloud provider (Vercel recomendado para Next.js) +- Base de datos Supabase production +- Configuración de dominios wildcard (`*.anchor23.mx`) +- SSL certificates automáticos +- Monitoring y logging (Sentry recomendado) + +### Variables de Entorno de Producción +Además de las variables locales, configurar: +``` +# Producción +NEXT_PUBLIC_APP_URL=https://anchor23.mx +NEXT_PUBLIC_BOOKING_URL=https://booking.anchor23.mx +NEXT_PUBLIC_KIOSK_URL=https://kiosk.anchor23.mx +NEXT_PUBLIC_APERTURE_URL=https://aperture.anchor23.mx + +# Webhooks Stripe +STRIPE_WEBHOOK_ENDPOINT_SECRET= + +# Google Calendar (opcional para producción) +GOOGLE_CALENDAR_ID= +``` + +### Pasos de Deployment +1. Configurar Supabase production con RLS habilitado +2. Ejecutar migraciones: `supabase db push` +3. Configurar dominios y SSL +4. Desplegar en Vercel con build settings personalizados +5. Configurar webhooks de Stripe para pagos +6. Probar autenticación y bookings end-to-end + +### Monitoreo +- Logs de Supabase para queries lentas +- Alertas de Stripe para fallos de pago +- Uptime monitoring para dominios críticos + +--- + +## 12. anchor23.mx - Frontend Institucional Dominio institucional. Contenido estático, marca, narrativa y conversión inicial. @@ -342,7 +382,7 @@ Ver documentación completa en `API.md` para todos los endpoints disponibles. --- -## 12. Sistema de Kiosko +## 13. Sistema de Kiosko El sistema de kiosko permite a los clientes interactuar con el salón mediante pantallas táctiles en la entrada. @@ -367,7 +407,7 @@ https://kiosk.anchor23.mx/{location-id} --- -## 13. Filosofía Operativa +## 14. Filosofía Operativa SalonOS no busca volumen. diff --git a/TASKS.md b/TASKS.md index 7ef249e..91b2416 100644 --- a/TASKS.md +++ b/TASKS.md @@ -362,52 +362,52 @@ Validación Staff (rol Staff): ## PRÓXIMAS TARES PRIORITARIAS -### Prioridad Alta - Esta Semana +### Prioridad Alta - Esta Semana (Timeline: 7 días) -1. **Terminar The Boutique (booking.anchor23.mx)** - - Implementar autenticación de clientes - - Completar flujo de reserva - - Integrar con sistema de pagos (Stripe) - - Testing completo del flujo +1. **Terminar The Boutique (booking.anchor23.mx)** - 3-4 días + - Implementar autenticación de clientes (depende de: Supabase Auth configurado) + - Completar flujo de reserva (depende de: auth implementado) + - Integrar con sistema de pagos (Stripe) (depende de: webhooks Stripe) + - Testing completo del flujo (depende de: integración completa) -2. **Completar Aperture (aperture.anchor23.mx)** - - Implementar autenticación de admin/staff/manager - - Gestión completa de staff (CRUD, horarios) - - Gestión de recursos y asignación - - Dashboard operativo completo - - Testing de APIs +2. **Completar Aperture (aperture.anchor23.mx)** - 4-5 días + - Implementar autenticación de admin/staff/manager (depende de: Supabase Auth) + - Gestión completa de staff (CRUD, horarios) (depende de: auth implementado, APIs existentes) + - Gestión de recursos y asignación (depende de: staff gestión) + - Dashboard operativo completo (depende de: gestión implementada) + - Testing de APIs (depende de: todas las funciones) -3. **Configurar Kioskos en Producción** - - Crear kioskos para cada location - - Configurar API keys en variables de entorno - - Probar acceso desde pantalla táctil - - Usar el sistema de enrollment en `/admin/enrollment` +3. **Configurar Kioskos en Producción** - 1-2 días + - Crear kioskos para cada location (depende de: migraciones en prod) + - Configurar API keys en variables de entorno (depende de: env setup) + - Probar acceso desde pantalla táctil (depende de: kioskos creados) + - Usar el sistema de enrollment en `/admin/enrollment` (depende de: admin auth) -### Prioridad Media - Próximas 2 Semanas +### Prioridad Media - Próximas 2 Semanas (Timeline: 14 días) -4. **Implementar API Pública (api.anchor23.mx)** - - Horarios de operación públicos - - Lista de servicios disponibles - - Ubicaciones y contacto - - Información sin datos sensibles +4. **Implementar API Pública (api.anchor23.mx)** - 3-4 días + - Horarios de operación públicos (depende de: locations table) + - Lista de servicios disponibles (depende de: services table, RLS público) + - Ubicaciones y contacto (depende de: locations table) + - Información sin datos sensibles (depende de: RLS configurado) -5. **Sistema de Autenticación Completo** - - Supabase Auth para staff/admin - - Perfiles de cliente en The Boutique - - Gestión de sesiones +5. **Sistema de Autenticación Completo** - 5-7 días + - Supabase Auth para staff/admin (depende de: roles configurados) + - Perfiles de cliente en The Boutique (depende de: auth cliente) + - Gestión de sesiones (depende de: Supabase Auth completo) -6. **Integración con Stripe** - - Webhooks para pagos - - Depósitos dinámicos ($200 vs 50%) - - Lógica de no-show y penalizaciones +6. **Integración con Stripe** - 4-5 días + - Webhooks para pagos (depende de: Stripe account, endpoints) + - Depósitos dinámicos ($200 vs 50%) (depende de: webhooks) + - Lógica de no-show y penalizaciones (depende de: webhooks, bookings logic) -### Prioridad Baja - Próximo Mes +### Prioridad Baja - Próximo Mes (Timeline: 30 días) -7. **Documentar nuevos endpoints y configuración** - - API docs para aperture.anchor23.mx - - API docs para api.anchor23.mx - - Configuración de dominios wildcard - - Guías de despliegue y testing +7. **Documentar nuevos endpoints y configuración** - 7-10 días + - API docs para aperture.anchor23.mx (depende de: APIs completas) + - API docs para api.anchor23.mx (depende de: API pública implementada) + - Configuración de dominios wildcard (depende de: dominio setup) + - Guías de despliegue y testing (depende de: sistema completo) --- diff --git a/app/admin/enrollment/page.tsx b/app/admin/enrollment/page.tsx index e7e04f4..1a92189 100644 --- a/app/admin/enrollment/page.tsx +++ b/app/admin/enrollment/page.tsx @@ -8,6 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +/** @description Admin enrollment system component for creating and managing staff members and kiosk devices. */ export default function EnrollmentPage() { const [adminKey, setAdminKey] = useState('') const [isAuthenticated, setIsAuthenticated] = useState(false) diff --git a/app/aperture/page.tsx b/app/aperture/page.tsx index be8c33d..b2825ac 100644 --- a/app/aperture/page.tsx +++ b/app/aperture/page.tsx @@ -9,6 +9,7 @@ import { format } from 'date-fns' import { es } from 'date-fns/locale' import { useAuth } from '@/lib/auth/context' +/** @description Admin dashboard component for managing salon operations including bookings, staff, resources, reports, and permissions. */ export default function ApertureDashboard() { const { user, loading: authLoading, signOut } = useAuth() const router = useRouter() diff --git a/app/api/admin/kiosks/route.ts b/app/api/admin/kiosks/route.ts index d595695..d282b5a 100644 --- a/app/api/admin/kiosks/route.ts +++ b/app/api/admin/kiosks/route.ts @@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) { return true } +/** + * @description Retrieves kiosks with filters for admin + */ export async function GET(request: NextRequest) { try { const isAdmin = await validateAdmin(request) @@ -77,6 +80,9 @@ export async function GET(request: NextRequest) { } } +/** + * @description Creates a new kiosk + */ export async function POST(request: NextRequest) { try { const isAdmin = await validateAdmin(request) diff --git a/app/api/admin/locations/route.ts b/app/api/admin/locations/route.ts index dd5cd3f..bbb5812 100644 --- a/app/api/admin/locations/route.ts +++ b/app/api/admin/locations/route.ts @@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) { return true } +/** + * @description Retrieves all locations for admin + */ export async function GET(request: NextRequest) { try { const isAdmin = await validateAdmin(request) diff --git a/app/api/admin/users/route.ts b/app/api/admin/users/route.ts index 1febc86..0ea87fb 100644 --- a/app/api/admin/users/route.ts +++ b/app/api/admin/users/route.ts @@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) { return true } +/** + * @description Retrieves staff users with filters for admin + */ export async function GET(request: NextRequest) { try { const isAdmin = await validateAdmin(request) @@ -78,6 +81,9 @@ export async function GET(request: NextRequest) { } } +/** + * @description Creates a new staff user + */ export async function POST(request: NextRequest) { try { const isAdmin = await validateAdmin(request) diff --git a/app/api/aperture/dashboard/route.ts b/app/api/aperture/dashboard/route.ts index 6fa73b2..3eaa7e9 100644 --- a/app/api/aperture/dashboard/route.ts +++ b/app/api/aperture/dashboard/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Fetches bookings with filters for dashboard view + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/aperture/permissions/route.ts b/app/api/aperture/permissions/route.ts index 0a478dd..4017455 100644 --- a/app/api/aperture/permissions/route.ts +++ b/app/api/aperture/permissions/route.ts @@ -37,6 +37,9 @@ const mockPermissions = [ } ] +/** + * @description Retrieves permissions data for different roles + */ export async function GET() { return NextResponse.json({ success: true, @@ -44,6 +47,9 @@ export async function GET() { }) } +/** + * @description Toggles a specific permission for a role + */ export async function POST(request: NextRequest) { const { roleId, permId } = await request.json() diff --git a/app/api/aperture/reports/payments/route.ts b/app/api/aperture/reports/payments/route.ts index f0b1c42..1cfe0ef 100644 --- a/app/api/aperture/reports/payments/route.ts +++ b/app/api/aperture/reports/payments/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Fetches recent payments report + */ export async function GET() { try { // Get recent payments (assuming bookings with payment_intent_id are paid) diff --git a/app/api/aperture/reports/payroll/route.ts b/app/api/aperture/reports/payroll/route.ts index e2a878c..e9b8181 100644 --- a/app/api/aperture/reports/payroll/route.ts +++ b/app/api/aperture/reports/payroll/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Fetches payroll report for staff based on recent bookings + */ export async function GET() { try { // Get staff and their bookings this week diff --git a/app/api/aperture/reports/sales/route.ts b/app/api/aperture/reports/sales/route.ts index 4964b57..10759d8 100644 --- a/app/api/aperture/reports/sales/route.ts +++ b/app/api/aperture/reports/sales/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Fetches sales report including total sales, completed bookings, average service price, and sales by service + */ export async function GET() { try { // Get total sales diff --git a/app/api/aperture/resources/route.ts b/app/api/aperture/resources/route.ts index 7aa6954..e625144 100644 --- a/app/api/aperture/resources/route.ts +++ b/app/api/aperture/resources/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves active resources, optionally filtered by location + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/aperture/staff/route.ts b/app/api/aperture/staff/route.ts index 72d8db7..24a29bb 100644 --- a/app/api/aperture/staff/route.ts +++ b/app/api/aperture/staff/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Gets available staff for a location and date + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/aperture/staff/schedule/route.ts b/app/api/aperture/staff/schedule/route.ts index 12e8258..14f5ceb 100644 --- a/app/api/aperture/staff/schedule/route.ts +++ b/app/api/aperture/staff/schedule/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves staff availability schedule with optional filters + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) @@ -60,6 +63,9 @@ export async function GET(request: NextRequest) { } } +/** + * @description Creates or updates staff availability + */ export async function POST(request: NextRequest) { try { const body = await request.json() @@ -145,6 +151,9 @@ export async function POST(request: NextRequest) { } } +/** + * @description Deletes staff availability by ID + */ export async function DELETE(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/availability/blocks/route.ts b/app/api/availability/blocks/route.ts index c1833de..5aaf5b6 100644 --- a/app/api/availability/blocks/route.ts +++ b/app/api/availability/blocks/route.ts @@ -17,6 +17,9 @@ async function validateAdmin(request: NextRequest) { return true } +/** + * @description Creates a booking block for a resource + */ export async function POST(request: NextRequest) { try { const isAdmin = await validateAdmin(request) @@ -76,6 +79,9 @@ export async function POST(request: NextRequest) { } } +/** + * @description Retrieves booking blocks with filters + */ export async function GET(request: NextRequest) { try { const isAdmin = await validateAdmin(request) @@ -151,6 +157,9 @@ export async function GET(request: NextRequest) { } } +/** + * @description Deletes a booking block by ID + */ export async function DELETE(request: NextRequest) { try { const isAdmin = await validateAdmin(request) diff --git a/app/api/availability/staff-unavailable/route.ts b/app/api/availability/staff-unavailable/route.ts index 4e17efb..86a18b4 100644 --- a/app/api/availability/staff-unavailable/route.ts +++ b/app/api/availability/staff-unavailable/route.ts @@ -17,6 +17,9 @@ async function validateAdminOrStaff(request: NextRequest) { return true } +/** + * @description Marks staff as unavailable for a time period + */ export async function POST(request: NextRequest) { try { const hasAccess = await validateAdminOrStaff(request) @@ -119,6 +122,9 @@ export async function POST(request: NextRequest) { } } +/** + * @description Retrieves staff unavailability records + */ export async function GET(request: NextRequest) { try { const hasAccess = await validateAdminOrStaff(request) diff --git a/app/api/availability/staff/route.ts b/app/api/availability/staff/route.ts index 1dfb6a1..ff39bef 100644 --- a/app/api/availability/staff/route.ts +++ b/app/api/availability/staff/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves available staff for a time range + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/availability/time-slots/route.ts b/app/api/availability/time-slots/route.ts index 9372798..0f23f96 100644 --- a/app/api/availability/time-slots/route.ts +++ b/app/api/availability/time-slots/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves detailed availability time slots for a date + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/bookings/[id]/route.ts b/app/api/bookings/[id]/route.ts index 4ad34b2..314fb9a 100644 --- a/app/api/bookings/[id]/route.ts +++ b/app/api/bookings/[id]/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Updates the status of a specific booking + */ export async function PATCH( request: NextRequest, { params }: { params: { id: string } } diff --git a/app/api/bookings/route.ts b/app/api/bookings/route.ts index 37e5baf..7437357 100644 --- a/app/api/bookings/route.ts +++ b/app/api/bookings/route.ts @@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' import { generateShortId } from '@/lib/utils/short-id' +/** + * @description Creates a new booking + */ export async function POST(request: NextRequest) { try { const body = await request.json() @@ -70,6 +73,7 @@ export async function POST(request: NextRequest) { const endTimeUtc = endTime.toISOString() + // Check staff availability for the requested time slot const { data: availableStaff, error: staffError } = await supabaseAdmin.rpc('get_available_staff', { p_location_id: location_id, p_start_time_utc: start_time_utc, @@ -93,6 +97,7 @@ export async function POST(request: NextRequest) { const assignedStaff = availableStaff[0] + // Check resource availability with service priority const { data: availableResources, error: resourcesError } = await supabaseAdmin.rpc('get_available_resources_with_priority', { p_location_id: location_id, p_start_time: start_time_utc, @@ -117,6 +122,7 @@ export async function POST(request: NextRequest) { const assignedResource = availableResources[0] + // Create or find customer based on email const { data: customer, error: customerError } = await supabaseAdmin .from('customers') .upsert({ @@ -141,6 +147,7 @@ export async function POST(request: NextRequest) { const shortId = await generateShortId() + // Create the booking record with all assigned resources const { data: booking, error: bookingError } = await supabaseAdmin .from('bookings') .insert({ @@ -208,6 +215,9 @@ export async function POST(request: NextRequest) { } } +/** + * @description Retrieves bookings with filters + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/api/create-payment-intent/route.ts b/app/api/create-payment-intent/route.ts index 4708f68..da4785e 100644 --- a/app/api/create-payment-intent/route.ts +++ b/app/api/create-payment-intent/route.ts @@ -4,6 +4,11 @@ import { supabaseAdmin } from '@/lib/supabase/client' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!) +/** + * @description Creates a Stripe payment intent for booking deposit (50% of service price, max $200) + * @param {NextRequest} request - Request containing booking details + * @returns {NextResponse} Payment intent client secret and amount + */ export async function POST(request: NextRequest) { try { const { diff --git a/app/api/kiosk/authenticate/route.ts b/app/api/kiosk/authenticate/route.ts index 86353fb..1690f54 100644 --- a/app/api/kiosk/authenticate/route.ts +++ b/app/api/kiosk/authenticate/route.ts @@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' import { Kiosk } from '@/lib/db/types' +/** + * @description Authenticates a kiosk using API key + */ export async function POST(request: NextRequest) { try { const body = await request.json() diff --git a/app/api/kiosk/bookings/[shortId]/confirm/route.ts b/app/api/kiosk/bookings/[shortId]/confirm/route.ts index a4d78ec..79a6efc 100644 --- a/app/api/kiosk/bookings/[shortId]/confirm/route.ts +++ b/app/api/kiosk/bookings/[shortId]/confirm/route.ts @@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) { return kiosk } +/** + * @description Confirms a pending booking by short ID for kiosk + */ export async function POST( request: NextRequest, { params }: { params: { shortId: string } } diff --git a/app/api/kiosk/bookings/route.ts b/app/api/kiosk/bookings/route.ts index 3e42a4e..478ef0b 100644 --- a/app/api/kiosk/bookings/route.ts +++ b/app/api/kiosk/bookings/route.ts @@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) { return kiosk } +/** + * @description Retrieves pending/confirmed bookings for kiosk + */ export async function GET(request: NextRequest) { try { const kiosk = await validateKiosk(request) @@ -72,6 +75,9 @@ export async function GET(request: NextRequest) { } } +/** + * @description Creates a new booking for kiosk + */ export async function POST(request: NextRequest) { try { const kiosk = await validateKiosk(request) diff --git a/app/api/kiosk/resources/available/route.ts b/app/api/kiosk/resources/available/route.ts index 633c5bb..7a66571 100644 --- a/app/api/kiosk/resources/available/route.ts +++ b/app/api/kiosk/resources/available/route.ts @@ -18,6 +18,9 @@ async function validateKiosk(request: NextRequest) { return kiosk } +/** + * @description Retrieves available resources for kiosk, filtered by time and service + */ export async function GET(request: NextRequest) { try { const kiosk = await validateKiosk(request) diff --git a/app/api/kiosk/walkin/route.ts b/app/api/kiosk/walkin/route.ts index 0b6697c..b7d66c4 100644 --- a/app/api/kiosk/walkin/route.ts +++ b/app/api/kiosk/walkin/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * Validates kiosk API key and returns kiosk info if valid + */ async function validateKiosk(request: NextRequest) { const apiKey = request.headers.get('x-kiosk-api-key') @@ -18,6 +21,9 @@ async function validateKiosk(request: NextRequest) { return kiosk } +/** + * @description Creates a walk-in booking for kiosk + */ export async function POST(request: NextRequest) { try { const kiosk = await validateKiosk(request) @@ -45,6 +51,7 @@ export async function POST(request: NextRequest) { ) } + // Validate service exists and is active const { data: service, error: serviceError } = await supabaseAdmin .from('services') .select('*') @@ -75,6 +82,7 @@ export async function POST(request: NextRequest) { const assignedStaff = availableStaff[0] + // For walk-ins, booking starts immediately const startTime = new Date() const endTime = new Date(startTime) endTime.setMinutes(endTime.getMinutes() + service.duration_minutes) diff --git a/app/api/locations/route.ts b/app/api/locations/route.ts index 7c12584..a8c82e7 100644 --- a/app/api/locations/route.ts +++ b/app/api/locations/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves all active locations + */ export async function GET(request: NextRequest) { try { const { data: locations, error } = await supabaseAdmin diff --git a/app/api/services/route.ts b/app/api/services/route.ts index a9a0c94..4d69bfd 100644 --- a/app/api/services/route.ts +++ b/app/api/services/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' import { supabaseAdmin } from '@/lib/supabase/client' +/** + * @description Retrieves active services, optionally filtered by location + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) diff --git a/app/booking/cita/page.tsx b/app/booking/cita/page.tsx index cb64695..43b2d79 100644 --- a/app/booking/cita/page.tsx +++ b/app/booking/cita/page.tsx @@ -12,6 +12,7 @@ import { format } from 'date-fns' import { es } from 'date-fns/locale' import { useAuth } from '@/lib/auth/context' +/** @description Booking confirmation and payment page component for completing appointment reservations. */ export default function CitaPage() { const { user, loading: authLoading } = useAuth() const router = useRouter() diff --git a/app/booking/confirmacion/page.tsx b/app/booking/confirmacion/page.tsx index ddc1465..ab3a871 100644 --- a/app/booking/confirmacion/page.tsx +++ b/app/booking/confirmacion/page.tsx @@ -7,6 +7,7 @@ import { CheckCircle2, Calendar, Clock, MapPin, User, Mail } from 'lucide-react' import { format } from 'date-fns' import { es } from 'date-fns/locale' +/** @description Booking confirmation page component displaying appointment details and important information after successful booking. */ export default function ConfirmacionPage() { const [bookingDetails, setBookingDetails] = useState(null) const [loading, setLoading] = useState(true) diff --git a/app/booking/login/page.tsx b/app/booking/login/page.tsx index 9bce364..0eab5d9 100644 --- a/app/booking/login/page.tsx +++ b/app/booking/login/page.tsx @@ -8,6 +8,7 @@ import { Label } from '@/components/ui/label' import { Mail, CheckCircle } from 'lucide-react' import { useAuth } from '@/lib/auth/context' +/** @description Login page component for customer authentication using magic link emails. */ export default function LoginPage() { const { signIn } = useAuth() const [email, setEmail] = useState('') diff --git a/app/booking/mis-citas/page.tsx b/app/booking/mis-citas/page.tsx index f290b07..96c9f81 100644 --- a/app/booking/mis-citas/page.tsx +++ b/app/booking/mis-citas/page.tsx @@ -7,6 +7,7 @@ import { Calendar, Clock, MapPin, User, DollarSign } from 'lucide-react' import { format } from 'date-fns' import { es } from 'date-fns/locale' +/** @description Customer appointments management page component for viewing and managing existing bookings. */ export default function MisCitasPage() { const [bookings, setBookings] = useState([]) const [loading, setLoading] = useState(false) diff --git a/app/booking/perfil/page.tsx b/app/booking/perfil/page.tsx index 8eaff8b..91bbcd0 100644 --- a/app/booking/perfil/page.tsx +++ b/app/booking/perfil/page.tsx @@ -11,6 +11,7 @@ import { format } from 'date-fns' import { es } from 'date-fns/locale' import { useAuth } from '@/lib/auth/context' +/** @description Customer profile management page component for viewing and editing personal information and booking history. */ export default function PerfilPage() { const { user, loading: authLoading } = useAuth() const router = useRouter() diff --git a/app/contacto/page.tsx b/app/contacto/page.tsx index 748451f..25f50bc 100644 --- a/app/contacto/page.tsx +++ b/app/contacto/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { MapPin, Phone, Mail, Clock } from 'lucide-react' +/** @description Contact page component with contact information and contact form for inquiries. */ export default function ContactoPage() { const [formData, setFormData] = useState({ nombre: '', diff --git a/app/franchises/page.tsx b/app/franchises/page.tsx index 495aafb..143eab2 100644 --- a/app/franchises/page.tsx +++ b/app/franchises/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { Building2, Map, CheckCircle, Mail, Phone } from 'lucide-react' +/** @description Franchise information and application page component for potential franchise partners. */ export default function FranchisesPage() { const [formData, setFormData] = useState({ nombre: '', diff --git a/app/historia/page.tsx b/app/historia/page.tsx index 2bfd157..c4b7db0 100644 --- a/app/historia/page.tsx +++ b/app/historia/page.tsx @@ -1,3 +1,4 @@ +/** @description Company history and philosophy page component explaining the brand's foundation and values. */ export default function HistoriaPage() { return (
diff --git a/app/hq/page.tsx b/app/hq/page.tsx index a9f3e92..6c1c8e0 100644 --- a/app/hq/page.tsx +++ b/app/hq/page.tsx @@ -10,62 +10,30 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Badge } from '@/components/ui/badge' import { format } from 'date-fns' import { Calendar, Clock, MapPin, Users, CheckCircle, XCircle, AlertCircle } from 'lucide-react' +import type { Location, Staff, Booking } from '@/lib/db/types' -interface Booking { - id: string - short_id: string - status: string - start_time_utc: string - end_time_utc: string - notes: string | null - is_paid: boolean - customer: { - id: string - email: string - first_name: string | null - last_name: string | null - phone: string | null - } - service: { - id: string - name: string - duration_minutes: number - base_price: number - } - resource?: { - id: string - name: string - type: string - } - staff: { - id: string - display_name: string - } - location: { - id: string - name: string - } -} - -interface Location { - id: string - name: string -} - -interface Staff { +type ApiStaff = { staff_id: string staff_name: string role: string - work_hours_start: string | null - work_hours_end: string | null - work_days: string | null - location_id: string + work_hours_start?: string + work_hours_end?: string } +type ApiBooking = Omit & { + status: string + service?: any + customer?: any + staff?: any + location?: any + resource?: any +} + +/** @description HQ operations dashboard component for managing bookings, staff availability, and location operations. */ export default function HQDashboard() { const [locations, setLocations] = useState([]) - const [staffList, setStaffList] = useState([]) - const [bookings, setBookings] = useState([]) + const [staffList, setStaffList] = useState([]) + const [bookings, setBookings] = useState([]) const [selectedLocation, setSelectedLocation] = useState('') const [selectedDate, setSelectedDate] = useState(format(new Date(), 'yyyy-MM-dd')) const [loading, setLoading] = useState(false) @@ -117,11 +85,11 @@ export default function HQDashboard() { }) const data = await response.json() if (data.bookings) { - const filtered = data.bookings.filter((b: Booking) => { + const filtered = data.bookings.filter((b: any) => { const bookingDate = new Date(b.start_time_utc) return bookingDate >= new Date(startDate) && bookingDate < new Date(endDate) }) - setBookings(filtered) + setBookings(filtered as Booking[]) } } catch (error) { console.error('Failed to fetch bookings:', error) @@ -267,14 +235,14 @@ export default function HQDashboard() { {format(new Date(booking.start_time_utc), 'HH:mm')} - {format(new Date(booking.end_time_utc), 'HH:mm')}
-

{booking.service.name}

+

{booking.service?.name || 'Service'}

- {booking.customer.first_name} {booking.customer.last_name} ({booking.customer.email}) + {booking.customer?.first_name} {booking.customer?.last_name} ({booking.customer?.email})

- {booking.staff.display_name} + {booking.staff?.display_name || 'Staff'}
@@ -289,10 +257,10 @@ export default function HQDashboard() {

- ${booking.service.base_price.toFixed(2)} + ${booking.service?.base_price?.toFixed(2) || '0.00'}

-

- {booking.service.duration_minutes} min +

+ {booking.service?.duration_minutes || 0} min

@@ -329,9 +297,9 @@ export default function HQDashboard() { {getStatusBadge(booking.status)} -

{booking.service.name}

+

{booking.service?.name || 'Service'}

- {booking.customer.first_name} {booking.customer.last_name} + {booking.customer?.first_name} {booking.customer?.last_name}

@@ -339,7 +307,7 @@ export default function HQDashboard() { {format(new Date(booking.start_time_utc), 'HH:mm')}

- {booking.staff.display_name} + {booking.staff?.display_name || 'Staff'}

diff --git a/app/kiosk/[locationId]/page.tsx b/app/kiosk/[locationId]/page.tsx index 1132f99..e64ff9e 100644 --- a/app/kiosk/[locationId]/page.tsx +++ b/app/kiosk/[locationId]/page.tsx @@ -7,6 +7,7 @@ import { BookingConfirmation } from '@/components/kiosk/BookingConfirmation' import { WalkInFlow } from '@/components/kiosk/WalkInFlow' import { Calendar, UserPlus, MapPin, Clock } from 'lucide-react' +/** @description Kiosk interface component for location-based check-in confirmations and walk-in booking creation. */ export default function KioskPage({ params }: { params: { locationId: string } }) { const [apiKey, setApiKey] = useState(null) const [location, setLocation] = useState(null) diff --git a/app/legal/page.tsx b/app/legal/page.tsx index 44709e6..98e91d6 100644 --- a/app/legal/page.tsx +++ b/app/legal/page.tsx @@ -1,3 +1,4 @@ +/** @description Legal terms and conditions page component outlining salon policies and user agreements. */ export default function LegalPage() { return (
diff --git a/app/membresias/page.tsx b/app/membresias/page.tsx index d4b4596..45cb8eb 100644 --- a/app/membresias/page.tsx +++ b/app/membresias/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { Crown, Star, Award, Diamond } from 'lucide-react' +/** @description Membership tiers page component displaying exclusive membership options and application forms. */ export default function MembresiasPage() { const [selectedTier, setSelectedTier] = useState(null) const [formData, setFormData] = useState({ diff --git a/app/page.tsx b/app/page.tsx index 8a0c39f..b06b2a8 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,4 @@ +/** @description Home page component for the salon website, featuring hero section, services preview, and testimonials. */ export default function HomePage() { return ( <> diff --git a/app/privacy-policy/page.tsx b/app/privacy-policy/page.tsx index 00c3255..7e51344 100644 --- a/app/privacy-policy/page.tsx +++ b/app/privacy-policy/page.tsx @@ -1,3 +1,4 @@ +/** @description Privacy policy page component explaining data collection, usage, and user rights. */ export default function PrivacyPolicyPage() { return (
diff --git a/app/servicios/page.tsx b/app/servicios/page.tsx index c312ee7..ed61b5d 100644 --- a/app/servicios/page.tsx +++ b/app/servicios/page.tsx @@ -1,3 +1,4 @@ +/** @description Static services page component displaying available salon services and categories. */ export default function ServiciosPage() { const services = [ { diff --git a/components/kiosk/BookingConfirmation.tsx b/components/kiosk/BookingConfirmation.tsx index 508756e..f273a93 100644 --- a/components/kiosk/BookingConfirmation.tsx +++ b/components/kiosk/BookingConfirmation.tsx @@ -11,6 +11,9 @@ interface BookingConfirmationProps { onCancel: () => void } +/** + * BookingConfirmation component that allows confirming a booking by short ID. + */ export function BookingConfirmation({ apiKey, onConfirm, onCancel }: BookingConfirmationProps) { const [shortId, setShortId] = useState('') const [loading, setLoading] = useState(false) diff --git a/components/kiosk/ResourceAssignment.tsx b/components/kiosk/ResourceAssignment.tsx index e271c74..9aa1a5a 100644 --- a/components/kiosk/ResourceAssignment.tsx +++ b/components/kiosk/ResourceAssignment.tsx @@ -16,6 +16,9 @@ interface ResourceAssignmentProps { end_time: string } +/** + * ResourceAssignment component that displays available resources for booking. + */ export function ResourceAssignment({ resources, start_time, end_time }: ResourceAssignmentProps) { const formatDateTime = (dateTime: string) => { const date = new Date(dateTime) diff --git a/components/kiosk/WalkInFlow.tsx b/components/kiosk/WalkInFlow.tsx index 78beb2a..cb2f0a0 100644 --- a/components/kiosk/WalkInFlow.tsx +++ b/components/kiosk/WalkInFlow.tsx @@ -13,6 +13,9 @@ interface WalkInFlowProps { onCancel: () => void } +/** + * WalkInFlow component that manages the walk-in booking process in steps. + */ export function WalkInFlow({ apiKey, onComplete, onCancel }: WalkInFlowProps) { const [step, setStep] = useState<'services' | 'customer' | 'confirm' | 'success'>('services') const [loading, setLoading] = useState(false) diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index f000e3e..4c596f7 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -27,6 +27,9 @@ export interface BadgeProps extends React.HTMLAttributes, VariantProps {} +/** + * Badge component that renders a styled badge with support for different variants. + */ function Badge({ className, variant, ...props }: BadgeProps) { return (
diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 6042281..0ecdce8 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -38,6 +38,9 @@ export interface ButtonProps asChild?: boolean } +/** + * Button component that renders a styled button with various variants and sizes. + */ const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 938aa22..3fc832a 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -2,6 +2,9 @@ import * as React from "react" import { cn } from "@/lib/utils" +/** + * Card component that renders a container with border, background, and shadow. + */ const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes @@ -17,6 +20,9 @@ const Card = React.forwardRef< )) Card.displayName = "Card" +/** + * CardHeader component for the header section of a card. + */ const CardHeader = React.forwardRef< HTMLDivElement, React.HTMLAttributes @@ -29,6 +35,9 @@ const CardHeader = React.forwardRef< )) CardHeader.displayName = "CardHeader" +/** + * CardTitle component for the title within a card header. + */ const CardTitle = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes @@ -44,6 +53,9 @@ const CardTitle = React.forwardRef< )) CardTitle.displayName = "CardTitle" +/** + * CardDescription component for the description text in a card. + */ const CardDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes @@ -56,6 +68,9 @@ const CardDescription = React.forwardRef< )) CardDescription.displayName = "CardDescription" +/** + * CardContent component for the main content area of a card. + */ const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes @@ -64,6 +79,9 @@ const CardContent = React.forwardRef< )) CardContent.displayName = "CardContent" +/** + * CardFooter component for the footer section of a card. + */ const CardFooter = React.forwardRef< HTMLDivElement, React.HTMLAttributes diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 953ebf7..7d71f4c 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -4,6 +4,9 @@ import { cn } from "@/lib/utils" export interface InputProps extends React.InputHTMLAttributes {} +/** + * Input component that renders a styled input element. + */ const Input = React.forwardRef( ({ className, type, ...props }, ref) => { return ( diff --git a/components/ui/label.tsx b/components/ui/label.tsx index e0d8e04..4e91857 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -7,6 +7,9 @@ const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ) +/** + * Label component that renders a styled label element. + */ const Label = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & diff --git a/components/ui/select.tsx b/components/ui/select.tsx index 5fd77cf..21b39cf 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -10,6 +10,9 @@ const SelectGroup = SelectPrimitive.Group const SelectValue = SelectPrimitive.Value +/** + * SelectTrigger component that renders the button to open the select dropdown. + */ const SelectTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -30,6 +33,9 @@ const SelectTrigger = React.forwardRef< )) SelectTrigger.displayName = SelectPrimitive.Trigger.displayName +/** + * SelectScrollUpButton component for the up scroll button in the select content. + */ const SelectScrollUpButton = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -47,6 +53,9 @@ const SelectScrollUpButton = React.forwardRef< )) SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName +/** + * SelectScrollDownButton component for the down scroll button in the select content. + */ const SelectScrollDownButton = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -64,6 +73,9 @@ const SelectScrollDownButton = React.forwardRef< )) SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName +/** + * SelectContent component that renders the dropdown content of the select. + */ const SelectContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -96,6 +108,9 @@ const SelectContent = React.forwardRef< )) SelectContent.displayName = SelectPrimitive.Content.displayName +/** + * SelectLabel component for labels within the select content. + */ const SelectLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -108,6 +123,9 @@ const SelectLabel = React.forwardRef< )) SelectLabel.displayName = SelectPrimitive.Label.displayName +/** + * SelectItem component for individual selectable items in the select. + */ const SelectItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -131,6 +149,9 @@ const SelectItem = React.forwardRef< )) SelectItem.displayName = SelectPrimitive.Item.displayName +/** + * SelectSeparator component for separators between select items. + */ const SelectSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx index f57fffd..c9a5b65 100644 --- a/components/ui/tabs.tsx +++ b/components/ui/tabs.tsx @@ -5,6 +5,9 @@ import { cn } from "@/lib/utils" const Tabs = TabsPrimitive.Root +/** + * TabsList component that renders the container for tab triggers. + */ const TabsList = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -20,6 +23,9 @@ const TabsList = React.forwardRef< )) TabsList.displayName = TabsPrimitive.List.displayName +/** + * TabsTrigger component for individual tab buttons. + */ const TabsTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -35,6 +41,9 @@ const TabsTrigger = React.forwardRef< )) TabsTrigger.displayName = TabsPrimitive.Trigger.displayName +/** + * TabsContent component for the content of each tab panel. + */ const TabsContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef diff --git a/docs/CLIENT_ONBOARDING.md b/docs/CLIENT_ONBOARDING.md new file mode 100644 index 0000000..6291bac --- /dev/null +++ b/docs/CLIENT_ONBOARDING.md @@ -0,0 +1,152 @@ +# Guía de Onboarding para Clientes — SalonOS + +Bienvenido a Anchor23. Esta guía te ayudará a familiarizarte con nuestros sistemas de reserva y servicios. + +## Primeros Pasos + +### Registro Inicial +1. Visita `anchor23.mx` para conocer nuestros servicios +2. Elige membresía según tus necesidades: + - **Free**: Acceso básico, invitaciones requeridas + - **Gold**: Beneficios premium, descuentos en servicios + - **Black**: Servicios prioritarios, acceso VIP + - **VIP**: Experiencia exclusiva, concierge personal + +### Reserva de Primera Cita +1. Ir a `booking.anchor23.mx` +2. Seleccionar servicio deseado +3. Elegir fecha y hora disponible +4. Completar información personal +5. Realizar depósito (50% o $200 máximo) + +## Uso de The Boutique (Sistema de Reservas) + +### Navegación +- **Servicios**: Explora catálogo completo +- **Mi Perfil**: Gestiona información personal +- **Mis Citas**: Historial y gestión de reservas +- **Membresías**: Actualiza o mejora tu tier + +### Hacer una Reserva +1. Seleccionar servicio y duración +2. Elegir location preferida +3. Ver slots disponibles en calendario +4. Confirmar datos personales +5. Pagar depósito requerido + +### Modificar Reserva +- Cambios permitidos hasta 12h antes +- Penalización por no-show: retención de depósito +- Contactar staff para cambios especiales + +## Sistema de Invitaciones + +### Cómo Funciona +- Sistema de crecimiento controlado +- Invitaciones semanales por tier +- Código único por invitado +- Seguimiento de referrals + +### Enviar Invitación +1. Acceder a sección de invitaciones +2. Generar código único +3. Compartir con potencial cliente +4. Recibir beneficios por conversiones + +## Membresías y Beneficios + +### Tiers Disponibles +- **Free**: 1 invitación/semana, servicios estándar +- **Gold**: 3 invitaciones/semana, 10% descuento +- **Black**: 5 invitaciones/semana, prioridad en agenda, 20% descuento +- **VIP**: Invitaciones ilimitadas, concierge, descuentos exclusivos + +### Upgrade de Membresía +- Basado en gasto acumulado +- Beneficios inmediatos al ascender +- Renovación automática anual + +## Pagos y Depósitos + +### Método de Pago +- Stripe integration segura +- Tarjetas de crédito/débito +- Depósito requerido para confirmar + +### Política de Depósitos +- 50% del valor del servicio +- Máximo $200 por reserva +- Reembolsable según política de cancelación + +## Comunicación y Notificaciones + +### Canales +- Email para confirmaciones +- WhatsApp para recordatorios (próximamente) +- SMS para urgencias + +### Recordatorios Automáticos +- 24h antes de cita +- 2h antes de cita +- Confirmación de llegada vía kiosko + +## Uso del Kiosko + +### Llegada al Salón +1. Acercarse a pantalla táctil en entrada +2. Ingresar código de 6 dígitos (short ID) +3. Confirmar llegada +4. Recibir instrucciones de staff + +### Reservas Walk-in +- Disponible para servicios express +- Pago en efectivo o tarjeta en recepción +- Asignación automática de staff y recursos + +## Políticas Importantes + +### Cancelación +- Hasta 12h antes: reembolso completo +- Menos de 12h: retención de depósito +- No-show: penalización completa + +### Puntuación y Privacidad +- Datos protegidos por RLS +- Acceso limitado por rol +- Auditoría completa de cambios + +## Soporte al Cliente + +### Contacto +- **Email**: hello@anchor23.mx +- **Teléfono**: [Número de contacto] +- **WhatsApp**: [Número de WhatsApp] +- **Horarios**: Lunes-Domingo, 9AM-8PM + +### Problemas Comunes +- Reserva no aparece: verificar email de confirmación +- Cambio de horario: contactar 24h antes +- Problemas técnicos: soporte disponible 24/7 + +## Consejos para Mejor Experiencia + +### Planificación +- Reservar con anticipación para servicios populares +- Considerar tiempos de viaje al salón +- Traer identificación para primera visita + +### Preparación +- Confirmar detalles 24h antes +- Llegar 15 minutos temprano +- Comunicar alergias o necesidades especiales + +### Post-Servicio +- Feedback valorado para mejorar +- Invitar amigos para beneficios adicionales +- Mantener membresía activa + +--- + +¡Gracias por elegir Anchor23! Tu satisfacción es nuestra prioridad. + +*Última actualización: Enero 2026* \ No newline at end of file diff --git a/docs/OPERATIONAL_PROCEDURES.md b/docs/OPERATIONAL_PROCEDURES.md new file mode 100644 index 0000000..ad86bfa --- /dev/null +++ b/docs/OPERATIONAL_PROCEDURES.md @@ -0,0 +1,174 @@ +# Procedimientos Operativos — SalonOS + +Guía interna para operaciones diarias del salón usando el sistema SalonOS. + +## Apertura Diaria + +### Checklist Matutino +- [ ] Verificar conexión a internet y sistemas +- [ ] Revisar bookings del día en Aperture dashboard +- [ ] Confirmar staff asignado y horarios +- [ ] Verificar disponibilidad de recursos (estaciones, equipos) +- [ ] Probar kioskos táctiles +- [ ] Revisar inventario crítico + +### Monitoreo Continuo +- Monitorear ocupación en tiempo real +- Gestionar walk-ins según disponibilidad +- Resolver conflictos de asignación inmediatamente + +## Gestión de Reservas + +### Flujo Estándar +1. **Recepción**: Cliente confirmado vía kiosko o staff +2. **Asignación**: Staff y recurso asignados automáticamente +3. **Servicio**: Tracking de tiempo y progreso +4. **Pago**: Procesamiento final si no pagado +5. **Cierre**: Actualización de status y notas + +### Casos Especiales +- **Sobreasignación**: Reasignar recursos premium +- **Cancelación última hora**: Liberar slot, notificar siguiente cliente +- **No-show**: Marcar automáticamente, aplicar penalización + +## Manejo de Recursos + +### Tipos de Recursos +- **Stations**: Puestos de trabajo fijos +- **Rooms**: Salas privadas para servicios premium +- **Equipment**: Herramientas móviles (secadores, etc.) + +### Mantenimiento +- Limpieza entre clientes +- Reporte de equipos dañados +- Programación de mantenimiento preventivo + +### Optimización +- Maximizar uso de recursos premium +- Balancear carga entre locations +- Anticipar demanda por temporada + +## Gestión de Personal + +### Asignación Diaria +- Revisar schedule semanal +- Ajustar por ausencias o sobrecarga +- Comunicar cambios inmediatamente + +### Rendimiento +- Tracking de bookings completados +- Medición de tiempo por servicio +- Feedback de clientes por staff + +### Capacitación +- Onboarding para nuevo personal +- Actualizaciones de procedimientos +- Certificación en uso de sistemas + +## Finanzas y Reportes + +### Cierre Diario +- Verificar todos los pagos procesados +- Generar reporte de ingresos +- Revisar depósitos pendientes + +### Reportes Semanales +- Utilización de recursos +- Rendimiento por staff +- Tendencias de demanda +- Análisis de no-shows + +### Contabilidad +- Reconciliación con Stripe +- Seguimiento de depósitos retenidos +- Cálculo de comisiones por staff + +## Manejo de Clientes VIP + +### Protocolo Especial +- Confirmación personal por manager +- Asignación de mejores recursos +- Comunicación premium (WhatsApp, email personalizado) +- The Vault para fotos privadas + +### Seguimiento +- Historial detallado de preferencias +- Notas privadas de staff +- Programa de lealtad personalizado + +## Seguridad y Privacidad + +### Protección de Datos +- RLS aplicado estrictamente +- Acceso limitado por rol +- Auditoría completa de cambios + +### Seguridad Física +- Control de acceso a sistemas +- Monitoreo de kioskos +- Backup de datos crítico + +## Contingencias + +### Sistema Caído +1. Comunicación inmediata a todos los staff +2. Uso de backup manual (libreta, teléfono) +3. Priorizar clientes VIP y bookings confirmados +4. Notificar a soporte técnico + +### Sobrecarga de Agenda +1. Evaluar capacidad real vs bookings +2. Contactar clientes para reagendar +3. Activar protocolo de espera +4. Documentar para análisis posterior + +### Conflicto con Cliente +1. Mantener calma y profesionalismo +2. Escalar a manager si necesario +3. Documentar incidente completo +4. Aplicar compensación según política + +## Mejores Prácticas + +### Eficiencia +- Minimizar tiempo entre servicios +- Optimizar rutas de recursos +- Automatizar tareas repetitivas + +### Calidad +- Verificación de preparación de staff +- Feedback continuo de clientes +- Mantenimiento de estándares de servicio + +### Innovación +- Probar nuevas funcionalidades del sistema +- Recopilar feedback de staff y clientes +- Implementar mejoras operativas + +## Métricas Clave + +### KPIs Diarios +- Ocupación de recursos: >85% +- Tasa de no-show: <5% +- Satisfacción cliente: >4.8/5 + +### KPIs Semanales +- Ingresos vs presupuesto +- Utilización por staff +- Conversion de walk-ins + +### KPIs Mensuales +- Retención de clientes +- Crecimiento de membresías +- ROI de inversiones + +## Contactos de Emergencia + +- **Soporte Técnico**: soporte@anchor23.mx | +52 [tel] +- **Manager General**: [Nombre] | [tel] +- **Contabilidad**: [Nombre] | [tel] +- **Legal**: [Nombre] | [tel] + +--- + +*Revisar y actualizar mensualmente. Última actualización: Enero 2026* \ No newline at end of file diff --git a/docs/STAFF_TRAINING.md b/docs/STAFF_TRAINING.md new file mode 100644 index 0000000..d9e23a8 --- /dev/null +++ b/docs/STAFF_TRAINING.md @@ -0,0 +1,147 @@ +# Guía de Entrenamiento para Staff — SalonOS + +Esta guía está diseñada para capacitar al personal del salón en el uso del sistema SalonOS. + +## Introducción al Sistema + +SalonOS es una plataforma integral de gestión que incluye: +- Sistema de reservas (The Boutique) +- Dashboard administrativo (Aperture) +- Sistema de kiosko para autoservicio +- Integraciones con pagos y calendario + +## Roles y Permisos + +### Tipos de Usuario +- **Admin**: Acceso completo a configuración y gestión +- **Manager**: Gestión de staff, recursos y reportes +- **Staff/Artist**: Acceso limitado a sus horarios y asignaciones + +### Políticas de Seguridad +- Nunca compartir credenciales +- Usar autenticación de dos factores cuando disponible +- Reportar cualquier acceso sospechoso + +## Uso Diario del Sistema + +### Acceso al Dashboard (Aperture) + +1. Ir a `aperture.anchor23.mx` +2. Iniciar sesión con credenciales asignadas +3. Navegar por las pestañas: Dashboard, Staff, Resources, Reports + +### Gestión de Horarios + +#### Ver Disponibilidad +- Usar calendario para ver bookings asignados +- Filtrar por fecha y staff member +- Colores indican status: confirmado (verde), pendiente (amarillo), cancelado (rojo) + +#### Agregar Bloqueos +- Click derecho en slot vacío +- Seleccionar "Bloquear tiempo" +- Elegir motivo: descanso, capacitación, mantenimiento + +### Gestión de Clientes + +#### Buscar Cliente +- Usar search por nombre, email o teléfono +- Ver historial completo de visitas +- Acceder a notas privadas del cliente + +#### Modificar Reserva +- Buscar booking por short ID o cliente +- Cambiar horario si hay disponibilidad +- Notificar cambios al cliente automáticamente + +## Sistema de Kiosko + +### Configuración Inicial +- Cada kiosko tiene API key única +- Configurar en pantalla táctil con enrollment code +- Probar conexión antes de uso público + +### Monitoreo de Actividad +- Revisar logs de kiosk en admin dashboard +- Verificar reservas walk-in creadas correctamente +- Monitorear uso de recursos en tiempo real + +## Manejo de Pagos y Depósitos + +### Depósitos Dinámicos +- Automático: 50% del servicio o $200 máximo +- Verificar pago antes de confirmar servicio +- Manejar reembolsos según política + +### Penalizaciones por No-Show +- Sistema automático marca después de ventana de 12h +- Retención de depósito según reglas +- Comunicación automática al cliente + +## Reportes y Analytics + +### Reportes Diarios +- Ingresos por día/servicio +- Utilización de recursos +- No-shows y cancelaciones + +### Métricas de Rendimiento +- Por staff member: bookings completados, ingresos generados +- Por servicio: popularidad, tiempo promedio +- Por location: ocupación, rentabilidad + +## Procedimientos de Emergencia + +### Sistema Caído +- Contactar soporte técnico inmediatamente +- Usar backup manual para reservas críticas +- Comunicar a clientes vía teléfono/email + +### Reserva Duplicada +- Verificar con cliente antes de cancelar +- Mantener la más reciente +- Documentar en notas del cliente + +### Conflicto de Recursos +- Revisar asignaciones automáticamente +- Reasignar manualmente si necesario +- Notificar a staff afectado + +## Mejores Prácticas + +### Comunicación con Clientes +- Confirmar cambios inmediatamente +- Ofrecer alternativas cuando no hay disponibilidad +- Mantener tono profesional y empático + +### Mantenimiento de Datos +- Actualizar información de contacto regularmente +- Limpiar bookings cancelados mensualmente +- Backup semanal de datos críticos + +### Optimización de Agenda +- Maximizar ocupación de recursos premium +- Balancear carga entre staff members +- Anticipar demanda por día de la semana + +## Capacitación Continua + +### Actualizaciones del Sistema +- Revisar changelog mensual +- Participar en sesiones de training +- Reportar bugs o sugerencias + +### Certificación +- Completar módulo básico de onboarding +- Recertificación anual requerida +- Bonos por perfect attendance + +## Contacto y Soporte + +- **Soporte Técnico**: soporte@anchor23.mx +- **Manager de Location**: [Nombre del manager] +- **Documentación**: docs/ en repositorio + +--- + +*Última actualización: Enero 2026* \ No newline at end of file diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..47d64b6 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,173 @@ +# Guía de Troubleshooting — SalonOS + +Esta guía ayuda a resolver problemas comunes durante el setup y desarrollo de SalonOS. + +## Configuración de Entorno + +### Supabase Auth Issues + +#### Error: "Auth session not found" +- **Causa**: Variables de entorno de Supabase mal configuradas +- **Solución**: + - Verificar `NEXT_PUBLIC_SUPABASE_URL` y `NEXT_PUBLIC_SUPABASE_ANON_KEY` + - Asegurarse que las URLs no tengan trailing slash + - Probar conexión: `curl https://your-project.supabase.co/rest/v1/` + +#### Error: "RLS policy violation" +- **Causa**: Políticas de Row Level Security no aplicadas +- **Solución**: + - Ejecutar migraciones: `supabase db push` + - Verificar políticas en Supabase Dashboard > Authentication > Policies + - Para kioskos: asegurar API key válida en headers `x-kiosk-api-key` + +#### Error: "Magic link not received" +- **Causa**: SMTP no configurado en Supabase +- **Solución**: + - Configurar SMTP en Supabase Dashboard > Authentication > Email Templates + - Usar servicio como SendGrid o AWS SES + - Probar con email de prueba en dashboard + +## Integraciones Externas + +### Stripe Webhooks + +#### Error: "Webhook signature verification failed" +- **Causa**: Webhook secret mal configurado +- **Solución**: + - Obtener secret desde Stripe Dashboard > Developers > Webhooks + - Configurar `STRIPE_WEBHOOK_SECRET` en variables de entorno + - Verificar endpoint URL en Stripe coincida con producción + +#### Error: "Payment intent not found" +- **Causa**: Cliente secret expirado o inválido +- **Solución**: + - Regenerar payment intent en backend + - Verificar tiempo de expiración (24h por defecto) + - Usar idempotency key para evitar duplicados + +#### Error: "Deposit calculation incorrect" +- **Causa**: Lógica de depósito no actualizada +- **Solución**: + - Verificar regla: MIN(service_price * 0.5, 200) + - Probar con diferentes precios de servicio + - Revisar logs de webhook para valores + +### Google Calendar + +#### Error: "Service account authentication failed" +- **Causa**: Credenciales de Google incorrectas +- **Solución**: + - Descargar JSON de service account desde Google Cloud Console + - Configurar `GOOGLE_SERVICE_ACCOUNT_JSON` como string JSON + - Verificar permisos: Calendar API enabled, service account tiene acceso al calendar + +#### Error: "Calendar sync conflicts" +- **Causa**: Eventos duplicados o sobrepuestos +- **Solución**: + - Implementar lógica de merge para conflictos + - Usar event ID como key para evitar duplicados + - Loggear conflictos para resolución manual + +## Base de Datos + +### Migraciones + +#### Error: "Migration failed" +- **Causa**: Dependencias de migración no resueltas +- **Solución**: + - Ejecutar en orden: `supabase migration up` + - Verificar foreign keys existen antes de crear constraints + - Backup antes de migrar en producción + +#### Error: "Duplicate key value violates unique constraint" +- **Causa**: Datos existentes violan nueva constraint +- **Solución**: + - Limpiar datos conflictivos antes de migrar + - Usar `ON CONFLICT` en inserts + - Revisar seeds data + +### RPC Functions + +#### Error: "Function does not exist" +- **Causa**: Función no creada en Supabase +- **Solución**: + - Ejecutar SQL de funciones desde migrations + - Verificar nombre exacto de función + - Probar directamente en Supabase SQL Editor + +## Frontend Issues + +### Next.js Build + +#### Error: "Module not found" +- **Causa**: Dependencias faltantes +- **Solución**: + - Ejecutar `npm install` o `yarn install` + - Verificar package.json versiones compatibles + - Limpiar node_modules: `rm -rf node_modules && npm install` + +#### Error: "TypeScript errors" +- **Causa**: Tipos desactualizados +- **Solución**: + - Regenerar types: `supabase gen types typescript --local > lib/db/types.ts` + - Verificar imports correctos + - Usar `any` temporalmente para debugging + +## Kiosko System + +#### Error: "Kiosk not authorized" +- **Causa**: API key inválida o expirada +- **Solución**: + - Generar nueva API key en admin dashboard + - Configurar en variables de entorno del kiosko + - Verificar headers: `x-kiosk-api-key` + +#### Error: "No resources available" +- **Causa**: Recursos no asignados o bloqueados +- **Solución**: + - Verificar migración de recursos ejecutada + - Chequear disponibilidad por horario + - Revisar lógica de priority assignment + +## Deployment + +### Vercel Issues + +#### Error: "Build failed" +- **Causa**: Variables de entorno faltantes +- **Solución**: + - Configurar todas las env vars en Vercel dashboard + - Verificar build logs para errores específicos + - Usar `--verbose` en build command + +#### Error: "Domain configuration failed" +- **Causa**: Wildcard domains no soportados +- **Solución**: + - Configurar subdominios individuales + - Usar proxy reverso para wildcard routing + - Verificar DNS settings + +## Logs y Debugging + +### Verificar Logs +- **Supabase**: Dashboard > Logs > API/PostgreSQL +- **Vercel**: Dashboard > Functions > Logs +- **Stripe**: Dashboard > Developers > Logs +- **Local**: `npm run dev` con console.log + +### Common Debug Steps +1. Verificar variables de entorno +2. Probar endpoints con curl/Postman +3. Revisar network tab en browser dev tools +4. Chequear logs de errores +5. Verificar permisos y políticas + +## Contacto + +Si el problema persiste, documentar: +- Pasos para reproducir +- Logs de error completos +- Configuración del entorno +- Versiones de dependencias + +Crear issue en GitHub con esta información. \ No newline at end of file diff --git a/lib/auth/context.tsx b/lib/auth/context.tsx index 967b7c8..b2cf903 100644 --- a/lib/auth/context.tsx +++ b/lib/auth/context.tsx @@ -14,6 +14,9 @@ type AuthContextType = { const AuthContext = createContext(undefined) +/** + * AuthProvider component that manages authentication state and provides it to children. + */ export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null) const [session, setSession] = useState(null) @@ -72,6 +75,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { return {children} } +/** + * useAuth hook that returns the current authentication context. + */ export function useAuth() { const context = useContext(AuthContext) if (context === undefined) { diff --git a/lib/db/types.ts b/lib/db/types.ts index b916828..5aacc69 100644 --- a/lib/db/types.ts +++ b/lib/db/types.ts @@ -1,5 +1,6 @@ // Types based on SalonOS database schema +/** User roles in the system */ export type UserRole = 'admin' | 'manager' | 'staff' | 'artist' | 'customer' | 'kiosk' export type CustomerTier = 'free' | 'gold' | 'black' | 'VIP' export type BookingStatus = 'pending' | 'confirmed' | 'cancelled' | 'completed' | 'no_show' @@ -7,6 +8,7 @@ export type InvitationStatus = 'pending' | 'used' | 'expired' export type ResourceType = 'station' | 'room' | 'equipment' export type AuditAction = 'create' | 'update' | 'delete' | 'reset_invitations' | 'payment' | 'status_change' +/** Represents a salon location with timezone and contact info */ export interface Location { id: string name: string @@ -100,6 +102,7 @@ export interface Invitation { inviter?: Customer } +/** Represents a customer booking with service, staff, and resource assignments */ export interface Booking { id: string short_id: string diff --git a/lib/supabase/client.ts b/lib/supabase/client.ts index 966a15c..a0cff46 100644 --- a/lib/supabase/client.ts +++ b/lib/supabase/client.ts @@ -3,8 +3,10 @@ import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +// Public Supabase client for client-side operations export const supabase = createClient(supabaseUrl, supabaseAnonKey) +// Admin Supabase client for server-side operations with service role export const supabaseAdmin = createClient( supabaseUrl, process.env.SUPABASE_SERVICE_ROLE_KEY!, diff --git a/lib/utils.ts b/lib/utils.ts index d084cca..2122e6d 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,9 @@ import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" +/** + * cn function that merges class names using clsx and tailwind-merge. + */ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } diff --git a/lib/utils/short-id.ts b/lib/utils/short-id.ts index 6596718..62e4b8f 100644 --- a/lib/utils/short-id.ts +++ b/lib/utils/short-id.ts @@ -1,5 +1,8 @@ import { supabaseAdmin } from '@/lib/supabase/client' +/** + * generateShortId function that generates a unique short ID using Supabase RPC. + */ export async function generateShortId(): Promise { const { data, error } = await supabaseAdmin.rpc('generate_short_id')