feat: implement customer registration flow and business hours system

Major changes:
- Add customer registration with email/phone lookup (app/booking/registro)
- Add customers API endpoint (app/api/customers/route)
- Implement business hours for locations (mon-fri 10-7, sat 10-6, sun closed)
- Fix availability function type casting issues
- Add business hours utilities (lib/utils/business-hours.ts)
- Update Location type to include business_hours JSONB
- Add mock payment component for testing
- Remove Supabase auth from booking flow
- Fix /cita redirect path in booking flow

Database migrations:
- Add category column to services table
- Add business_hours JSONB column to locations table
- Fix availability functions with proper type casting
- Update get_detailed_availability to use business_hours

Features:
- Customer lookup by email or phone
- Auto-redirect to registration if customer not found
- Pre-fill customer data if exists
- Business hours per day of week
- Location-specific opening/closing times
This commit is contained in:
Marco Gallegos
2026-01-17 00:48:49 -06:00
parent 583a25a6f6
commit e3c4f30648
7 changed files with 455 additions and 88 deletions

180
docs/API.md Normal file
View File

@@ -0,0 +1,180 @@
# AnchorOS API Documentation
## Overview
AnchorOS is a comprehensive salon management system built with Next.js, Supabase, and Stripe integration.
## Authentication
- **Client Authentication**: Magic link via Supabase Auth
- **Staff/Admin Authentication**: Supabase Auth with role-based access
- **Kiosk Authentication**: API key based
## API Endpoints
### Public APIs
#### Services
- `GET /api/services` - List all available services
- `POST /api/services` - Create new service (Admin only)
#### Locations
- `GET /api/locations` - List all salon locations
#### Availability
- `GET /api/availability/time-slots` - Get available time slots for booking
- `POST /api/availability/staff-unavailable` - Mark staff unavailable (Staff auth required)
#### Customers
- `GET /api/customers` - Search customer by email or phone
- `POST /api/customers` - Register new customer
#### Bookings (Public)
- `POST /api/bookings` - Create new booking (supports customer_id or customer info)
- `GET /api/bookings/[id]` - Get booking details
- `PUT /api/bookings/[id]` - Update booking
### Staff/Admin APIs (Aperture)
#### Dashboard
- `GET /api/aperture/dashboard` - Dashboard data
- `GET /api/aperture/stats` - Statistics
#### Staff Management
- `GET /api/aperture/staff` - List staff members
- `POST /api/aperture/staff` - Create/Update staff
#### Resources
- `GET /api/aperture/resources` - List resources
- `POST /api/aperture/resources` - Manage resources
#### Reports
- `GET /api/aperture/reports/sales` - Sales reports
- `GET /api/aperture/reports/payments` - Payment reports
- `GET /api/aperture/reports/payroll` - Payroll reports
#### Permissions
- `GET /api/aperture/permissions` - Get role permissions
- `POST /api/aperture/permissions` - Update permissions
### Kiosk APIs
- `POST /api/kiosk/authenticate` - Authenticate kiosk
- `GET /api/kiosk/resources/available` - Get available resources for kiosk
- `POST /api/kiosk/bookings` - Create walk-in booking
- `PUT /api/kiosk/bookings/[shortId]/confirm` - Confirm booking
### Payment APIs
- `POST /api/create-payment-intent` - Create Stripe payment intent
### Admin APIs
- `GET /api/admin/locations` - List locations (Admin key required)
- `POST /api/admin/users` - Create staff/user
- `POST /api/admin/kiosks` - Create kiosk
## Data Models
### User Roles
- `customer` - End customers
- `staff` - Salon staff
- `artist` - Service providers
- `manager` - Location managers
- `admin` - System administrators
- `kiosk` - Kiosk devices
### Key Tables
- `locations` - Salon locations with business hours (JSONB)
- `staff` - Staff members
- `services` - Available services with category
- `resources` - Physical resources (stations)
- `customers` - Customer profiles
- `bookings` - Service bookings
- `kiosks` - Kiosk devices
- `audit_logs` - System audit trail
### Business Hours Structure
Locations table includes `business_hours` JSONB column with format:
```json
{
"monday": {"open": "10:00", "close": "19:00", "is_closed": false},
"tuesday": {"open": "10:00", "close": "19:00", "is_closed": false},
"wednesday": {"open": "10:00", "close": "19:00", "is_closed": false},
"thursday": {"open": "10:00", "close": "19:00", "is_closed": false},
"friday": {"open": "10:00", "close": "19:00", "is_closed": false},
"saturday": {"open": "10:00", "close": "18:00", "is_closed": false},
"sunday": {"is_closed": true}
}
```
Default business hours (updated via migration):
- Monday-Friday: 10:00 AM - 7:00 PM
- Saturday: 10:00 AM - 6:00 PM
- Sunday: Closed
## Environment Variables
### Required
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
- `SUPABASE_SERVICE_ROLE_KEY`
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`
- `STRIPE_SECRET_KEY`
### Optional
- `ADMIN_ENROLLMENT_KEY` - For staff enrollment
- `GOOGLE_SERVICE_ACCOUNT_KEY` - For Calendar sync
## Deployment
### Prerequisites
- Node.js 18+
- Supabase account
- Stripe account
- Google Cloud (for Calendar)
### Setup Steps
1. Clone repository
2. Install dependencies: `npm install`
3. Configure environment variables
4. Run database migrations: `npm run db:migrate`
5. Seed data: `npm run db:seed`
6. Build: `npm run build`
7. Start: `npm start`
## Features
### Core Functionality
- Multi-location salon management
- Real-time availability system with business hours
- Customer registration and lookup by email/phone
- Location-specific opening/closing times
- Automated payment processing (currently mock)
- Staff scheduling and payroll
- Customer relationship management
- Kiosk system for walk-ins
### Booking Flow
1. Customer selects service and location
2. Customer chooses date and time slot
3. Customer searches by email or phone:
- If found: Pre-fill data and proceed
- If not found: Redirect to registration
4. Customer completes registration if needed
5. Customer confirms personal details
6. Customer pays deposit (mock currently)
7. Booking confirmed with email confirmation
### Advanced Features
- Role-based access control
- Audit logging
- Automated no-show handling
- Commission-based payroll
- Sales analytics and reporting
- Permission management
### Security
- Row Level Security (RLS) in Supabase
- API key authentication for kiosks
- Magic link authentication for customers
- Encrypted payment processing
## Support
For API issues or feature requests, please check the TASKS.md for current priorities or create an issue in the repository.

121
docs/PRD.md Normal file
View File

@@ -0,0 +1,121 @@
# PRD — AnchorOS
**Codename: Adela**
## 1. Objetivo
AnchorOS es un sistema operativo para salones de belleza orientado a agenda, pagos, membresías e invitados, con reglas estrictas de tiempo, seguridad y automatización.
---
## 2. Principios del Sistema
* UTC-first en todo el backend.
* UUID como identificador primario interno.
* Short ID solo para referencia humana.
* Automatismos auditables.
* PRD como única fuente de verdad.
---
## 3. Roles y Membresías
### 3.1 Tiers
* Free
* Gold
### 3.2 Tier Gold — Beneficios
* Acceso prioritario a agenda.
* Beneficios financieros definidos en pricing.
* Invitaciones semanales.
### 3.3 Ecosistema de Exclusividad (Invitaciones)
* Cada cuenta Tier Gold tiene **5 invitaciones semanales**.
* Las invitaciones **se resetean cada semana** (Lunes 00:00 UTC).
* El reseteo es automático mediante:
* Supabase Edge Function **o**
* Cron Job externo.
* El proceso debe ser:
* Idempotente.
* Auditado en `audit_logs`.
### 3.4 Jerarquía de Roles
* **Admin**: Acceso total. Puede ver PII de clientes y hacer ajustes.
* **Manager**: Acceso operacional. Puede ver PII de clientes y hacer ajustes.
* **Staff**: Nivel de coordinación. Puede ver PII de clientes y hacer ajustes.
* **Artist**: Nivel de ejecución. **Solo puede ver nombre y notas** del cliente. No ve email ni phone.
* **Customer**: Nivel más bajo. Solo puede ver sus propios datos.
---
## 4. Gestión de Tiempo y Zonas Horarias
* **Todos los timestamps se almacenan en UTC**.
* `locations.timezone` define la zona local del salón.
* Conversión a hora local:
* Solo en frontend.
* Solo en notificaciones (WhatsApp / Email).
* Backend, reglas de negocio y validaciones **operan exclusivamente en UTC**.
---
## 5. Agenda y Bookings
### 5.1 Identificadores
* Cada booking tiene:
* `id` (UUID, primario).
* `short_id` (6 caracteres alfanuméricos).
### 5.2 Short ID — Reglas
* Se genera antes de persistir el booking.
* Debe verificarse unicidad.
* Si existe colisión:
* Reintentar generación hasta ser único.
* El Short ID:
* Es referencia de pago.
* Es identificador operativo.
* **No sustituye** el UUID.
---
## 6. Pagos
* Stripe como proveedor principal.
* El Short ID se utiliza como referencia visible.
* UUID se mantiene interno.
---
## 7. Auditoría
* Toda acción automática o crítica debe registrarse en `audit_logs`.
* Incluye:
* Reseteo de invitaciones.
* Cambios de estado de bookings.
* Eventos de pago.
---
## 8. Límites de los Agentes de IA
* Ningún agente puede modificar reglas aquí descritas.
* Toda implementación debe alinearse estrictamente a este PRD.
---
## 9. Estado del Documento
Este PRD es la fuente única de verdad funcional del sistema AnchorOS.

201
docs/STRIPE_SETUP.md Normal file
View File

@@ -0,0 +1,201 @@
# Stripe Payment Integration
## Current Status
Stripe is **ENABLED in mock mode** for testing. Real Stripe integration is partially implemented but not yet activated.
## Current Implementation
### Mock Payment System
- **Component**: `components/booking/mock-payment-form.tsx`
- **Usage**: Used in `/booking/cita` for customer bookings
- **Functionality**:
- Validates card number format (Luhn algorithm)
- Accepts any card with correct format
- Simulates payment processing
- Does not charge real payments
### Environment Variables
Currently set in `.env.local` (keys removed for security - use your own Stripe keys):
```bash
NEXT_PUBLIC_STRIPE_ENABLED=false
STRIPE_SECRET_KEY=sk_live_your_stripe_secret_key_here
STRIPE_PUBLISHABLE_KEY=pk_live_your_stripe_publishable_key_here
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
```
**Note**: `NEXT_PUBLIC_STRIPE_ENABLED=false` means we use mock payments. Stripe keys are stored but not used yet.
## To Enable Real Stripe Payments
### 1. Update Environment Variables
In `.env.local`:
```bash
NEXT_PUBLIC_STRIPE_ENABLED=true
STRIPE_SECRET_KEY=sk_test_your_real_stripe_secret_key
STRIPE_PUBLISHABLE_KEY=pk_test_your_real_stripe_publishable_key
STRIPE_WEBHOOK_SECRET=whsec_your_real_webhook_secret
```
### 2. Create Stripe Webhook Endpoint
Create `app/api/stripe/webhook/route.ts`:
```typescript
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
import { supabaseAdmin } from '@/lib/supabase/admin'
const stripe = new Stripe(process.env.STRIPE_WEBHOOK_SECRET!)
export async function POST(request: NextRequest) {
const body = await request.text()
const sig = request.headers.get('stripe-signature')!
let event
try {
event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent
// Update booking status to confirmed
await supabaseAdmin
.from('bookings')
.update({ status: 'confirmed', is_paid: true })
.eq('payment_reference', paymentIntent.id)
break
case 'payment_intent.payment_failed':
const failedIntent = event.data.object as Stripe.PaymentIntent
// Update booking status to pending/notify customer
await supabaseAdmin
.from('bookings')
.update({ status: 'pending' })
.eq('payment_reference', failedIntent.id)
break
case 'charge.refunded':
// Handle refunds - mark as cancelled or retain deposit
break
default:
console.log(`Unhandled event type: ${event.type}`)
}
return NextResponse.json({ received: true })
}
```
### 3. Replace Mock Payment with Real Stripe
In `app/booking/cita/page.tsx`:
Replace the `MockPaymentForm` component usage with real Stripe integration:
```tsx
import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js'
// Replace the mock payment section with:
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: 'var(--charcoal-brown)',
'::placeholder': {
color: 'var(--mocha-taupe)',
},
},
},
}}
/>
```
### 4. Update Payment Handling
Replace the `handleMockPayment` function with real Stripe confirmation:
```tsx
const handlePayment = async () => {
if (!stripe || !elements) return
const { error, paymentIntent } = await stripe.confirmCardPayment(
paymentIntent.clientSecret,
{
payment_method: {
card: elements.getElement(CardElement)!,
}
}
)
if (error) {
// Handle error
setErrors({ submit: error.message })
setPageLoading(false)
} else {
// Payment succeeded, create booking
createBooking(paymentIntent.id)
}
}
```
### 5. Configure Stripe Dashboard
1. Go to Stripe Dashboard > Webhooks
2. Add endpoint: `https://booking.anchor23.mx/api/stripe/webhook`
3. Select events:
- `payment_intent.succeeded`
- `payment_intent.payment_failed`
- `charge.refunded`
4. Copy the webhook signing secret to `STRIPE_WEBHOOK_SECRET`
### 6. Update Create Payment Intent API
Ensure `/api/create-payment-intent` uses your real Stripe secret key.
## Deposit Calculation Logic
According to PRD, deposit is calculated as:
- **Weekday (Mon-Fri)**: 50% of service price, max $200
- **Weekend (Sat-Sun)**: $200 flat rate
Current implementation: `Math.min(service.base_price * 0.5, 200)`
Weekend logic needs to be added.
## Testing
### Mock Payment Testing (Current)
- Use any valid card number (Luhn algorithm compliant)
- Any expiration date in the future
- Any 3-digit CVC
- Payment always succeeds
### Real Stripe Testing (When Enabled)
- Use Stripe test cards: https://stripe.com/docs/testing
- Test success scenarios: `4242 4242 4242 4242`
- Test failure scenarios: `4000 0000 0002` (generic decline)
- Verify webhooks are received correctly
- Test refunds via Stripe Dashboard
## Deployment Checklist
Before going live with Stripe:
- [ ] Update `NEXT_PUBLIC_STRIPE_ENABLED=true`
- [ ] Use live Stripe keys (not test keys)
- [ ] Configure production webhook endpoint
- [ ] Test payment flow end-to-end
- [ ] Verify bookings are marked as confirmed
- [ ] Test refund flow
- [ ] Monitor Stripe dashboard for errors
- [ ] Set up email notifications for payment failures

