Compare commits

6 Commits

Author SHA1 Message Date
Marco Gallegos
b0ea5548ef docs: Complete HIGH priority documentation tasks
TASK 4.3: Document granular permissions system - COMPLETED
- Add Section 7: Granular Permissions System to APERTURE_SPECS.md
- Define flexible permission system for any user
- Only admin can assign permissions (independent of user roles)
- 8 permission categories with 30+ individual permissions
- Database schema: user_permissions table with RLS
- API endpoints for checking and assigning permissions
- Helper functions: hasPermission(), hasPermissions(), isAdmin()
- UI components: PermissionChecker, MultiPermissionChecker
- Admin panel for permission management

TASK 4.5: Update APERTURE_SQUARE_UI.md with Radix UI - COMPLETED
- Add Section 3: Radix UI Components Used
- Document all Radix UI packages installed
- Radix components styled with Square UI tokens
- Section 5: Square UI styling examples for each component
- Section 6: Accessibility guidelines (Radix UI support)
- Code examples for Button, Dialog, Tooltip with Square UI
- Complete dashboard page example with Radix UI components

TASK 4.2: Update globals.css with complete variables - COMPLETED
- Add all Square UI color variables to :root
- Add UI component shadows (sm, md, lg, xl)
- Add UI border colors
- Add UI text colors (primary, secondary, tertiary)
- Add UI state colors (success, warning, error, info)
- Add UI radiuses (sm, md, lg, xl, 2xl, full)
- Add UI text sizes (xs, sm, base, lg, xl, 2xl, 3xl, 4xl, 5xl)
- Maintain backward compatibility with anchor23.mx colors

Impact:
- Complete Square UI design system foundation
- All color variables centralized in globals.css
- Ready for Radix UI integration
- Supports both anchor23.mx and Aperture styling

Files Modified:
- docs/APERTURE_SPECS.md (added granular permissions section)
- docs/APERTURE_SQUARE_UI.md (added Radix UI documentation)
- app/globals.css (added complete Square UI variables)

Next: FASE 1 - Componentes Base con Radix UI
2026-01-17 11:01:49 -06:00
Marco Gallegos
71e8c9af0f docs: Update APERTURE_SQUARE_UI.md with Radix UI
TASK 4: Update APERTURE_SQUARE_UI.md with Radix UI - COMPLETED
- Add Section 3: Radix UI Components Used
- Document installed Radix UI packages:
  - @radix-ui/react-button
  - @radix-ui/react-select
  - @radix-ui/react-tabs
  - @radix-ui/react-dropdown-menu
  - @radix-ui/react-dialog
  - @radix-ui/react-tooltip
  @radix-ui/react-label
  @radix-ui/react-switch
  - @radix-ui/react-checkbox

- Document Radix UI styling with Square UI tokens:
  - Button variants: primary, secondary, ghost, danger, success, warning
  - Select: dropdown with Square UI colors
  - Tabs: active indicator, colors for states
  - Dialog: Square UI background, border, radius, shadow
  - Tooltip: Square UI styling with proper spacing
- Document Custom Components:
  - Card, Avatar, Table, Badge (no Radix UI, custom implementation)
- Add Section 5: Code Conventions
  - Examples of Radix UI with Square UI styling
- Component composition examples (Button, Dialog, Tooltip)
- Add Section 6: Example Page with Radix UI
- Complete dashboard page example with tabs, cards, badges
- Add Section 7: Accessibility Guidelines
  - Priority A: Keyboard Navigation (Radix UI support)
- Priority B: ARIA Attributes
- Priority C: Focus Management

Impact:
- Clear Radix UI integration guide for developers
- Square UI styling patterns documented
- Accessibility standards defined
- Complete examples for common use cases

Files Modified:
- docs/APERTURE_SQUARE_UI.md

Next: Task 5 - Update globals.css with complete variables
2026-01-17 11:00:41 -06:00
Marco Gallegos
51dc8f607e docs: Add granular permissions system to Aperture specs
TASK 4.2: Document granular permissions system - COMPLETED
- Add Section 7: Granular Permissions System to APERTURE_SPECS.md
- Defines flexible permission system allowing granular permission assignment to ANY user
- Only users with admin role can assign permissions
- Permissions are independent of user roles (not inherited)

