docs: Create Aperture technical specifications document

TASK 4.1: Create technical specifications document - COMPLETED
- Create docs/APERTURE_SPECS.md with complete technical specifications:
  - Response to Question 9: Hours worked (automatic from bookings)
  - Complete POS structure with multiple cashiers
  - Granular permissions system documentation
- Includes:
  - Hours worked calculation logic (automatic vs manual)
  - POS architecture (6 payment methods, receipt options)
  - Multiple cashiers system with individual tracking
  - Financial management (expenses, profit margin)
  - Database schemas for POS, cashiers, expenses
  - API endpoints for POS operations

Specifications Documented:
- Hours worked: Automatic from bookings (scheduled vs actual duration)
- Time adjustments: Manual updates allowed by staff
- Payroll: Base salary + service commissions + product commissions + tips
- POS payment methods: Cash, Transfer, Membership, Card, Giftcard, PIA
- Receipts: Email or client dashboard only (no physical printing)
- Cashiers: Individual tracking with movement logs for error resolution
- Dynamic pricing: Configurable by service, both channels (booking + POS)
- Giftcards: Purchaseable, redeemable, balance tracking
- PIA (Paid in Advance): Apply previously paid deposits
- Recurring expenses: Daily, weekly, monthly, yearly frequencies

Database Schemas:
- staff_time_tracking (NEW) - Track scheduled vs actual duration
- pos_sales (NEW) - All POS transactions
- giftcards (NEW) - Giftcard management
- daily_cash_close (NEW) - Individual cashier closing
- expenses (NEW) - Financial expense tracking

API Endpoints:
- POST /api/aperture/pos/sales
- GET /api/aperture/pos/daily-summary
- POST /api/aperture/pos/open-cash-register
- POST /api/aperture/pos/close-cash-register
- GET /api/aperture/pos/active-cash-registers
- POST /api/aperture/finance/expenses
- GET /api/aperture/finance/report

Impact:
- Complete technical foundation for POS implementation
- Clear data model for hours worked calculation
- Granular permissions architecture defined
- Multiple cashiers system fully specified

Files Created:
- docs/APERTURE_SPECS.md

Next: Task 2 - Document POS structure and multiple cashiers
This commit is contained in:
Marco Gallegos
2026-01-17 10:55:05 -06:00
parent 137cbfdf74
commit 197f07df7f

596
docs/APERTURE_SPECS.md Normal file
View File

