From 137cbfdf743c4b9dec3daf10620e1cadc26cf240 Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Sat, 17 Jan 2026 10:52:16 -0600 Subject: [PATCH] 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) --- TASKS.md | 27 ++++-- app/api/cron/reset-invitations/route.ts | 109 ++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 app/api/cron/reset-invitations/route.ts diff --git a/TASKS.md b/TASKS.md index 46621fb..fb7871b 100644 --- a/TASKS.md +++ b/TASKS.md @@ -527,16 +527,25 @@ Validación Staff (rol Staff): 2. ✅ **Implementar autenticación para Aperture** - COMPLETADO - ✅ Integración con Supabase Auth para roles admin/manager/staff - - ✅ Protección de rutas de Aperture (middleware creado) - - ✅ Session management con AuthProvider existente - - ✅ Página login ya existe en `/app/aperture/login/page.tsx` + - ✅ 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) diff --git a/app/api/cron/reset-invitations/route.ts b/app/api/cron/reset-invitations/route.ts new file mode 100644 index 0000000..7c29871 --- /dev/null +++ b/app/api/cron/reset-invitations/route.ts @@ -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 } + ) + } +}