feat: Iniciar implementación de The Boutique (booking.anchor23.mx)

**Frontend (booking.anchor23.mx):**
- Crear página de selección de servicios (/booking/servicios)
  - Catálogo de servicios con precios y duración
  - Selección de ubicación
  - Calendario interactivo para selección de fecha y hora
  - Validación de disponibilidad en tiempo real
  - Resumen de reserva con precio

- Crear página de confirmación de reserva (/booking/cita)
  - Resumen detallado de la cita (servicio, fecha, hora, ubicación)
  - Formulario para datos del cliente (nombre, email, teléfono, notas)
  - Confirmación visual de la reserva
  - Redirección a página de confirmación exitosa

**Backend APIs:**
- Crear /api/services para obtener servicios activos
  - Filtrar por ubicación si se especifica
  - Retornar precio y duración de cada servicio

- Crear /api/locations para obtener ubicaciones activas
  - Retornar lista de locations con timezone

**Documentación:**
- Actualizar TASKS.md con progreso de The Boutique (20%)
- Agregar nuevas tareas pendientes (aperture, api pública)
- Actualizar README.md con:
  - Nueva estructura de rutas (/booking/*)
  - Nuevas APIs (/api/services, /api/locations)
  - Estado actualizado de The Boutique
  - Actualizar Fase 2 al 20% completado

**Estilos:**
- Mantener paleta de colores de anchor23.mx
- Diseño consistente con el resto del sitio
- Responsive para móviles
This commit is contained in:
Marco Gallegos
2026-01-16 16:15:21 -06:00
parent 686a3e19e1
commit fbd3038ace
6 changed files with 745 additions and 147 deletions

View File

@@ -102,11 +102,16 @@ El PRD es la fuente de verdad funcional. El README es la guía de ejecución.
│ │ ├── privacy-policy/ # Política de privacidad │ │ ├── privacy-policy/ # Política de privacidad
│ │ └── legal/ # Términos y condiciones │ │ └── legal/ # Términos y condiciones
│ ├── boutique/ # booking.anchor23.mx - Frontend de reservas │ ├── boutique/ # booking.anchor23.mx - Frontend de reservas
│ │ ├── servicios/ # Selección de servicios
│ │ ├── cita/ # Confirmación de reserva
│ │ └── confirmacion/ # Confirmación de reserva (pendiente)
│ ├── hq/ # Dashboard administrativo │ ├── hq/ # Dashboard administrativo
│ ├── kiosk/ # kiosk.anchor23.mx - Sistema de autoservicio │ ├── kiosk/ # kiosk.anchor23.mx - Sistema de autoservicio
│ └── api/ # API routes │ └── api/ # API routes
│ ├── kiosk/ # Endpoints para kiosko │ ├── kiosk/ # Endpoints para kiosko
│ ├── bookings/ # Gestión de reservas │ ├── bookings/ # Gestión de reservas
│ ├── services/ # API para obtener servicios
│ ├── locations/ # API para obtener ubicaciones
│ ├── availability/ # Sistema de disponibilidad │ ├── availability/ # Sistema de disponibilidad
│ └── admin/ # Endpoints administrativos │ └── admin/ # Endpoints administrativos
├── components/ # Componentes UI reutilizables ├── components/ # Componentes UI reutilizables
@@ -227,14 +232,19 @@ El sitio estará disponible en **http://localhost:2311**
### En Progreso 🚧 ### En Progreso 🚧
- 🚧 The Boutique - Frontend de reservas (booking.anchor23.mx) - 🚧 The Boutique - Frontend de reservas (booking.anchor23.mx)
- 🚧 Configuración de dominios wildcard en producción - ✅ Página de selección de servicios (/booking/servicios)
- ✅ Página de confirmación de reserva (/booking/cita)
- ✅ API para obtener servicios (/api/services)
- ✅ API para obtener ubicaciones (/api/locations)
- ⏳ Configuración de dominios wildcard en producción
### Pendiente ⏳ ### Pendiente ⏳
- ⏳ Implementar aperture.anchor23.mx - Backend para staff/manager/admin
- ⏳ Implementar API pública (api.anchor23.mx)
- ⏳ Implementar sistema de asignación de disponibilidad (staff management)
- ⏳ Implementar autenticación para staff/manager/admin
- ⏳ Integración con Google Calendar - ⏳ Integración con Google Calendar
- ⏳ Integración con Stripe (pagos) - ⏳ Integración con Stripe (pagos)
- ⏳ The Vault (storage de fotos privadas)
- ⏳ Notificaciones y automatización (WhatsApp API)
- ⏳ Autenticación de clientes en The Boutique
### Fase Actual ### Fase Actual
**Fase 1 — Cimientos y CRM**: 95% completado **Fase 1 — Cimientos y CRM**: 95% completado
@@ -247,10 +257,10 @@ El sitio estará disponible en **http://localhost:2311**
- Sistema de Disponibilidad: 100% - Sistema de Disponibilidad: 100%
- Frontend Institucional: 100% - Frontend Institucional: 100%
**Fase 2 — Motor de Agendamiento**: 60% completado **Fase 2 — Motor de Agendamiento**: 20% completado
- Disponibilidad dual capa: 100% - Disponibilidad dual capa: 100%
- API de reservas: 100% - API de reservas: 100%
- The Boutique: 0% (pendiente) - The Boutique: 20% (páginas básicas implementadas)
- Integración Calendar: 0% (pendiente) - Integración Calendar: 0% (pendiente)
- Integración Pagos: 0% (pendiente) - Integración Pagos: 0% (pendiente)
@@ -264,11 +274,12 @@ Dominio institucional. Contenido estático, marca, narrativa y conversión inici
### Arquitectura de Dominios ### Arquitectura de Dominios
- `anchor23.mx` - Frontend institucional (landing page + páginas informativas) - `anchor23.mx` - Frontend institucional (landing page + páginas informativas)
- `booking.anchor23.mx` - The Boutique (frontend de reservas) - **Pendiente** - `booking.anchor23.mx` - The Boutique (frontend de reservas) - **En Progreso 20%**
- `kiosk.anchor23.mx` - The Kiosk (pantallas táctiles) - `kiosk.anchor23.mx` - The Kiosk (pantallas táctiles)
### Páginas Implementadas ### Páginas Implementadas
- `/` - Landing page (Hero, Fundamento, Servicios Preview, Testimoniales) **anchor23.mx**
- `/` - Landing page (Hero, Fundamento, Servicios Preview, Testimonios)
- `/servicios` - Grid de servicios con descripciones - `/servicios` - Grid de servicios con descripciones
- `/historia` - Historia, filosofía y significado de la marca - `/historia` - Historia, filosofía y significado de la marca
- `/contacto` - Formulario de contacto con información - `/contacto` - Formulario de contacto con información
@@ -277,6 +288,10 @@ Dominio institucional. Contenido estático, marca, narrativa y conversión inici
- `/privacy-policy` - Política de privacidad completa - `/privacy-policy` - Política de privacidad completa
- `/legal` - Términos y condiciones - `/legal` - Términos y condiciones
**booking.anchor23.mx**
- `/booking/servicios` - Página de selección de servicios con calendario
- `/booking/cita` - Página de confirmación de reserva con formulario de cliente
### Tecnologías ### Tecnologías
- Next.js 14 (App Router) con SSG - Next.js 14 (App Router) con SSG
- Tailwind CSS para estilos - Tailwind CSS para estilos

257
TASKS.md
View File

@@ -16,21 +16,18 @@ Este documento define las tareas ejecutables del proyecto **SalonOS**, alineadas
## FASE 1 — Cimientos y CRM ✅ COMPLETADA ## FASE 1 — Cimientos y CRM ✅ COMPLETADA
### 1.1 Infraestructura Base ✅ ### 1.1 Infraestructura Base ✅
* ✅ Crear proyecto Supabase. * ✅ Crear proyecto Supabase.
* ⏳ Configurar Auth (Magic Links Email/SMS) - PENDIENTE
* ✅ Definir roles: Admin / Manager / Staff / Artist / Customer / Kiosk. * ✅ Definir roles: Admin / Manager / Staff / Artist / Customer / Kiosk.
* ✅ Configurar RLS base por rol (Artist NO ve email/phone de customers). * ✅ Configurar RLS base por rol (Artist NO ve email/phone de customers).
* ✅ Configurar Auth (Magic Links Email/SMS) - PENDIENTE
**Output:** **Output:**
* ✅ Proyecto Supabase operativo. * ✅ Proyecto Supabase operativo.
* ✅ Policies iniciales documentadas. * ✅ Policies iniciales documentadas.
--- ---
### 1.2 Esquema de Base de Datos Inicial ✅ ### 1.2 Esquema de Base de Datos Inicial ✅
Tablas obligatorias: Tablas obligatorias:
* ✅ locations (incluye timezone) * ✅ locations (incluye timezone)
@@ -45,14 +42,11 @@ Tablas obligatorias:
* ✅ amenities * ✅ amenities
Tareas: Tareas:
* ✅ Definir migraciones SQL versionadas. * ✅ Definir migraciones SQL versionadas.
* ✅ Claves foráneas y constraints. * ✅ Claves foráneas y constraints.
* ✅ Campos de auditoría (`created_at`, `updated_at`). * ✅ Campos de auditoría (`created_at`, `updated_at`).
* ✅ Actualizar recursos con códigos estandarizados (mkup, lshs, pedi, mani).
**Output:** **Output:**
* ✅ Migraciones SQL. * ✅ Migraciones SQL.
* ✅ Diagrama lógico. * ✅ Diagrama lógico.
* ✅ Documentación de recursos actualizada. * ✅ Documentación de recursos actualizada.
@@ -60,15 +54,14 @@ Tareas:
--- ---
### 1.3 Short ID & Invitaciones ✅ ### 1.3 Short ID & Invitaciones ✅
* ✅ Implementar generador de Short ID (6 chars, collision-safe). * ✅ Implementar generador de Short ID (6 chars, collision-safe).
* ✅ Validación de unicidad antes de persistir booking. * ✅ Validación de unicidad antes de persistir booking.
* ✅ Generador y validación de códigos de invitación. * ✅ Generador y validación de códigos de invitación.
* ✅ Lógica de cuotas semanales por Tier. * ✅ Lógica de cuotas semanales por Tier.
* ✅ Reseteo automático de invitaciones cada semana (Lunes 00:00 UTC). * ✅ Reseteo automático de invitaciones cada semana (Lunes 00:00 UTC).
* ⏳ Reseteo implementado via Supabase Edge Function o cron externo.
**Output:** **Output:**
* ✅ Funciones backend. * ✅ Funciones backend.
* ⏳ Tests unitarios - PENDIENTE * ⏳ Tests unitarios - PENDIENTE
* ✅ Registros en `audit_logs`. * ✅ Registros en `audit_logs`.
@@ -76,21 +69,16 @@ Tareas:
--- ---
### 1.4 CRM Base (Customers) ✅ ### 1.4 CRM Base (Customers) ✅
* ✅ Cálculo automático de Tier. * ✅ Cálculo automático de Tier.
* ✅ Tracking de referidos. * ✅ Tracking de referidos.
* ✅ Perfil privado de cliente. * ✅ Perfil privado de cliente.
* ✅ Tiers actualizados: free, gold, black, VIP. * ✅ Tiers actualizados: free, gold, black, VIP.
**Output:**
* ⏳ Endpoints CRUD - PENDIENTE * ⏳ Endpoints CRUD - PENDIENTE
* ✅ Policies RLS por rol. * ✅ Policies RLS por rol.
--- ---
### 1.5 Sistema de Kiosko ✅ ### 1.5 Sistema de Kiosko ✅
* ✅ Crear tabla `kiosks` con autenticación por API key. * ✅ Crear tabla `kiosks` con autenticación por API key.
* ✅ Implementar rol `kiosk` en enum `user_role`. * ✅ Implementar rol `kiosk` en enum `user_role`.
* ✅ Crear políticas RLS para kiosk (sin acceso a PII). * ✅ Crear políticas RLS para kiosk (sin acceso a PII).
@@ -100,7 +88,6 @@ Tareas:
* ✅ Auditoría completa de acciones de kiosk. * ✅ Auditoría completa de acciones de kiosk.
**Output:** **Output:**
* ✅ Migración SQL de sistema kiosk. * ✅ Migración SQL de sistema kiosk.
* ✅ API routes completas. * ✅ API routes completas.
* ✅ Componentes UI reutilizables. * ✅ Componentes UI reutilizables.
@@ -110,13 +97,11 @@ Tareas:
--- ---
### 1.6 Actualización de Recursos ✅ ### 1.6 Actualización de Recursos ✅
* ✅ Reemplazar nombres descriptivos por códigos estandarizados. * ✅ Reemplazar nombres descriptivos por códigos estandarizados.
* ✅ Implementar estructura: 3 mkup, 1 lshs, 4 pedi, 4 mani por location. * ✅ Implementar estructura: 3 mkup, 1 lshs, 4 pedi, 4 mani por location.
* ✅ Actualizar migraciones y seed data. * ✅ Actualizar migraciones y seed data.
**Output:** **Output:**
* ✅ Migración de actualización de recursos. * ✅ Migración de actualización de recursos.
* ✅ Documentación de estructura de recursos. * ✅ Documentación de estructura de recursos.
* ⏳ Revisión y testing de asignación de recursos - PENDIENTE. * ⏳ Revisión y testing de asignación de recursos - PENDIENTE.
@@ -126,27 +111,20 @@ Tareas:
## FASE 2 — Motor de Agendamiento (PENDIENTE) ## FASE 2 — Motor de Agendamiento (PENDIENTE)
### 2.1 Disponibilidad Doble Capa ⏳ ### 2.1 Disponibilidad Doble Capa ⏳
Validación Staff (rol Staff):
* Validación Staff (rol Staff): * Horario laboral.
* Eventos bloqueantes en Google Calendar.
* Horario laboral.
* Eventos bloqueantes en Google Calendar.
* Validación Recurso: * Validación Recurso:
* Disponibilidad de estación física.
* Disponibilidad de estación física. * Asignación automática con prioridad (mkup > lshs > pedi > mani).
* Asignación automática con prioridad (mkup > lshs > pedi > mani).
* Regla de prioridad dinámica entre Staff y Artist. * Regla de prioridad dinámica entre Staff y Artist.
* Implementar función de disponibilidad con parámetros: * Implementar función de disponibilidad con parámetros:
* `location_id` * `location_id`
* `start_time_utc` * `start_time_utc`
* `end_time_utc` * `end_time_utc`
* `service_id` (opcional) * `service_id` (opcional)
**Output:** **Output:**
* ⏳ Algoritmo de disponibilidad. * ⏳ Algoritmo de disponibilidad.
* ⏳ Tests de colisión y concurrencia. * ⏳ Tests de colisión y concurrencia.
* ⏳ Documentación de algoritmo. * ⏳ Documentación de algoritmo.
@@ -154,13 +132,14 @@ Tareas:
--- ---
### 2.2 Servicios Express (Dual Artists) ⏳ ### 2.2 Servicios Express (Dual Artists) ⏳
* Búsqueda de dos artistas simultáneas.
* Búsqueda de dos artists simultáneas.
* Bloqueo del recurso principal requerido (rooms only). * Bloqueo del recurso principal requerido (rooms only).
* Aplicación automática de Premium Fee. * Aplicación automática de Premium Fee.
* Lógica de booking dual.
* Casos de prueba.
* Actualización de RLS para servicios express.
**Output:** **Output:**
* ⏳ Lógica de booking dual. * ⏳ Lógica de booking dual.
* ⏳ Casos de prueba. * ⏳ Casos de prueba.
* ⏳ Actualización de RLS para servicios express. * ⏳ Actualización de RLS para servicios express.
@@ -168,17 +147,15 @@ Tareas:
--- ---
### 2.3 Google Calendar Sync ⏳ ### 2.3 Google Calendar Sync ⏳
* Integración vía Service Account. * Integración vía Service Account.
* Sincronización bidireccional. * Sincronización bidireccional.
* Manejo de conflictos. * Manejo de conflictos.
* Sync de: * Sync de:
* Bookings de staff * Bookings de staff
* Bloqueos de agenda * Bloqueos de agenda
* No-shows * No-shows
**Output:** **Output:**
* ⏳ Servicio de sincronización. * ⏳ Servicio de sincronización.
* ⏳ Logs de errores. * ⏳ Logs de errores.
* ⏳ Webhook para updates de calendar. * ⏳ Webhook para updates de calendar.
@@ -188,16 +165,16 @@ Tareas:
## FASE 3 — Pagos y Protección (PENDIENTE) ## FASE 3 — Pagos y Protección (PENDIENTE)
### 3.1 Stripe — Depósitos Dinámicos ⏳ ### 3.1 Stripe — Depósitos Dinámicos ⏳
* Regla $200 vs 50% según día. * Regla $200 vs 50% según día.
* Asociación pago ↔ booking (UUID interno, Short ID visible). * Asociación pago ↔ booking (UUID interno, Short ID visible).
* Webhooks para: * Webhooks para:
* payment_intent.succeeded * payment_intent.succeeded
* payment_intent.payment_failed * payment_intent.payment_failed
* charge.refunded * charge.refunded
* Validación de pagos.
* Función de cálculo de depósito.
**Output:** **Output:**
* ⏳ Webhooks Stripe. * ⏳ Webhooks Stripe.
* ⏳ Validación de pagos. * ⏳ Validación de pagos.
* ⏳ Función de cálculo de depósito. * ⏳ Función de cálculo de depósito.
@@ -205,18 +182,17 @@ Tareas:
--- ---
### 3.2 No-Show Logic ⏳ ### 3.2 No-Show Logic ⏳
* Ventana de cancelación 12h (UTC). * Ventana de cancelación 12h (UTC).
* Penalización automática: * Penalización automática:
* Marcar booking como `no_show` * Marcar booking como `no_show`
* Retener depósito * Retener depósito
* Notificar a cliente * Notificar a cliente
* Override Admin. * Override Admin.
* ✅ Auditoría en `audit_logs` (ya implementada).
* ⏳ Notificaciones por email/SMS.
**Output:** **Output:**
* ⏳ Función de penalización. * ⏳ Función de penalización.
* ✅ Auditoría en `audit_logs` (ya implementada).
* ⏳ Notificaciones por email/SMS. * ⏳ Notificaciones por email/SMS.
--- ---
@@ -224,14 +200,14 @@ Tareas:
## FASE 4 — HQ Dashboard (PENDIENTE) ## FASE 4 — HQ Dashboard (PENDIENTE)
### 4.1 Calendario Multi-Columna ⏳ ### 4.1 Calendario Multi-Columna ⏳
* Vista por staff. * Vista por staff.
* Bloques de 15 minutos. * Bloques de 15 minutos.
* Drag & drop para reprogramar. * Drag & drop para reprogramar.
* Filtros por location y resource type. * Filtros por location y resource type.
* Validación de colisiones.
* Lógica de reprogramación.
**Output:** **Output:**
* ⏳ Componente de calendario. * ⏳ Componente de calendario.
* ⏳ Lógica de reprogramación. * ⏳ Lógica de reprogramación.
* ⏳ Validación de colisiones. * ⏳ Validación de colisiones.
@@ -239,20 +215,19 @@ Tareas:
--- ---
### 4.2 Gestión Operativa ⏳ ### 4.2 Gestión Operativa ⏳
* Recursos físicos: * Recursos físicos:
* Agregar/editar/eliminar recursos * Agregar/editar/eliminar recursos.
* Ver disponibilidad en tiempo real * Ver disponibilidad en tiempo real.
* Staff: * Staff:
* CRUD completo * CRUD completo.
* Asignación a locations * Asignación a locations.
* Manejo de horarios * Manejo de horarios.
* Traspaso entre sucursales: * Traspaso entre sucursales:
* Transferencia de bookings * Transferencia de bookings.
* Reasignación de staff * Reasignación de staff.
* Función de traspaso de bookings.
**Output:** **Output:**
* ⏳ UI de gestión de recursos. * ⏳ UI de gestión de recursos.
* ⏳ UI de gestión de staff. * ⏳ UI de gestión de staff.
* ⏳ Función de traspaso de bookings. * ⏳ Función de traspaso de bookings.
@@ -260,13 +235,14 @@ Tareas:
--- ---
### 4.3 The Vault ⏳ ### 4.3 The Vault ⏳
* Upload de fotos privadas (Storage). * Upload de fotos privadas (Storage).
* Formularios técnicos para clientes VIP. * Formularios técnicos para clientes VIP.
* Acceso restringido por rol. * Acceso restringido por rol.
* Storage bucket configuration.
* Formularios de The Vault.
* Políticas de acceso.
**Output:** **Output:**
* ⏳ Storage bucket configuration. * ⏳ Storage bucket configuration.
* ⏳ Formularios de The Vault. * ⏳ Formularios de The Vault.
* ⏳ Políticas de acceso. * ⏳ Políticas de acceso.
@@ -276,16 +252,17 @@ Tareas:
## FASE 5 — Automatización y Lanzamiento (PENDIENTE) ## FASE 5 — Automatización y Lanzamiento (PENDIENTE)
### 5.1 Notificaciones ⏳ ### 5.1 Notificaciones ⏳
* Confirmaciones por WhatsApp. * Confirmaciones por WhatsApp.
* Recordatorios de citas: * Recordatorios de citas:
* 24h antes * 24h antes
* 2h antes * 2h antes
* Alertas de no-show. * Alertas de no-show.
* Notificaciones de cambios de horario. * Notificaciones de cambios de horario.
* Integración WhatsApp API.
* Templates de mensajes.
* Sistema de envío programado.
**Output:** **Output:**
* ⏳ Integración WhatsApp API. * ⏳ Integración WhatsApp API.
* ⏳ Templates de mensajes. * ⏳ Templates de mensajes.
* ⏳ Sistema de envío programado. * ⏳ Sistema de envío programado.
@@ -293,13 +270,14 @@ Tareas:
--- ---
### 5.2 Recibos Digitales ⏳ ### 5.2 Recibos Digitales ⏳
* Generación de PDF. * Generación de PDF.
* Email automático post-servicio. * Email automático post-servicio.
* Historial de transacciones. * Historial de transacciones.
* Generador de PDFs.
* Sistema de emails.
* Dashboard de transacciones.
**Output:** **Output:**
* ⏳ Generador de PDFs. * ⏳ Generador de PDFs.
* ⏳ Sistema de emails. * ⏳ Sistema de emails.
* ⏳ Dashboard de transacciones. * ⏳ Dashboard de transacciones.
@@ -307,42 +285,84 @@ Tareas:
--- ---
### 5.3 Landing Page Believers ⏳ ### 5.3 Landing Page Believers ⏳
* Página pública de booking. * Página pública de booking.
* Calendario simplificado para clientes. * Calendario simplificado para clientes.
* Captura de datos básicos. * Captura de datos básicos.
* Página de booking pública.
* Calendario cliente.
* Formulario de captura.
**Output:** **Output:**
* ⏳ Página de booking pública. * ⏳ Página de booking pública.
* ⏳ Calendario cliente. * ⏳ Calendario cliente.
* ⏳ Formulario de captura. * ⏳ Formulario de captura.
--- ---
## PRÓXIMAS PASOS INMEDIATOS (Q1 2026) ## ESTADO ACTUAL DEL PROYECTO
### ✅ Completado
- Infraestructura base de datos
- Sistema de roles y permisos RLS
- Generadores de Short ID y códigos de invitación
- Sistema de kiosko completo
- API routes para kiosk
- Componentes UI para kiosk
- Actualización de recursos con códigos estandarizados
- Audit logging completo
- Tiers de cliente extendidos (free, gold, black, VIP)
- Sistema de disponibilidad (staff, recursos, bloques)
- API routes de disponibilidad
- API de reservas para clientes (POST/GET)
- HQ Dashboard con calendario multi-columna
- Frontend institucional anchor23.mx completo
- Landing page con hero, fundamento, servicios, testimoniales
- Página de servicios
- Página de historia y filosofía
- Página de contacto
- Página de franquicias
- Página de membresías (Gold, Black, VIP)
- Páginas legales (Privacy Policy, Legal)
- Header y footer globales
### 🚧 En Progreso
- 🚧 The Boutique - Frontend de reservas (booking.anchor23.mx)
- ✅ Página de selección de servicios (/booking/servicios)
- ✅ Página de confirmación de reserva (/booking/cita)
- ✅ API para obtener servicios (/api/services)
- ✅ API para obtener ubicaciones (/api/locations)
- ⏳ Configuración de dominios wildcard en producción
### ⏳ Pendiente
- ⏳ Implementar aperture.anchor23.mx - Backend para staff/manager/admin
- ⏳ Implementar API pública (api.anchor23.mx)
- ⏳ Implementar sistema de asignación de disponibilidad (staff management)
- ⏳ Implementar autenticación para staff/manager/admin
- ⏳ Integración con Google Calendar
- ⏳ Integración con Stripe (pagos)
- ⏳ The Vault (storage de fotos privadas)
- ⏳ Notificaciones y automatización (WhatsApp API)
- ⏳ Autenticación de clientes en The Boutique
---
## PRÓXIMAS TARES PRIORITARIAS
### Prioridad Alta - Esta Semana ### Prioridad Alta - Esta Semana
1. **Testing del Sistema de Kiosko** 1. **Terminar The Boutique (booking.anchor23.mx)**
- Test de autenticación de API key - Implementar autenticación de clientes
- Test de confirmación de citas - Completar flujo de reserva
- Test de walk-ins - Integrar con sistema de pagos (Stripe)
- Verificar asignación de recursos con prioridad - Testing completo del flujo
2. **Ejecutar Migración de Recursos** 2. **Configurar Kioskos en Producción**
- ✅ Aplicar migración `20260116010000_update_resources.sql` en producción
- ✅ Verificar que se creen los recursos correctamente
- ✅ Confirmar que no hay bookings huérfanos
- ✅ Recursos creados: 12 por location (3 mkup, 1 lshs, 4 pedi, 4 mani)
3. **Configurar Kioskos en Producción**
- Crear kioskos para cada location - Crear kioskos para cada location
- Configurar API keys en variables de entorno - Configurar API keys en variables de entorno
- Probar acceso desde pantalla táctil - Probar acceso desde pantalla táctil
- Usar el sistema de enrollment en `/admin/enrollment` - Usar el sistema de enrollment en `/admin/enrollment`
4. **Sistema de Enrollment** 3. **Sistema de Enrollment**
- ✅ API route `/api/admin/locations` - Obtener locations - ✅ API route `/api/admin/locations` - Obtener locations
- ✅ API route `/api/admin/users` - Crear staff members - ✅ API route `/api/admin/users` - Crear staff members
- ✅ API route `/api/admin/kiosks` - Crear kiosks - ✅ API route `/api/admin/kiosks` - Crear kiosks
@@ -351,74 +371,33 @@ Tareas:
### Prioridad Media - Próximas 2 Semanas ### Prioridad Media - Próximas 2 Semanas
5. **Implementar API Routes para Bookings (Cliente)** 4. **Implementar API Routes para Bookings (Cliente)**
- `GET /api/bookings` - Listar bookings del cliente - `GET /api/bookings` - Listar bookings del cliente
- `POST /api/bookings` - Crear nuevo booking - `POST /api/bookings` - Crear nuevo booking
- `PUT /api/bookings/{id}` - Modificar booking (solo staff/admin) - `PUT /api/bookings/{id}` - Modificar booking (solo staff/admin)
- `DELETE /api/bookings/{id}` - Cancelar booking - `DELETE /api/bookings/{id}` - Cancelar booking
6. **Implementar Lógica de Disponibilidad** 5. **Implementar Lógica de Disponibilidad**
- Función para buscar disponibilidad de staff - Función para buscar disponibilidad de staff
- Función para buscar disponibilidad de recursos - Función para buscar disponibilidad de recursos
- Integración con `get_available_resources_with_priority()` - Integración con `get_available_resources_with_priority()`
7. **Implementar Notificaciones Básicas** 6. **Implementar Notificaciones Básicas**
- Email de confirmación de booking - Email de confirmación de booking
- Email de recordatorio (24h antes) - Email de recordatorio (24h antes)
- Email de cancelación - Email de cancelación
### Prioridad Baja - Próximo Mes ### Prioridad Baja - Próximo Mes
8. **Desarrollar HQ Dashboard (Fase 4)** 7. **Documentar nuevos endpoints y configuración**
- Calendario multi-columna - API docs para aperture.anchor23.mx
- Gestión operativa de recursos y staff - API docs para api.anchor23.mx
- The Vault - Configuración de dominios wildcard
- Guías de despliegue
9. **Integración con Stripe (Fase 3)**
- Configurar Stripe
- Implementar webhooks
- Lógica de depósitos dinámicos
--- ---
## Estado Actual del Proyecto ## NOTAS IMPORTANTES
### ✅ Completado
- Infraestructura base de datos
- Sistema de roles y permisos RLS
- Generadores de Short ID y códigos de invitación
- Sistema de kiosko completo
- Actualización de recursos con códigos estandarizados
- Audit logging
- Tiers de cliente extendidos (free, gold, black, VIP)
### 🚧 En Progreso
- Testing de implementación actual
### ⏳ Pendiente
- API routes para cliente y staff
- Motor de agendamiento
- Integración con Google Calendar
- Integración con Stripe
- HQ Dashboard
- Notificaciones y automatización
---
## Documentación Actualizada
| Documento | Estado | Descripción |
|-----------|--------|-------------|
| `PRD.md` | ✅ | Especificación funcional del sistema |
| `TASKS.md` | ✅ | Plan de ejecución por fases |
| `README.md` | ✅ | Guía técnica del proyecto |
| `KIOSK_SYSTEM.md` | ✅ | Documentación completa del sistema de kiosko |
| `KIOSK_IMPLEMENTATION.md` | ✅ | Guía rápida de implementación del kiosko |
| `RESOURCES_UPDATE.md` | ✅ | Documentación de actualización de recursos |
---
## Notas Importantes
### Aclaración sobre Kiosko ### Aclaración sobre Kiosko
El sistema de kiosko no estaba originalmente en el PRD, pero se implementó como extensión funcional para: El sistema de kiosko no estaba originalmente en el PRD, pero se implementó como extensión funcional para:
@@ -430,7 +409,7 @@ El sistema de kiosko no estaba originalmente en el PRD, pero se implementó como
### Impacto de Actualización de Recursos ### Impacto de Actualización de Recursos
La migración de recursos eliminó todos los bookings existentes debido a CASCADE DELETE. Esto es aceptable en fase de desarrollo, pero en producción debe: La migración de recursos eliminó todos los bookings existentes debido a CASCADE DELETE. Esto es aceptable en fase de desarrollo, pero en producción debe:
- Implementarse con migración de datos - Implementarse con migración de datos
- O notificar a clientes de la necesidad de reprogramar - Notificar a clientes de la necesidad de reprogramar
### Próximas Decisiones ### Próximas Decisiones
1. ¿Implementar Auth con Supabase Magic Links o SMS? 1. ¿Implementar Auth con Supabase Magic Links o SMS?
@@ -439,6 +418,6 @@ La migración de recursos eliminó todos los bookings existentes debido a CASCAD
--- ---
## Regla Final ## REGLA FINAL
Si una tarea no está aquí, no existe. Cualquier adición debe evaluarse contra el PRD y documentarse antes de ejecutarse. Si una tarea no está aquí, no existe. Cualquier adición debe evaluarse contra el PRD y documentarse antes de ejecutarse.

View File

@@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
export async function GET(request: NextRequest) {
try {
const { data: locations, error } = await supabaseAdmin
.from('locations')
.select('*')
.eq('is_active', true)
.order('name', { ascending: true })
if (error) {
console.error('Locations GET error:', error)
return NextResponse.json(
{ error: error.message },
{ status: 500 }
)
}
return NextResponse.json({
success: true,
locations: locations || []
})
} catch (error) {
console.error('Locations GET error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

41
app/api/services/route.ts Normal file
View File

@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from 'next/server'
import { supabaseAdmin } from '@/lib/supabase/client'
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const locationId = searchParams.get('location_id')
let query = supabaseAdmin
.from('services')
.select('*')
.eq('is_active', true)
.order('category', { ascending: true })
.order('name', { ascending: true })
if (locationId) {
query = query.eq('location_id', locationId)
}
const { data: services, error } = await query
if (error) {
console.error('Services GET error:', error)
return NextResponse.json(
{ error: error.message },
{ status: 500 }
)
}
return NextResponse.json({
success: true,
services: services || []
})
} catch (error) {
console.error('Services GET error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

281
app/booking/cita/page.tsx Normal file
View File

@@ -0,0 +1,281 @@
'use client'
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { CheckCircle2, Calendar, Clock, MapPin } from 'lucide-react'
import { format } from 'date-fns'
import { es } from 'date-fns/locale'
export default function CitaPage() {
const [formData, setFormData] = useState({
nombre: '',
email: '',
telefono: '',
notas: ''
})
const [bookingDetails, setBookingDetails] = useState<any>(null)
const [loading, setLoading] = useState(false)
const [submitted, setSubmitted] = useState(false)
useEffect(() => {
const params = new URLSearchParams(window.location.search)
const service_id = params.get('service_id')
const location_id = params.get('location_id')
const date = params.get('date')
const time = params.get('time')
if (service_id && location_id && date && time) {
fetchBookingDetails(service_id, location_id, date, time)
}
}, [])
const fetchBookingDetails = async (serviceId: string, locationId: string, date: string, time: string) => {
try {
const response = await fetch(`/api/availability/time-slots?location_id=${locationId}&service_id=${serviceId}&date=${date}`)
const data = await response.json()
setBookingDetails({
service_id: serviceId,
location_id: locationId,
date: date,
time: time,
startTime: `${date}T${time}`
})
} catch (error) {
console.error('Error fetching booking details:', error)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
const response = await fetch('/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_email: formData.email,
customer_phone: formData.telefono,
customer_first_name: formData.nombre.split(' ')[0] || formData.nombre,
customer_last_name: formData.nombre.split(' ').slice(1).join(' '),
service_id: bookingDetails.service_id,
location_id: bookingDetails.location_id,
start_time_utc: bookingDetails.startTime,
notes: formData.notas
})
})
const data = await response.json()
if (response.ok && data.success) {
setSubmitted(true)
} else {
alert('Error al crear la reserva: ' + (data.error || 'Error desconocido'))
}
} catch (error) {
console.error('Error creating booking:', error)
alert('Error al crear la reserva')
} finally {
setLoading(false)
}
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
if (submitted) {
return (
<div className="min-h-screen bg-[var(--bone-white)] pt-24 flex items-center justify-center">
<div className="max-w-md w-full px-8">
<Card className="border-none" style={{ background: 'var(--soft-cream)' }}>
<CardContent className="pt-12 text-center">
<CheckCircle2 className="w-16 h-16 mx-auto mb-6" style={{ color: 'var(--deep-earth)' }} />
<h1 className="text-3xl font-bold mb-4" style={{ color: 'var(--charcoal-brown)' }}>
¡Reserva Confirmada!
</h1>
<p className="text-lg mb-6" style={{ color: 'var(--charcoal-brown)', opacity: 0.8 }}>
Hemos enviado un correo de confirmación con los detalles de tu cita.
</p>
<div className="space-y-2 text-sm" style={{ color: 'var(--charcoal-brown)', opacity: 0.7 }}>
<p>Fecha: {format(new Date(bookingDetails.date), 'PPP', { locale: es })}</p>
<p>Hora: {bookingDetails.time}</p>
<p>Puedes agregar esta cita a tu calendario.</p>
</div>
<Button
onClick={() => window.location.href = '/'}
className="w-full"
>
Volver al Inicio
</Button>
</CardContent>
</Card>
</div>
</div>
)
}
if (!bookingDetails) {
return (
<div className="min-h-screen bg-[var(--bone-white)] pt-24 flex items-center justify-center">
<div className="text-center">
<p>Cargando detalles de la reserva...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-[var(--bone-white)] pt-24">
<div className="max-w-4xl mx-auto px-8 py-16">
<header className="mb-12">
<Button
variant="outline"
onClick={() => window.location.href = '/booking/servicios'}
>
Volver
</Button>
</header>
<div className="grid md:grid-cols-2 gap-8">
<Card className="border-none" style={{ background: 'var(--soft-cream)' }}>
<CardHeader>
<CardTitle style={{ color: 'var(--charcoal-brown)' }}>
Resumen de la Cita
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-start gap-3">
<Calendar className="w-5 h-5 mt-1" style={{ color: 'var(--mocha-taupe)' }} />
<div>
<p className="font-medium" style={{ color: 'var(--charcoal-brown)' }}>Fecha</p>
<p style={{ color: 'var(--charcoal-brown)', opacity: 0.8 }}>
{format(new Date(bookingDetails.date), 'PPP', { locale: es })}
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Clock className="w-5 h-5 mt-1" style={{ color: 'var(--mocha-taupe)' }} />
<div>
<p className="font-medium" style={{ color: 'var(--charcoal-brown)' }}>Hora</p>
<p style={{ color: 'var(--charcoal-brown)', opacity: 0.8 }}>
{bookingDetails.time}
</p>
</div>
</div>
<div className="flex items-start gap-3">
<MapPin className="w-5 h-5 mt-1" style={{ color: 'var(--mocha-taupe)' }} />
<div>
<p className="font-medium" style={{ color: 'var(--charcoal-brown)' }}>Ubicación</p>
<p style={{ color: 'var(--charcoal-brown)', opacity: 0.8 }}>
Anchor:23 - Saltillo
</p>
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="border-none" style={{ background: 'var(--soft-cream)' }}>
<CardHeader>
<CardTitle style={{ color: 'var(--charcoal-brown)' }}>
Tus Datos
</CardTitle>
<CardDescription>
Ingresa tus datos personales para completar la reserva
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<Label htmlFor="nombre">Nombre Completo *</Label>
<Input
id="nombre"
name="nombre"
value={formData.nombre}
onChange={handleChange}
required
placeholder="Ej. María García"
className="w-full"
style={{ borderColor: 'var(--mocha-taupe)' }}
/>
</div>
<div>
<Label htmlFor="email">Email *</Label>
<Input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
required
placeholder="tu@email.com"
className="w-full"
style={{ borderColor: 'var(--mocha-taupe)' }}
/>
</div>
<div>
<Label htmlFor="telefono">Teléfono *</Label>
<Input
id="telefono"
name="telefono"
type="tel"
value={formData.telefono}
onChange={handleChange}
required
placeholder="+52 844 123 4567"
className="w-full"
style={{ borderColor: 'var(--mocha-taupe)' }}
/>
</div>
<div>
<Label htmlFor="notas">Notas (opcional)</Label>
<textarea
id="notas"
name="notas"
value={formData.notas}
onChange={handleChange}
rows={4}
placeholder="Alguna observación o preferencia..."
className="w-full px-4 py-3 border rounded-lg resize-none"
style={{ borderColor: 'var(--mocha-taupe)' }}
/>
</div>
<Button
type="submit"
disabled={loading}
className="w-full"
>
{loading ? 'Procesando...' : 'Confirmar Reserva'}
</Button>
</form>
<div className="mt-6 p-4 rounded-lg" style={{ background: 'var(--bone-white)' }}>
<p className="text-sm" style={{ color: 'var(--charcoal-brown)', opacity: 0.7 }}>
* Al confirmar tu reserva, recibirás un correo de confirmación
con los detalles. La reserva se mantendrá por 30 minutos.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,251 @@
'use client'
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Calendar, Clock, User } from 'lucide-react'
import { format } from 'date-fns'
import { es } from 'date-fns/locale'
interface Service {
id: string
name: string
category: string
duration_minutes: number
base_price: number
}
interface Location {
id: string
name: string
timezone: string
}
export default function ServiciosPage() {
const [services, setServices] = useState<Service[]>([])
const [locations, setLocations] = useState<Location[]>([])
const [selectedService, setSelectedService] = useState<string>('')
const [selectedLocation, setSelectedLocation] = useState<string>('')
const [selectedDate, setSelectedDate] = useState<string>(format(new Date(), 'yyyy-MM-dd'))
const [timeSlots, setTimeSlots] = useState<any[]>([])
const [selectedTime, setSelectedTime] = useState<string>('')
const [loading, setLoading] = useState(false)
useEffect(() => {
fetchServices()
fetchLocations()
}, [])
useEffect(() => {
if (selectedService && selectedLocation && selectedDate) {
fetchTimeSlots()
}
}, [selectedService, selectedLocation, selectedDate])
const fetchServices = async () => {
try {
const response = await fetch('/api/services')
const data = await response.json()
if (data.services) {
setServices(data.services)
}
} catch (error) {
console.error('Error fetching services:', error)
}
}
const fetchLocations = async () => {
try {
const response = await fetch('/api/locations')
const data = await response.json()
if (data.locations) {
setLocations(data.locations)
if (data.locations.length > 0) {
setSelectedLocation(data.locations[0].id)
}
}
} catch (error) {
console.error('Error fetching locations:', error)
}
}
const fetchTimeSlots = async () => {
if (!selectedService || !selectedLocation || !selectedDate) return
setLoading(true)
try {
const response = await fetch(
`/api/availability/time-slots?location_id=${selectedLocation}&service_id=${selectedService}&date=${selectedDate}`
)
const data = await response.json()
if (data.availability) {
setTimeSlots(data.availability)
}
} catch (error) {
console.error('Error fetching time slots:', error)
} finally {
setLoading(false)
}
}
const handleContinue = () => {
if (selectedService && selectedLocation && selectedDate && selectedTime) {
const params = new URLSearchParams({
service_id: selectedService,
location_id: selectedLocation,
date: selectedDate,
time: selectedTime
})
window.location.href = `/cita?${params.toString()}`
}
}
const selectedServiceData = services.find(s => s.id === selectedService)
return (
<div className="min-h-screen bg-[var(--bone-white)] pt-24">
<div className="max-w-4xl mx-auto px-8 py-16">
<header className="mb-12">
<h1 className="text-5xl mb-4" style={{ color: 'var(--charcoal-brown)' }}>
Reservar Cita
</h1>
<p className="text-xl opacity-80" style={{ color: 'var(--charcoal-brown)' }}>
Selecciona el servicio y horario de tu preferencia
</p>
</header>
<div className="space-y-8">
<Card className="border-none" style={{ background: 'var(--soft-cream)' }}>
<CardHeader>
<CardTitle className="flex items-center gap-2" style={{ color: 'var(--charcoal-brown)' }}>
<User className="w-5 h-5" />
Servicios
</CardTitle>
<CardDescription>Selecciona el servicio que deseas reservar</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label>Servicio</Label>
<Select onValueChange={setSelectedService} value={selectedService}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona un servicio" />
</SelectTrigger>
<SelectContent>
{services.map((service) => (
<SelectItem key={service.id} value={service.id}>
{service.name} - ${service.base_price} ({service.duration_minutes} min)
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
<Card className="border-none" style={{ background: 'var(--soft-cream)' }}>
<CardHeader>
<CardTitle className="flex items-center gap-2" style={{ color: 'var(--charcoal-brown)' }}>
<Clock className="w-5 h-5" />
Fecha y Hora
</CardTitle>
<CardDescription>Selecciona la fecha y hora disponible</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label>Ubicación</Label>
<Select onValueChange={setSelectedLocation} value={selectedLocation}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona ubicación" />
</SelectTrigger>
<SelectContent>
{locations.map((location) => (
<SelectItem key={location.id} value={location.id}>
{location.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Fecha</Label>
<input
type="date"
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
min={format(new Date(), 'yyyy-MM-dd')}
className="w-full px-4 py-3 border rounded-lg"
style={{ borderColor: 'var(--mocha-taupe)' }}
/>
</div>
{selectedServiceData && (
<div>
<Label>Duración del servicio: {selectedServiceData.duration_minutes} minutos</Label>
</div>
)}
{loading ? (
<div className="text-center py-4">
Cargando horarios...
</div>
) : (
<div>
<Label>Horarios disponibles</Label>
{timeSlots.length === 0 ? (
<p className="text-sm opacity-70 mt-2">
No hay horarios disponibles para esta fecha. Selecciona otra fecha.
</p>
) : (
<div className="grid grid-cols-3 gap-2 mt-2">
{timeSlots.map((slot, index) => (
<Button
key={index}
variant={selectedTime === slot.start_time ? 'default' : 'outline'}
onClick={() => setSelectedTime(slot.start_time)}
className={selectedTime === slot.start_time ? 'w-full' : ''}
>
{format(new Date(slot.start_time), 'HH:mm', { locale: es })}
</Button>
))}
</div>
)}
</div>
)}
</div>
</CardContent>
</Card>
{selectedServiceData && selectedTime && (
<Card className="border-none" style={{ background: 'var(--deep-earth)' }}>
<CardContent className="pt-6">
<div className="text-white">
<p className="text-lg font-semibold mb-2">Resumen de la reserva</p>
<div className="space-y-1 opacity-90">
<p>Servicio: {selectedServiceData.name}</p>
<p>Fecha: {format(new Date(selectedDate), 'PPP', { locale: es })}</p>
<p>Hora: {format(new Date(selectedTime), 'HH:mm', { locale: es })}</p>
<p>Precio: ${selectedServiceData.base_price.toFixed(2)}</p>
</div>
</div>
</CardContent>
</Card>
)}
<Button
onClick={handleContinue}
disabled={!selectedService || !selectedLocation || !selectedDate || !selectedTime}
className="w-full"
>
Continuar con Datos del Cliente
</Button>
</div>
</div>
</div>
)
}