Key Features:
- User-based permissions (not role-based)
- Admin-only permission assignment
- Audit logging of permission changes
- Reusable UI components for permission checking

Permissions Categories Documented:
1. Dashboard & Stats (8 permissions)
2. Calendar & Bookings (6 permissions)
3. Staff Management (10 permissions)
4. Client Management (11 permissions)
5. POS & Sales (8 permissions)
6. Finance (6 permissions)
7. Marketing (9 permissions)
8. Configuration (4 permissions)

Database Schema Added:
- user_permissions table
- Supports user_id, permission_key, granted, granted_by, granted_at
- Unique constraint on (user_id, permission_key)
- Check constraint to verify user exists in auth.users

API Endpoints:
- GET /api/aperture/permissions/check - Check single permission
- GET /api/aperture/permissions/user - Get user permissions
- POST /api/aperture/permissions/assign - Assign permissions (admin only)
- GET /api/aperture/permissions/list - Get all available permissions

Helper Functions Documented:
- hasPermission(user_id, permission_key) - Check single permission
- hasPermissions(user_id, permission_keys) - Check multiple permissions
- isAdmin(user_id) - Check if user is admin role

UI Components Documented:
- PermissionChecker - Single permission check with fallback
- MultiPermissionChecker - Multiple permissions check (all/any mode)
- Usage examples for Staff, POS, Dashboard pages

Security Considerations:
- Row Level Security (RLS) for all sensitive tables
- Only admin can assign permissions
- All financial actions must be audited
- Validation before allowing actions

Files Modified:
- docs/APERTURE_SPECS.md

Next: Task 4 - Update APERTURE_SQUARE_UI.md with Radix UI
2026-01-17 10:58:02 -06:00
Marco Gallegos
197f07df7f 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
2026-01-17 10:55:05 -06:00
Marco Gallegos
137cbfdf74 feat(critical): Implement weekly invitations reset
TASK 3: Implement weekly invitations reset - COMPLETED
- Create Edge Function: app/api/cron/reset-invitations/route.ts
- Resets weekly_invitations_used to 0 for all Gold tier customers
- Runs automatically every Monday 00:00 UTC
- Logs action to audit_logs table
- Authentication required (CRON_SECRET)

Configuration Required:
- Add CRON_SECRET to environment variables (.env.local)
- Configure Vercel Cron Job or similar for automatic execution:
  ```bash
  curl -X GET "https://aperture.anchor23.mx/api/cron/reset-invitations" \
    -H "Authorization: Bearer YOUR_CRON_SECRET"
  ```

Impact:
- Gold tier memberships now work correctly
- Weekly invitation quotas are automatically reset
- All actions are audited in audit_logs

Files Created:
- app/api/cron/reset-invitations/route.ts

Files Modified:
- TASKS.md (marked task 3 as completed)
- package-lock.json (updated dependency)

Next: Priority HIGH tasks (documentation & design)
2026-01-17 10:52:16 -06:00
Marco Gallegos
e33a9a4573 feat(critical): Implement critical Aperture features
TASK 1: Implement GET /api/aperture/stats
- Create endpoint at app/api/aperture/stats/route.ts
- Returns dashboard statistics: { totalBookings, totalRevenue, completedToday, upcomingToday }
- Calculates stats from bookings table by month and today
- Dashboard now has functional statistics display

TASK 2: Implement authentication for Aperture
- Create middleware.ts for protecting Aperture routes
- Only allows access to users with admin, manager, or staff roles
- Redirects unauthorized users to /aperture/login
- Uses Supabase Auth with session verification
- Integrates with existing AuthProvider in lib/auth/context.tsx

Stack Updates:
- Update @supabase/auth-helpers-nextjs to latest version (0.15.0)
- Note: Package marked as deprecated but still functional

Files Created:
- app/api/aperture/stats/route.ts
- middleware.ts

Files Modified:
- TASKS.md (marked tasks 1 and 2 as completed)
- package.json (updated dependency)

Impact:
- Aperture dashboard now has working statistics
- Aperture routes are now protected by authentication
- Only authorized staff/admin/manager can access dashboard

Next: Task 3 - Implement weekly invitation reset
2026-01-17 10:48:40 -06:00
9 changed files with 1503 additions and 55 deletions

View File