211
docs/site_requirements.md Normal file
View File

@@ -0,0 +1,211 @@
# Anchor:23 — Site Requirements
Documento de ejecución para OpenCode / Codex.
Define identidad visual, estructura del sitio, copy y reglas de implementación.
---
## 1. Objetivo del sitio
* Representar a Anchor:23 como concepto de belleza de ultra lujo.
* Comunicar exclusividad basada en estándar, no en aspiración.
* Separar marca institucional de sistemas operativos (booking / kiosk).
* Convertir sin presión: membresía y cita.
---
## 2. Arquitectura de dominios
* `anchor23.mx` — Sitio institucional
* `booking.anchor23.mx` — Sistema de reservas (The Boutique)
* `kiosk.anchor23.mx` — UI táctil en sucursal
El sitio **anchor23.mx** no contiene lógica compleja.
Es contenido, marca y narrativa.
---
## 3. Paleta de Color
### Base
* Bone White `#F6F1EC` — fondo principal
* Soft Cream `#EFE7DE` — bloques y secciones
* Mocha Taupe `#B8A89A` — íconos y divisores
* Deep Earth `#6F5E4F` — botones primarios
* Charcoal Brown `#3F362E` — texto principal / footer
Reglas:
* Sin colores saturados
* Sin gradientes
* Sin sombras duras
---
## 4. Tipografía
### Headings
* Serif editorial sobria
* Ejemplos: The Seasons, Canela
### Body / UI
* Sans neutral
* Ejemplos: Inter, DM Sans
Reglas:
* Mucho espacio
* Jerarquía estricta
* Peso visual contenido
---
## 5. Layout
* Grid amplio
* Márgenes generosos
* Ritmo vertical lento
* Espacio negativo dominante
Nunca:
* UI densa
* Animaciones llamativas
* Efectos innecesarios
---
## 6. Header
### Navegación
* Inicio
* Nosotros
* Servicios
* Membresías
CTA principal:
* Solicitar Membresía
---
## 7. Landing Page
### Hero
**Título**
ANCHOR:23
**Subtítulo**
Belleza anclada en exclusividad
**Texto**
Un estándar exclusivo de lujo y precisión.
**CTAs**
* Ver servicios
* Solicitar cita
---
### Fundamento
**Título**
Fundamento
**Subtítulo**
Nada sólido nace del caos
**Texto**
Anchor:23 nace de la unión de dos creativos que creen en el lujo no como promesa, sino como estándar.
Aquí, lo excepcional es norma: una experiencia exclusiva y coherente, diseñada para quienes entienden que el verdadero lujo está en la precisión, no en el exceso.
---
### Servicios Exclusivos (Preview)
#### Spa de Alta Gama
Sauna y spa excepcionales, diseñados para el rejuvenecimiento y el equilibrio.
#### Arte y Manicure de Precisión
Estilización y técnica donde el detalle define el resultado.
#### Peinado y Maquillaje de Lujo
Transformaciones discretas y sofisticadas para ocasiones selectas.
CTA:
* Ver todos los servicios
---
### Testimonios
Título:
Testimonios
Ejemplos:
* "La atención al detalle define el lujo real." — Gabriela M.
* "Exclusivo sin ser pretencioso." — Lorena T.
CTA:
* Solicitar Membresía
---
## 8. Footer
Contenido:
* Marca y ciudad
* Links: Nosotros, Servicios, Contacto
* Legal: Privacy Policy, Legal
* Teléfono y correo
---
## 9. Páginas internas
### /servicios
* Listado completo
* CTA a booking.anchor23.mx
### /historia
* Origen de Anchor
* Significado de : y 23
### /contacto
* Formulario
* Datos de contacto
### /franchises
* Modelo: una sucursal por ciudad
* No franquicia masiva
---
## 10. Principios de ejecución
* HTML semántico
* CSS limpio
* JS mínimo
* Accesibilidad básica
* Performance sobre efectos
El sitio debe sentirse silencioso, sólido y permanente.