user()->tasks() ->with(['project:id,name,color', 'tags:id,name,color']) ->orderBy('done') ->orderByRaw("FIELD(priority,'high','med','low')") ->orderBy('created_at') ->get(); return response()->json($tasks); } public function pending(Request $r): JsonResponse { $tasks = $r->user()->tasks() ->active() ->with('project:id,name,color') ->orderByRaw("FIELD(priority,'high','med','low')") ->orderBy('created_at') ->limit(20) ->get(); return response()->json($tasks->map(fn ($t) => [ 'id' => $t->id, 'title' => $t->title, 'priority' => $t->priority, 'project' => $t->project?->name, 'next_due_in' => $t->next_due_in, 'estimate_min' => $t->estimate_min, ])); } public function store(Request $r): JsonResponse { $data = $r->validate([ 'title' => 'required|string|max:120', 'description' => 'nullable|string', 'priority' => 'nullable|in:low,med,high', 'estimate_min' => 'nullable|integer|min:1|max:9999', 'deadline' => 'nullable|date', 'project_id' => 'nullable|integer|exists:projects,id', 'recurrence' => 'nullable|in:daily,weekly,monthly', 'tags' => 'nullable|array', 'tags.*' => 'string|max:30', ]); $task = $r->user()->tasks()->create([ 'title' => $data['title'], 'description' => $data['description'] ?? null, 'priority' => $data['priority'] ?? 'med', 'estimate_min' => $data['estimate_min'] ?? null, 'deadline' => $data['deadline'] ?? null, 'project_id' => $data['project_id'] ?? null, 'recurrence' => $data['recurrence'] ?? null, ]); if (! empty($data['tags'])) { $this->syncTags($task, $data['tags']); } return response()->json($task->load('project', 'tags'), 201); } public function update(Request $r, Task $task): JsonResponse { abort_unless($task->user_id === $r->user()->id, 403); $data = $r->validate([ 'title' => 'sometimes|required|string|max:120', 'description' => 'nullable|string', 'priority' => 'nullable|in:low,med,high', 'estimate_min' => 'nullable|integer|min:1|max:9999', 'deadline' => 'nullable|date', 'project_id' => 'nullable|integer|exists:projects,id', 'recurrence' => 'nullable|in:daily,weekly,monthly', 'tags' => 'nullable|array', ]); $task->update(array_intersect_key($data, array_flip([ 'title', 'description', 'priority', 'estimate_min', 'deadline', 'project_id', 'recurrence', ]))); if (array_key_exists('tags', $data)) { $this->syncTags($task, $data['tags']); } return response()->json($task->load('project', 'tags')); } public function done(Request $r, Task $task): JsonResponse { abort_unless($task->user_id === $r->user()->id, 403); $task->update(['done' => true, 'done_at' => now()]); // Si la tarea es recurrente, generamos la siguiente if ($task->recurrence) { $next = $task->replicate(['done', 'done_at', 'snooze_until']); $next->done = false; $next->done_at = null; $next->snooze_until = null; $next->deadline = match ($task->recurrence) { 'daily' => $task->deadline?->addDay() ?? now()->addDay(), 'weekly' => $task->deadline?->addWeek() ?? now()->addWeek(), 'monthly' => $task->deadline?->addMonth() ?? now()->addMonth(), default => null, }; $next->save(); } return response()->json(['ok' => true, 'id' => $task->id]); } public function snooze(Request $r, Task $task): JsonResponse { abort_unless($task->user_id === $r->user()->id, 403); $minutes = (int) $r->input('minutes', 5); $minutes = max(1, min(720, $minutes)); $task->update(['snooze_until' => now()->addMinutes($minutes)]); return response()->json(['ok' => true, 'id' => $task->id, 'minutes' => $minutes]); } public function destroy(Request $r, Task $task): JsonResponse { abort_unless($task->user_id === $r->user()->id, 403); $task->delete(); return response()->json(['ok' => true]); } public function restore(Request $r, Task $task): JsonResponse { abort_unless($task->user_id === $r->user()->id, 403); $task->update(['done' => false, 'done_at' => null, 'snooze_until' => null]); return response()->json($task); } /** Estadísticas rápidas para el dashboard */ public function stats(Request $r): JsonResponse { $userId = $r->user()->id; $today = now()->startOfDay(); return response()->json([ 'pending' => Task::forUser($userId)->active()->count(), 'today' => Task::forUser($userId)->active() ->where('created_at', '>=', $today)->count(), 'done_today'=> Task::forUser($userId)->where('done', true) ->where('done_at', '>=', $today)->count(), 'overdue' => Task::forUser($userId)->active() ->where('deadline', '<', now())->count(), 'total_done'=> Task::forUser($userId)->where('done', true)->count(), ]); } private function syncTags(Task $task, array $tagNames): void { $userId = $task->user_id; $ids = []; foreach ($tagNames as $name) { $tag = \App\Models\Tag::firstOrCreate( ['user_id' => $userId, 'name' => $name], ['color' => '#a3a3a3'] ); $ids[] = $tag->id; } $task->tags()->sync($ids); } }