@@ -519,24 +519,33 @@ Validación Staff (rol Staff):
### 🔴 CRÍTICO - Bloquea Funcionamiento (Timeline: 1-2 días)
1. **Implementar `GET /api/aperture/stats`** - ~30 min
- Dashboard de Aperture espera este endpoint
- Sin esto, estadísticas no se cargan
- Respuesta esperada: `{ success: true, stats: { totalBookings, totalRevenue, completedToday, upcomingToday } }`
- Ubicación: `app/api/aperture/stats/route.ts`
1. **Implementar `GET /api/aperture/stats`** - COMPLETADO
- Dashboard de Aperture espera este endpoint
- Sin esto, estadísticas no se cargan
- Respuesta esperada: `{ success: true, stats: { totalBookings, totalRevenue, completedToday, upcomingToday } }`
- Ubicación: `app/api/aperture/stats/route.ts`
2. **Implementar autenticación para Aperture** - ~2-3 horas
- Integración con Supabase Auth para roles admin/manager/staff
- Protección de rutas de Aperture (middleware)
- Session management
- Página login ya existe en `/app/aperture/login/page.tsx`, needs Supabase Auth integration
2. **Implementar autenticación para Aperture** - COMPLETADO
- Integración con Supabase Auth para roles admin/manager/staff
- Protección de rutas de Aperture (middleware)
- Session management
- Página login ya existe en `/app/aperture/login/page.tsx`, integration completada
3. **Implementar reseteo semanal de invitaciones** - ~2-3 horas
- Script/Edge Function que se ejecuta cada Lunes 00:00 UTC
- Resetea `weekly_invitations_used` a 0 para todos los clientes Tier Gold
- Registra acción en `audit_logs`
- Documentado en TASKS.md línea 211 pero NO implementado
- Impacto: Membresías Gold no funcionan correctamente sin esto
3. **Implementar reseteo semanal de invitaciones** - COMPLETADO
- Script/Edge Function que se ejecuta cada Lunes 00:00 UTC
- Resetea `weekly_invitations_used` a 0 para todos los clientes Tier Gold
- Registra acción en `audit_logs`
- ✅ Ubicación: `app/api/cron/reset-invitations/route.ts`
- Impacto: Membresías Gold ahora funcionan correctamente
**Configuración Necesaria:**
- Agregar `CRON_SECRET` a variables de entorno (.env.local)
- Configurar Vercel Cron Job o similar para ejecución automática
- Comando de ejemplo:
```bash
curl -X GET "https://aperture.anchor23.mx/api/cron/reset-invitations" \
-H "Authorization: Bearer YOUR_CRON_SECRET"
```
### 🟡 ALTA - Documentación y Diseño (Timeline: 1 semana)

View File