@@ -0,0 +1,596 @@
# Aperture Technical Specifications
**Especificaciones técnicas completas para Aperture (HQ Dashboard)**
**Última actualización: Enero 2026**
---
## 1. Objetivo
Este documento define las especificaciones técnicas para el desarrollo de Aperture (aperture.anchor23.mx), el dashboard administrativo y CRM interno de AnchorOS.
---
## 2. Stack Tecnológico
### Frontend
- **Framework**: Next.js 14 (App Router)
- **UI Library**: Radix UI (componentes accesibles preconstruidos)
- **Estilizado**: Tailwind CSS + Square UI custom styling
- **Icons**: Lucide React (24px, stroke 2px)
- **Charts**: Recharts o similar (para gráficos de rendimiento)
- **PDF Generation**: PDFKit o similar (para cierre de caja)
### Backend
- **Database**: Supabase (PostgreSQL + RLS)
- **Auth**: Supabase Auth (magic links para clientes, password para staff/admin)
- **API**: Next.js App Router API routes
### Integraciones
- **Payments**: Stripe SDK
- **Calendar**: Google Calendar API v3 (Service Account)
- **Notifications**: WhatsApp API (Twilio / Meta) - Good to have, no priority
---
## 3. Horas Trabajadas - Respuesta a Pregunta 9
### Calculo de Horas Trabajadas
**Enfoque**: El sistema de nómina calcula el **tiempo efectivo de trabajo** basado en la comparación entre **tiempo programado** y **tiempo real utilizado**.
#### Lógica de Cálculo
1. **Tiempo Programado**:
- Basado en la duración de los servicios agendados
- Calculado desde `bookings.start_time_utc` hasta `bookings.end_time_utc`
- Excluye tiempos de espera entre citas
2. **Tiempo Real Utilizado**:
- Actualizado manualmente por el staff después de completar una cita
- Se almacena en tabla de control (opcional: `staff_time_tracking`)
- Permite ajustes por diferencias en duración real
#### Campos de Base de Datos
**Tabla `bookings` (ya existe):**
```sql
- id (UUID)
- staff_id (UUID)
- service_id (UUID)
- start_time_utc (TIMESTAMPTZ)
- end_time_utc (TIMESTAMPTZ)
- scheduled_duration_minutes (INTEGER) - Calculado automáticamente
- actual_duration_minutes (INTEGER) - Actualizado manualmente por staff
- time_difference_minutes (INTEGER) - Diferencia calculada
- status (TEXT) - 'confirmed', 'pending', 'in_progress', 'completed', 'no_show'
```
**Nueva tabla sugerida: `staff_time_tracking`**
```sql
CREATE TABLE staff_time_tracking (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
booking_id UUID REFERENCES bookings(id) ON DELETE CASCADE,
staff_id UUID REFERENCES staff(id) ON DELETE CASCADE,
scheduled_duration_minutes INTEGER NOT NULL,
actual_duration_minutes INTEGER NOT NULL,
time_difference_minutes INTEGER NOT NULL,
notes TEXT,
created_by UUID REFERENCES staff(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_staff_time_tracking_staff_date ON staff_time_tracking(staff_id, created_at);
```
#### Algoritmo de Cálculo
```sql
-- Duración programada (automática)
UPDATE bookings
SET scheduled_duration_minutes = EXTRACT(EPOCH FROM (end_time_utc - start_time_utc)) / 60
WHERE scheduled_duration_minutes IS NULL;
-- Diferencia de tiempo (automática)
UPDATE bookings
SET time_difference_minutes = actual_duration_minutes - scheduled_duration_minutes
WHERE status = 'completed' AND actual_duration_minutes IS NOT NULL;
```
#### Cálculo de Nómina
**Horas Totales por Periodo:**
```sql
SELECT
s.id,
s.display_name,
SUM(b.scheduled_duration_minutes) / 60 AS scheduled_hours,
SUM(b.actual_duration_minutes) / 60 AS actual_hours,
COUNT(b.id) AS total_bookings,
SUM(CASE WHEN b.time_difference_minutes > 0 THEN b.time_difference_minutes ELSE 0 END) / 60 AS extra_hours
FROM staff s
LEFT JOIN bookings b ON b.staff_id = s.id
WHERE b.status = 'completed'
AND b.start_time_utc >= $1::TIMESTAMPTZ
AND b.start_time_utc < $2::TIMESTAMPTZ
GROUP BY s.id, s.display_name;
```
#### Reglas de Negocio
1. **Tiempo programado**: Base para el cálculo de nómina
2. **Tiempo real**: Ajustes permitidos por staff (por ex: cliente llegó tarde, servicio se extendió)
3. **Tiempo extra**: Se paga al 100% si fue trabajo adicional
4. **Tiempo faltante**: Se descuenta del pago (horarios no cubiertos por citas)
5. **Tiempo no-productivo**: No se paga (esperas, preparación post-cita)
---
## 4. Estructura del Sistema de POS (Punto de Venta)
### 4.1 Arquitectura del POS
#### Componentes Principales
**1. Service Selector**
- Grid de categorías: Servicios, Productos de venta, Membresías, Giftcards
- Búsqueda fonética de productos/servicios
- Filtros por tipo y categoría
**2. Customer Selection**
- Buscador de clientes (email/teléfono)
- Selección de cliente existente o registro de nuevo
- Display de tier y saldo de créditos/membresía
**3. Payment Processor**
- Métodos de pago disponibles:
- Efectivo
- Transferencia
- Membership (créditos de membresía)
- Tarjeta (Stripe terminal)
- Giftcard (código canjeable)
- PIA (Paid in Advance - depósito ya pagado)
- Cálculo automático de cambio
**4. Receipt Options**
- NO imprimir recibos físicos
- Enviar por email (SendGrid, AWS SES, o similar)
- Guardar en dashboard del cliente
**5. Transaction History**
- Historial de transacciones del día
- Filtros por método de pago y cajero
### 4.2 Opciones de Pago
#### 1. Efectivo
- Registro manual del monto recibido
- No requiere integración externa
#### 2. Transferencia
- Referencia bancaria del cliente
- Comprobante de transferencia
- Estado: "pendiente" hasta confirmación
#### 3. Membership (Créditos de Membresía)
- Verificar saldo disponible
- Deducir créditos automáticamente
- Restringir a clientes con membresía activa
#### 4. Tarjeta (Stripe Terminal)
- Integración con Stripe SDK para terminales físicas
- Procesamiento de pago en tiempo real
- Confirmación de transacción
#### 5. Giftcard
- Validar código de giftcard
- Verificar saldo y estado (activo/inactivo/expirado)
- Deducir saldo del giftcard
#### 6. PIA (Paid in Advance)
- Verificar depósito previamente pagado
- Aplicar al saldo total de la transacción
- No requiere pago adicional
### 4.3 Campos de Base de Datos
**Nueva tabla: `pos_sales`**
```sql
CREATE TABLE pos_sales (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
location_id UUID REFERENCES locations(id) ON DELETE CASCADE,
staff_id UUID REFERENCES staff(id) ON DELETE CASCADE,
customer_id UUID REFERENCES customers(id) ON DELETE SET NULL,
-- Payment details
payment_method TEXT NOT NULL CHECK (payment_method IN ('cash', 'transfer', 'membership', 'card', 'giftcard', 'pia')),
payment_amount DECIMAL(10, 2) NOT NULL,
payment_reference TEXT,
payment_status TEXT NOT NULL DEFAULT 'completed' CHECK (payment_status IN ('pending', 'completed', 'failed')),
-- Transaction details
total_amount DECIMAL(10, 2) NOT NULL,
discount_amount DECIMAL(10, 2) DEFAULT 0,
tax_amount DECIMAL(10, 2) DEFAULT 0,
tip_amount DECIMAL(10, 2) DEFAULT 0,
-- Items sold
items JSONB NOT NULL,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_pos_sales_location_date ON pos_sales(location_id, created_at);
CREATE INDEX idx_pos_sales_staff_date ON pos_sales(staff_id, created_at);
```
**Formato de `items` JSONB:**
```json
{
"services": [
{
"service_id": "uuid",
"service_name": "Manicure",
"quantity": 1,
"unit_price": 150.00,
"total": 150.00
}
],
"products": [
{
"product_id": "uuid",
"product_name": "Cuticle Remover",
"quantity": 2,
"unit_price": 45.00,
"total": 90.00
}
],
"memberships": [
{
"membership_id": "uuid",
"membership_name": "VIP Monthly",
"quantity": 1,
"unit_price": 500.00,
"total": 500.00
}
]
}
```
**Nueva tabla: `giftcards`**
```sql
CREATE TABLE giftcards (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code TEXT UNIQUE NOT NULL,
initial_balance DECIMAL(10, 2) NOT NULL,
current_balance DECIMAL(10, 2) NOT NULL,
purchased_by UUID REFERENCES customers(id) ON DELETE SET NULL,
purchased_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_giftcards_code ON giftcards(code);
```
### 4.4 API Endpoints
**POS Sales:**
```typescript
POST /api/aperture/pos/sales
Body: {
customer_id: UUID | null,
items: {
services: Array<{ service_id, quantity }>,
products: Array<{ product_id, quantity }>,
memberships: Array<{ membership_id, quantity }>
},
payment_method: 'cash' | 'transfer' | 'membership' | 'card' | 'giftcard' | 'pia',
payment_amount: number,
tip_amount?: number,
giftcard_code?: string
}
Response: { success, sale_id, items, total_amount, change }
```
**Daily Summary:**
```typescript
GET /api/aperture/pos/daily-summary?date=YYYY-MM-DD&location_id=UUID
Response: {
success: true,
summary: {
total_sales,
by_payment_method: { cash, transfer, membership, card, giftcard, pia },
transactions_count
}
}
```
---
## 5. Sistema de Múltiples Cajeros
### 5.1 Arquitectura
Cada cajero tiene su propia sesión y cierre de caja independiente. El sistema permite:
- Múltiples cajeros trabajando simultáneamente
- Control individual de transacciones por cajero
- Rastreo de errores en cobros por usuario específico
- Cierre de caja individual por cajero
### 5.2 Campos de Base de Datos
**Nueva tabla: `daily_cash_close`**
```sql
CREATE TABLE daily_cash_close (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
location_id UUID REFERENCES locations(id) ON DELETE CASCADE,
cashier_id UUID REFERENCES staff(id) ON DELETE CASCADE,
-- Cash balance tracking
opening_balance DECIMAL(10, 2) NOT NULL DEFAULT 0,
cash_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
cash_refunds DECIMAL(10, 2) NOT NULL DEFAULT 0,
closing_balance DECIMAL(10, 2) NOT NULL DEFAULT 0,
cash_difference DECIMAL(10, 2) NOT NULL DEFAULT 0,
-- Transaction summary
total_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
card_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
transfer_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
membership_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
giftcard_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
pia_sales DECIMAL(10, 2) NOT NULL DEFAULT 0,
-- Timestamps
date DATE NOT NULL,
open_at TIMESTAMPTZ NOT NULL,
closed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
closed_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_daily_cash_close_location_date ON daily_cash_close(location_id, date);
CREATE INDEX idx_daily_cash_close_cashier_date ON daily_cash_close(cashier_id, date);
CREATE UNIQUE INDEX idx_daily_cash_close_unique ON daily_cash_close(location_id, cashier_id, date);
```
### 5.3 Flujo de Cierre de Caja
#### 1. Apertura de Caja
- Cajero registra monto de efectivo inicial (`opening_balance`)
- Sistema crea registro de apertura (`open_at` timestamp)
#### 2. Registro de Ventas
- Todas las transacciones se asocian al `cashier_id` activo
- Sistema calcula totales por método de pago en tiempo real
#### 3. Cierre de Caja
- Cajero cierra el día:
1. Conta efectivo en caja
2. Ingresa `closing_balance` real
3. Sistema calcula `cash_difference`
4. Genera reporte PDF automático
5. Envía reporte al dueño por email
#### 4. Rastreo de Errores
- Si `cash_difference` ≠ 0:
- Sistema marca discrepancia
- Asocia la transacción específica al cajero
- Permite investigación del error con el usuario correcto
### 5.4 API Endpoints
**Open Cash Register:**
```typescript
POST /api/aperture/pos/open-cash-register
Body: {
opening_balance: number
}
Response: { success, cash_register_id, open_at }
```
**Close Cash Register:**
```typescript
POST /api/aperture/pos/close-cash-register
Body: {
closing_balance: number,
notes?: string
}
Response: {
success: true,
summary: { total_sales, cash_difference, transactions_count },
pdf_report_url
}
```
**Get Active Cash Registers:**
```typescript
GET /api/aperture/pos/active-cash-registers?location_id=UUID
Response: {
success: true,
registers: [
{
id,
cashier_id,
cashier_name,
opening_balance,
current_balance,
open_at,
location_name
}
]
}
```
---
## 6. Sistema de Finanzas
### 6.1 Campos de Base de Datos
**Nueva tabla: `expenses`**
```sql
CREATE TABLE expenses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
location_id UUID REFERENCES locations(id) ON DELETE CASCADE,
category TEXT NOT NULL,
description TEXT,
amount DECIMAL(10, 2) NOT NULL,
expense_date DATE NOT NULL,
-- Recurring expenses
is_recurring BOOLEAN DEFAULT false,
recurring_frequency TEXT CHECK (recurring_frequency IN ('daily', 'weekly', 'monthly', 'yearly')),
recurring_end_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_expenses_location_date ON expenses(location_id, expense_date);
CREATE INDEX idx_expenses_category ON expenses(category);
```
### 6.2 Categorías de Gastos
- **Renta**: Alquiler del local
- **Insumos**: Productos para servicios (cuticles, esmaltes, etc.)
- **Servicios**: Servicios externos contratados
- **Personal**: Pagos de nómina (si se maneja por cash)
- **Marketing**: Publicidad y promociones
- **Utilidades**: Electricidad, agua, internet
- **Otros**: Cualquier otro gasto
### 6.3 API Endpoints
**Create Expense:**
```typescript
POST /api/aperture/finance/expenses
Body: {
category,
description,
amount,
expense_date,
is_recurring?: boolean,
recurring_frequency?: 'daily' | 'weekly' | 'monthly' | 'yearly',
recurring_end_date?: string
}
Response: { success, expense_id }
```
**Get Financial Report:**
```typescript
GET /api/aperture/finance/report?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&location_id=UUID
Response: {
success: true,
report: {
total_revenue,
total_expenses,
net_margin,
expenses_by_category,
profit_margin_percentage
}
}
```
---
## 7. Convenciones de Código
### TypeScript
- **Strict mode**: Habilitado
- **Interfaces**: Definir tipos para todas las respuestas de API
- **Enums**: Usar enums para constantes (status, roles, métodos de pago)
### Naming Conventions
- **Componentes**: PascalCase (ej: `StatsCard`, `BookingCard`)
- **Funciones**: camelCase (ej: `fetchBookings`, `calculatePayroll`)
- **Variables**: camelCase (ej: `totalSales`, `staffId`)
- **Constantes**: UPPER_SNAKE_CASE (ej: `API_URL`, `DEFAULT_TIMEOUT`)
### SQL
- **Table names**: snake_case (ej: `daily_cash_close`, `pos_sales`)
- **Column names**: snake_case (ej: `opening_balance`, `cash_difference`)
- **Functions**: snake_case (ej: `calculate_weekly_invitations_reset`)
---
## 8. Consideraciones de Seguridad
1. **Row Level Security (RLS)**:
- Todas las tablas sensibles deben tener políticas RLS
- Solo roles apropiados pueden acceder a datos financieros
- Audit logging completo de todas las acciones
2. **Validaciones**:
- Validar todos los inputs de usuario
- Verificar permisos antes de permitir acciones
- Validar montos de pagos (rangos aceptables)
3. **Auditoría**:
- Todas las acciones financieras deben registrarse en `audit_logs`
- Incluir: acción, usuario, timestamp, detalles
---
## 9. Checklist de Implementación
### Fase 0: Documentación y Configuración ✅
- [x] Crear documento de especificaciones técnicas
- [x] Documentar cálculo de horas trabajadas
- [x] Definir estructura de POS completa
- [x] Documentar sistema de múltiples cajeros
### Fase 1-7: Pendiente
- [ ] Instalar Radix UI
- [ ] Crear componentes base Square UI
- [ ] Implementar Dashboard Home
- [ ] Implementar Calendario Maestro
- [ ] Implementar Staff & Nómina
- [ ] Implementar Clientes & Fidelización
- [ ] Implementar POS
- [ ] Implementar Finanzas
- [ ] Implementar Marketing & Configuración
- [ ] Testing completo
---
## 10. Documentos Relacionados
- [TASKS.md](../TASKS.md) - Plan de ejecución por fases
- [APERTURE_SQUARE_UI.md](./APERTURE_SQUARE_UI.md) - Guía de estilo Square UI
- [DESIGN_SYSTEM.md](./DESIGN_SYSTEM.md) - Sistema de diseño completo
- [API.md](./API.md) - Documentación de APIs y endpoints
- [PRD.md](./PRD.md) - Documento maestro de producto
---
## 11. Notas Importantes
1. **Precios Inteligentes**:
- Configurables por servicio
- Aplican a ambos canales (booking + POS)
- Solo activables en temporada alta (backend toggle)
2. **Sin Impresión de Recibos**:
- Email a cliente
- Dashboard del cliente
- Reporte PDF al dueño (cierre de caja)
3. **Múltiples Cajeros**:
- Cada cajero con su propio cierre de caja
- Rastreo de errores por usuario específico
- Control de movimientos para investigación
4. **Horas Trabajadas**:
- Automático desde bookings (tiempo programado)
- Actualización manual de tiempo real por staff
- Cálculo de diferencias
- Nómina basada en horas reales trabajadas