validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'password' => 'nullable|string|min:8', 'language' => 'sometimes|string|in:es,pt-BR,en', 'currency' => 'sometimes|string|size:3', 'user_type' => 'sometimes|string|in:free,pro,admin', 'send_welcome_email' => 'sometimes|boolean', ]); // Generate random password if not provided or empty $password = !empty($validated['password']) ? $validated['password'] : bin2hex(random_bytes(8)); $userType = $validated['user_type'] ?? 'free'; $sendWelcomeEmail = $validated['send_welcome_email'] ?? true; $user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => Hash::make($password), 'language' => $validated['language'] ?? 'es', 'currency' => $validated['currency'] ?? 'EUR', 'email_verified_at' => now(), // Auto-verify admin-created users 'is_admin' => $userType === 'admin', ]); $subscriptionInfo = null; // Create Pro subscription if user_type is 'pro' or 'admin' if (in_array($userType, ['pro', 'admin'])) { $proPlan = Plan::where('slug', 'pro-annual')->first(); if ($proPlan) { $subscription = Subscription::create([ 'user_id' => $user->id, 'plan_id' => $proPlan->id, 'status' => Subscription::STATUS_ACTIVE, 'current_period_start' => now(), 'current_period_end' => Carbon::create(2037, 12, 31, 23, 59, 59), // "Lifetime" subscription (max timestamp) 'paypal_subscription_id' => 'ADMIN_GRANTED_' . strtoupper(bin2hex(random_bytes(8))), 'paypal_status' => 'ACTIVE', 'price_paid' => 0, 'currency' => 'EUR', ]); $subscriptionInfo = [ 'plan' => $proPlan->name, 'status' => 'active', 'expires' => 'Nunca (otorgado por admin)', ]; } } // Send welcome email with temporary password $emailSent = false; if ($sendWelcomeEmail) { try { Mail::to($user->email)->send(new WelcomeNewUser($user, $password)); $emailSent = true; } catch (\Exception $e) { \Log::error('Failed to send welcome email: ' . $e->getMessage()); } } return response()->json([ 'success' => true, 'message' => 'Usuario creado correctamente', 'data' => [ 'user' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'is_admin' => $user->is_admin, 'language' => $user->language, ], 'user_type' => $userType, 'subscription' => $subscriptionInfo, 'temporary_password' => isset($validated['password']) ? null : $password, 'welcome_email_sent' => $emailSent, ], ], 201); } /** * List all users with their subscription info */ public function index(Request $request) { $query = User::with(['subscription.plan']) ->withCount(['accounts', 'categories', 'budgets', 'transactions']); // Search if ($request->has('search') && $request->search) { $search = $request->search; $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); }); } // Filter by subscription status if ($request->has('subscription_status')) { if ($request->subscription_status === 'active') { $query->whereHas('subscription'); } elseif ($request->subscription_status === 'free') { $query->whereDoesntHave('subscription'); } } // Pagination $perPage = $request->get('per_page', 20); $users = $query->orderBy('created_at', 'desc')->paginate($perPage); // Transform data $users->getCollection()->transform(function ($user) { return [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'language' => $user->language, 'currency' => $user->currency, 'is_admin' => (bool) $user->is_admin, 'created_at' => $user->created_at, 'last_login_at' => $user->last_login_at, 'email_verified_at' => $user->email_verified_at, 'subscription' => $user->subscription ? [ 'plan_name' => $user->subscription->plan->name ?? 'Unknown', 'plan_slug' => $user->subscription->plan->slug ?? 'unknown', 'status' => $user->subscription->status, 'current_period_end' => $user->subscription->current_period_end, ] : null, 'usage' => [ 'accounts' => $user->accounts_count, 'categories' => $user->categories_count, 'budgets' => $user->budgets_count, 'transactions' => $user->transactions_count, ], ]; }); return response()->json([ 'success' => true, 'data' => $users->items(), 'pagination' => [ 'current_page' => $users->currentPage(), 'last_page' => $users->lastPage(), 'per_page' => $users->perPage(), 'total' => $users->total(), ], ]); } /** * Get single user details */ public function show($id) { $user = User::with(['subscription.plan', 'subscriptions.plan']) ->withCount(['accounts', 'categories', 'budgets', 'transactions', 'goals']) ->findOrFail($id); return response()->json([ 'success' => true, 'data' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'language' => $user->language, 'currency' => $user->currency, 'created_at' => $user->created_at, 'last_login_at' => $user->last_login_at, 'email_verified_at' => $user->email_verified_at, 'subscription' => $user->subscription ? [ 'id' => $user->subscription->id, 'plan' => $user->subscription->plan, 'status' => $user->subscription->status, 'paypal_subscription_id' => $user->subscription->paypal_subscription_id, 'current_period_start' => $user->subscription->current_period_start, 'current_period_end' => $user->subscription->current_period_end, 'canceled_at' => $user->subscription->canceled_at, ] : null, 'subscription_history' => $user->subscriptions->map(function ($sub) { return [ 'plan_name' => $sub->plan->name ?? 'Unknown', 'status' => $sub->status, 'created_at' => $sub->created_at, 'canceled_at' => $sub->canceled_at, ]; }), 'usage' => [ 'accounts' => $user->accounts_count, 'categories' => $user->categories_count, 'budgets' => $user->budgets_count, 'transactions' => $user->transactions_count, 'goals' => $user->goals_count, ], ], ]); } /** * Update user */ public function update(Request $request, $id) { $user = User::findOrFail($id); // Don't allow changing main admin's admin status if ($user->email === 'marco@cnxifly.com' && $request->has('is_admin') && !$request->is_admin) { return response()->json([ 'success' => false, 'message' => 'No se puede remover permisos del administrador principal', ], 403); } $validated = $request->validate([ 'name' => 'sometimes|string|max:255', 'email' => 'sometimes|email|unique:users,email,' . $id, 'language' => 'sometimes|string|in:es,pt-BR,en', 'currency' => 'sometimes|string|size:3', 'is_admin' => 'sometimes|boolean', ]); $user->update($validated); return response()->json([ 'success' => true, 'message' => 'Usuario actualizado correctamente', 'data' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'language' => $user->language, 'currency' => $user->currency, 'is_admin' => $user->is_admin, ], ]); } /** * Reset user password (generate random) */ public function resetPassword($id) { $user = User::findOrFail($id); // Generate random password $newPassword = bin2hex(random_bytes(8)); // 16 chars $user->password = Hash::make($newPassword); $user->save(); return response()->json([ 'success' => true, 'message' => 'ContraseƱa restablecida correctamente', 'data' => [ 'temporary_password' => $newPassword, ], ]); } /** * Delete user and all their data */ public function destroy($id) { $user = User::findOrFail($id); // Don't allow deleting admin if ($user->email === 'marco@cnxifly.com') { return response()->json([ 'success' => false, 'message' => 'No se puede eliminar el usuario administrador', ], 403); } // Delete user (cascade will handle related data) $user->delete(); return response()->json([ 'success' => true, 'message' => 'Usuario eliminado correctamente', ]); } /** * Change user subscription plan */ public function changePlan(Request $request, $id) { $user = User::findOrFail($id); $validated = $request->validate([ 'plan' => 'required|string|in:free,pro', ]); // If changing to free, cancel any existing subscription if ($validated['plan'] === 'free') { $subscription = $user->subscription; if ($subscription) { $subscription->update([ 'status' => Subscription::STATUS_CANCELED, 'canceled_at' => now(), ]); } return response()->json([ 'success' => true, 'message' => 'Usuario cambiado a plan Free', 'data' => [ 'plan' => 'free', 'subscription' => null, ], ]); } // If changing to pro, create or reactivate subscription if ($validated['plan'] === 'pro') { $proPlan = Plan::where('slug', 'pro-annual')->first(); if (!$proPlan) { return response()->json([ 'success' => false, 'message' => 'Plan Pro no encontrado en el sistema', ], 404); } // Check if user has existing subscription $subscription = $user->subscription; if ($subscription) { // Reactivate existing subscription $subscription->update([ 'status' => Subscription::STATUS_ACTIVE, 'canceled_at' => null, 'current_period_start' => now(), 'current_period_end' => Carbon::create(2037, 12, 31, 23, 59, 59), ]); } else { // Create new subscription $subscription = Subscription::create([ 'user_id' => $user->id, 'plan_id' => $proPlan->id, 'status' => Subscription::STATUS_ACTIVE, 'current_period_start' => now(), 'current_period_end' => Carbon::create(2037, 12, 31, 23, 59, 59), 'paypal_subscription_id' => 'ADMIN_GRANTED_' . strtoupper(bin2hex(random_bytes(8))), 'paypal_status' => 'ACTIVE', 'price_paid' => 0, 'currency' => 'EUR', ]); } return response()->json([ 'success' => true, 'message' => 'Usuario cambiado a plan Pro', 'data' => [ 'plan' => 'pro', 'subscription' => [ 'id' => $subscription->id, 'plan_name' => $proPlan->name, 'status' => $subscription->status, 'current_period_end' => $subscription->current_period_end, ], ], ]); } } /** * Get summary statistics */ public function summary() { $totalUsers = User::count(); $activeSubscribers = Subscription::where('status', 'active')->count(); $freeUsers = $totalUsers - $activeSubscribers; $newUsersThisMonth = User::whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year) ->count(); return response()->json([ 'success' => true, 'data' => [ 'total_users' => $totalUsers, 'active_subscribers' => $activeSubscribers, 'free_users' => $freeUsers, 'new_users_this_month' => $newUsersThisMonth, ], ]); } }