@@ -0,0 +1,103 @@
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
/**
* @description Get Aperture dashboard statistics
* @returns Statistics for dashboard display
*/
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !supabaseServiceKey) {
throw new Error('Missing Supabase environment variables');
}
const supabase = createClient(supabaseUrl, supabaseServiceKey);
export async function GET() {
try {
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const todayEnd = new Date(todayStart);
todayEnd.setHours(23, 59, 59, 999);
const todayStartUTC = todayStart.toISOString();
const todayEndUTC = todayEnd.toISOString();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const monthEndUTC = monthEnd.toISOString();
const { count: totalBookings, error: bookingsError } = await supabase
.from('bookings')
.select('*', { count: 'exact', head: true })
.gte('created_at', monthStart.toISOString())
.lte('created_at', monthEndUTC);
if (bookingsError) {
console.error('Error fetching total bookings:', bookingsError);
return NextResponse.json(
{ success: false, error: 'Failed to fetch total bookings' },
{ status: 500 }
);
}
const { data: payments, error: paymentsError } = await supabase
.from('bookings')
.select('total_price')
.eq('status', 'completed')
.gte('created_at', monthStart.toISOString())
.lte('created_at', monthEndUTC);
if (paymentsError) {
console.error('Error fetching payments:', paymentsError);
return NextResponse.json(
{ success: false, error: 'Failed to fetch payments' },
{ status: 500 }
);
}
const totalRevenue = payments?.reduce((sum, booking) => sum + (booking.total_price || 0), 0) || 0;
const { count: completedToday, error: completedError } = await supabase
.from('bookings')
.select('*', { count: 'exact', head: true })
.eq('status', 'completed')
.gte('end_time_utc', todayStartUTC)
.lte('end_time_utc', todayEndUTC);
if (completedError) {
console.error('Error fetching completed today:', completedError);
}
const { count: upcomingToday, error: upcomingError } = await supabase
.from('bookings')
.select('*', { count: 'exact', head: true })
.in('status', ['confirmed', 'pending'])
.gte('start_time_utc', todayStartUTC)
.lte('start_time_utc', todayEndUTC);
if (upcomingError) {
console.error('Error fetching upcoming today:', upcomingError);
}
const stats = {
totalBookings: totalBookings || 0,
totalRevenue: totalRevenue,
completedToday: completedToday || 0,
upcomingToday: upcomingToday || 0
};
return NextResponse.json({
success: true,
stats
});
} catch (error) {
console.error('Error in /api/aperture/stats:', error);
return NextResponse.json(
{ success: false, error: 'Internal server error' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,109 @@
import { NextResponse, NextRequest } from 'next/server'
import { createClient } from '@supabase/supabase-js'
/**
* @description Weekly reset of Gold tier invitations
* @description Runs automatically every Monday 00:00 UTC
* @description Resets weekly_invitations_used to 0 for all Gold tier customers
* @description Logs action to audit_logs table
*/
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !supabaseServiceKey) {
throw new Error('Missing Supabase environment variables')
}
const supabase = createClient(supabaseUrl, supabaseServiceKey)
export async function GET(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
const cronKey = authHeader.replace('Bearer ', '').trim()
if (cronKey !== process.env.CRON_SECRET) {
return NextResponse.json(
{ success: false, error: 'Invalid cron key' },
{ status: 403 }
)
}
const { data: goldCustomers, error: fetchError } = await supabase
.from('customers')
.select('id, first_name, last_name')
.eq('tier', 'gold')
if (fetchError) {
console.error('Error fetching gold customers:', fetchError)
return NextResponse.json(
{ success: false, error: 'Failed to fetch gold customers' },
{ status: 500 }
)
}
if (!goldCustomers || goldCustomers.length === 0) {
return NextResponse.json({
success: true,
message: 'No gold customers found. Reset skipped.',
resetCount: 0
})
}
const customerIds = goldCustomers.map(c => c.id)
const { error: updateError } = await supabase
.from('customers')
.update({ weekly_invitations_used: 0 })
.in('id', customerIds)
if (updateError) {
console.error('Error resetting weekly invitations:', updateError)
return NextResponse.json(
{ success: false, error: 'Failed to reset weekly invitations' },
{ status: 500 }
)
}
const { error: logError } = await supabase
.from('audit_logs')
.insert([{
action: 'weekly_invitations_reset',
entity_type: 'customer',
entity_id: null,
details: {
customer_count: goldCustomers.length,
customer_ids: customerIds
},
performed_by: 'system',
created_at: new Date().toISOString()
}])
if (logError) {
console.error('Error logging reset action:', logError)
}
console.log(`Weekly invitations reset completed for ${goldCustomers.length} gold customers`)
return NextResponse.json({
success: true,
message: 'Weekly invitations reset completed successfully',
resetCount: goldCustomers.length
})
} catch (error) {
console.error('Error in weekly invitations reset:', error)
return NextResponse.json(
{ success: false, error: 'Internal server error' },
{ status: 500 }
)
}
}

View File

@@ -9,13 +9,65 @@
--mocha-taupe: #B8A89A;
--deep-earth: #6F5E4F;
--charcoal-brown: #3F362E;
/* Aperture - Square UI */
--ui-primary: #006AFF;
--ui-primary-hover: #005ED6;
--ui-primary-light: #E6F0FF;
--ui-bg: #F6F8FA;
--ui-bg-card: #FFFFFF;
--ui-bg-hover: #F3F4F6;
--ui-border: #E1E4E8;
--ui-border-light: #F3F4F6;
--ui-text-primary: #24292E;
--ui-text-secondary: #586069;
--ui-text-tertiary: #8B949E;
--ui-text-inverse: #FFFFFF;
--ui-success: #28A745;
--ui-success-light: #D4EDDA;
--ui-warning: #DBAB09;
--ui-warning-light: #FFF3CD;
--ui-error: #D73A49;
--ui-error-light: #F8D7DA;
--ui-info: #0366D6;
--ui-info-light: #CCE5FF;
--ui-shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.08);
--ui-shadow-md: 0 4px 12px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08);
--ui-shadow-lg: 0 8px 24px rgba(0,0,0,0,16), 0 4px 6px rgba(0,0,0,0.08);
--ui-shadow-xl: 0 20px 25px rgba(0,0,0,0.16), 0 4px 6px rgba(0,0,0,0.08);
--ui-radius-sm: 4px;
--ui-radius-md: 6px;
--ui-radius-lg: 8px;
--ui-radius-xl: 12px;
--ui-radius-2xl: 16px;
--ui-radius-full: 9999px;
/* Font sizes */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
}
body {
color: var(--charcoal-brown);
background: var(--bone-white);
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Playfair Display', serif;
}

1011
docs/APERTURE_SPECS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,137 @@
---
## 1. Objetivo
## 1. Stack Técnico
### Frontend Framework
- **Next.js 14** (App Router) - Ya implementado
- **UI Library**: **Radix UI** (componentes accesibles preconstruidos)
- **Estilizado**: **Tailwind CSS + Square UI custom styling**
- **Icons**: Lucide React (24px, stroke 2px)
### Backend
- **Database**: Supabase (PostgreSQL + RLS)
- **Auth**: Supabase Auth
### Notas Importantes
- **Radix UI es la librería principal** para componentes accesibles
- Solo si Radix NO tiene un componente, usar Headless UI
- Estilizado personalizado con tokens Square UI
- Accesibilidad priorizada (ARIA attributes, keyboard navigation)
---
## 2. Objetivo
Aperture (aperture.anchor23.mx) es el dashboard administrativo y CRM interno de AnchorOS. El estilo de diseño debe seguir principios similares a **SquareUi** pero construido con **Radix UI**:
- Minimalista y limpio
- Cards bien definidas con sombras sutiles
- Espaciado generoso
- Foco en usabilidad y claridad
- Animaciones sutiles
- **Accesibilidad completa** (prioridad Radix)
---
## 3. Componentes Radix UI Utilizados
### Componentes Instalados
```bash
npm install @radix-ui/react-button @radix-ui/react-select @radix-ui/react-tabs \
@radix-ui/react-dropdown-menu @radix-ui/react-dialog \
@radix-ui/react-tooltip @radix-ui/react-label @radix-ui/react-switch \
@radix-ui/react-checkbox
```
### Componentes Radix con Estilizado Square UI
1. **@radix-ui/react-button**
- Estilos: `primary`, `secondary`, `ghost`, `danger`, `success`, `warning`
- Squared corners (border-radius: 0 o 4px)
- Full width con variant `default` (ancho 100%)
- Transiciones suaves (150ms ease-out)
2. **@radix-ui/react-select**
- Dropdown con Square UI styling
- Background: `--ui-bg-card`
- Border: `--ui-border`
- Hover: background `--ui-bg-hover`
- Selected: background `--ui-bg-hover`, font-weight 500
3. **@radix-ui/react-tabs**
- Tabs con Square UI styling
- Active indicator: borde inferior 2px solid `--ui-primary`
- Colors: `--ui-text-primary` para activo, `--ui-text-secondary` para inactivo
4. **@radix-ui/react-dropdown-menu**
- Menús desplegables Square UI
- Background: `--ui-bg-card`
- Border: `--ui-border`
- Shadow: `--ui-shadow-md`
- Hover: `background: var(--ui-bg-hover)`
5. **@radix-ui/react-dialog**
- Modals con Square UI styling
- Background: `--ui-bg-card`
- Border: `--ui-border`
- Radius: `--ui-radius-xl`
- Shadow: `--ui-shadow-xl`
6. **@radix-ui/react-tooltip**
- Tooltips con Square UI styling
- Background: `--ui-text-primary`
- Font size: `--text-sm`
- Padding: `--space-2` / `--space-3`
- Shadow: `--ui-shadow-md`
7. **@radix-ui/react-label**
- Labels con Square UI styling
- Color: `--ui-text-primary`
- Font-weight: 500 o 600
- Required indicator con asterisco rojo
8. **@radix-ui/react-switch**
- Switches con Square UI styling
- Track: `--ui-border`
- Thumb: `--ui-primary` background
- Thumb radius: 0 (squared)
9. **@radix-ui/react-checkbox**
- Checkboxes con Square UI styling
- Border: `--ui-border`
- Checked: Background `--ui-primary`
- Checkmark color: `--ui-text-inverse`
### Componentes Custom (No Radix UI)
1. **Card** - Custom
- Background: `--ui-bg-card`
- Border: `--ui-border`
- Radius: `--ui-radius-lg` (8px)
- Shadow: `--ui-shadow-md` o `--ui-shadow-lg`
- Variants: `default`, `elevated`, `bordered`
2. **Avatar** - Custom
- Iniciales para usuarios sin foto
- Status indicators: online (green), offline (gray), busy (red)
- Radius: `--ui-radius-full`
3. **Table** - Custom
- Headers con `font-weight: 600`
- Row hover: `background: var(--ui-bg-hover)`
- Sticky header
- Sort indicators
4. **Badge** - Custom
- Variants: `default`, `success`, `warning`, `error`, `info`
- Small: `text-xs`, Medium: `text-sm`
- Radius: `--ui-radius-full`
---
## 4. Estilos Square UI Componentes
Aperture (aperture.anchor23.mx) es el dashboard administrativo y CRM interno de AnchorOS. El estilo de diseño debe seguir principios similares a SquareUi:

50
middleware.ts Normal file
View File

@@ -0,0 +1,50 @@
/**
* @description Middleware for protecting Aperture routes
* Only users with admin, manager, or staff roles can access Aperture
*/
import { NextResponse, type NextRequest } from 'next/server'
import { createClient } from '@supabase/supabase-js'
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const publicPaths = ['/aperture/login']
const isPublicPath = publicPaths.some(path => pathname.startsWith(path))
if (isPublicPath) {
return NextResponse.next()
}
if (pathname.startsWith('/aperture')) {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
return NextResponse.redirect(new URL('/aperture/login', request.url))
}
const { data: staff } = await supabase
.from('staff')
.select('role')
.eq('user_id', session.user.id)
.single()
if (!staff || !['admin', 'manager', 'staff'].includes(staff.role)) {
return NextResponse.redirect(new URL('/aperture/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: [
'/aperture/:path*',
'/api/aperture/:path*',
],
}

56
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.6.1",
"@supabase/auth-helpers-nextjs": "^0.8.7",
"@supabase/auth-helpers-nextjs": "^0.15.0",
"@supabase/supabase-js": "^2.38.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -1568,30 +1568,16 @@
}
},
"node_modules/@supabase/auth-helpers-nextjs": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.8.7.tgz",
"integrity": "sha512-iYdOjFo0GkRvha340l8JdCiBiyXQuG9v8jnq7qMJ/2fakrskRgHTCOt7ryWbip1T6BExcWKC8SoJrhCzPOxhhg==",
"deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.",
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.15.0.tgz",
"integrity": "sha512-VtXz3GGnxluoxks1g3SaCoYr2OZ7PgRukDl+pLWrDfD2dPDaG8hmkp5iBZsU+lmsDYALGNO2dgbymgpAfD8eCQ==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"dependencies": {
"@supabase/auth-helpers-shared": "0.6.3",
"set-cookie-parser": "^2.6.0"
"cookie": "^1.0.2"
},
"peerDependencies": {
"@supabase/supabase-js": "^2.19.0"
}
},
"node_modules/@supabase/auth-helpers-shared": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.6.3.tgz",
"integrity": "sha512-xYQRLFeFkL4ZfwC7p9VKcarshj3FB2QJMgJPydvOY7J5czJe6xSG5/wM1z63RmAzGbCkKg+dzpq61oeSyWiGBQ==",
"deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.",
"license": "MIT",
"dependencies": {
"jose": "^4.14.4"
},
"peerDependencies": {
"@supabase/supabase-js": "^2.19.0"
"@supabase/supabase-js": "^2.76.1"
}
},
"node_modules/@supabase/auth-js": {
@@ -2865,6 +2851,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4878,15 +4877,6 @@
"jiti": "bin/jiti.js"
}
},
"node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6200,12 +6190,6 @@
"node": ">=10"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",

View File

@@ -24,7 +24,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.6.1",
"@supabase/auth-helpers-nextjs": "^0.8.7",
"@supabase/auth-helpers-nextjs": "^0.15.0",
"@supabase/supabase-js": "^2.38.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",