# Plan: Task Management Feature (Creación y Asignación de Tareas) ## Análisis del Estado Actual ### Modelos Existentes Relacionados - **Issue** + **IssueTask**: Sistema de incidencias con checklist de subtareas (ya implementado) - **User**: Con roles/permisos (Spatie), asignación a proyectos vía pivot `project_user` - **Project**: Con scope `accessibleBy()` para filtrado por permisos - **Notifications**: Sistema de notificaciones Laravel nativo ### Diferencia Clave: IssueTask vs Nueva Tarea | Aspecto | IssueTask (existente) | Nueva Tarea (propuesta) | |---------|----------------------|------------------------| | Contexto | Dentro de una Issue (incidencia) | Independiente, a nivel proyecto o global | | Propósito | Checklist de resolución de incidencia | Gestión de trabajo planificado/operativo | | Asignación | `assigned_to` en IssueTask | `assigned_to` + posible `assigned_by` | | Flujo de estado | `is_done` (boolean) | Estados: `pending` → `in_progress` → `completed` / `cancelled` | --- ## 1. Modelo de Datos ### Nueva Migración: `create_tasks_table.php` ```php Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->foreignId('project_id')->nullable()->constrained()->cascadeOnDelete(); $table->foreignId('phase_id')->nullable()->constrained()->nullOnDelete(); // opcional: vincular a fase $table->foreignId('parent_task_id')->nullable()->constrained('tasks')->nullOnDelete(); // subtareas $table->string('title'); $table->text('description')->nullable(); $table->enum('status', ['pending', 'in_progress', 'completed', 'cancelled'])->default('pending'); $table->enum('priority', ['low', 'medium', 'high', 'critical'])->default('medium'); $table->foreignId('created_by')->constrained('users')->cascadeOnDelete(); $table->foreignId('assigned_to')->nullable()->constrained('users')->nullOnDelete(); $table->date('due_date')->nullable(); $table->date('start_date')->nullable(); $table->datetime('completed_at')->nullable(); $table->foreignId('completed_by')->nullable()->constrained('users')->nullOnDelete(); $table->unsignedInteger('estimated_hours')->nullable(); $table->unsignedInteger('actual_hours')->nullable(); $table->integer('order')->default(0); // para ordenar en listas/Kanban $table->uuid('uuid')->unique(); // para sync offline $table->datetime('client_updated_at')->nullable(); // para sync offline $table->softDeletes(); $table->timestamps(); $table->index(['project_id', 'status']); $table->index(['assigned_to', 'status']); $table->index(['due_date', 'status']); $table->index(['parent_task_id']); }); ``` ### Relaciones en Modelos **Task.php** (nuevo) ```php class Task extends Model { use HasFactory, SoftDeletes, LogsActivity; protected $fillable = [/* todos los campos */]; protected $casts = [ 'due_date' => 'date', 'start_date' => 'date', 'completed_at' => 'datetime', 'estimated_hours' => 'integer', 'actual_hours' => 'integer', ]; public function project() { return $this->belongsTo(Project::class); } public function phase() { return $this->belongsTo(Phase::class); } public function creator() { return $this->belongsTo(User::class, 'created_by'); } public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); } public function completer() { return $this->belongsTo(User::class, 'completed_by'); } public function parent() { return $this->belongsTo(Task::class, 'parent_task_id'); } public function subtasks() { return $this->hasMany(Task::class, 'parent_task_id')->orderBy('order'); } public function media() { return $this->morphMany(Media::class, 'mediable'); } public function comments() { return $this->morphMany(Comment::class, 'commentable'); } // ver punto 6 // Scopes public function scopePending($q) { return $q->where('status', 'pending'); } public function scopeInProgress($q) { return $q->where('status', 'in_progress'); } public function scopeCompleted($q) { return $q->where('status', 'completed'); } public function scopeOverdue($q) { return $q->where('status', '!=', 'completed')->whereDate('due_date', '<', now()); } // Accessors public function getIsOverdueAttribute(): bool { ... } public function getProgressAttribute(): int { /* si tiene subtareas: done/total */ } } ``` **Actualizaciones a modelos existentes:** - `Project.php`: `public function tasks() { return $this->hasMany(Task::class); }` - `Phase.php`: `public function tasks() { return $this->hasMany(Task::class); }` - `User.php`: ```php public function createdTasks() { return $this->hasMany(Task::class, 'created_by'); } public function assignedTasks() { return $this->hasMany(Task::class, 'assigned_to'); } public function completedTasks() { return $this->hasMany(Task::class, 'completed_by'); } ``` --- ## 2. Permisos (Spatie) | Permiso | Descripción | |---------|-------------| | `view tasks` | Ver tareas en proyectos asignados | | `create tasks` | Crear tareas en proyectos donde tiene rol | | `edit tasks` | Editar tareas propias o del proyecto | | `delete tasks` | Eliminar tareas (soft delete) | | `assign tasks` | Cambiar `assigned_to` de cualquier tarea | | `manage all tasks` | Admin: ver/editar todas las tareas | Se añaden al seeder `RolesAndPermissionsSeeder.php`. --- ## 3. Livewire Components ### Estructura de Archivos ``` app/Livewire/Tasks/ ├── TaskManager.php # Lista principal con filtros, tabs (Kanban/List) ├── TaskForm.php # Create/Edit modal o page ├── TaskDetail.php # Vista detalle + comentarios + subtareas ├── TaskKanban.php # Vista Kanban por estado (drag & drop) ├── TaskCalendar.php # Vista calendario (opcional, fase 2) └── MyTasks.php # Widget dashboard: mis tareas asignadas ``` ### TaskManager (Lista Principal) - **Filtros**: proyecto, fase, asignado, estado, prioridad, vencidas, búsqueda texto - **Vistas alternables**: Tabla / Kanban / Lista simple - **Acciones masivas**: asignar, cambiar estado, eliminar, exportar - **Paginación** + ordenación por columnas ### TaskForm - Campos: título, descripción (rich text), proyecto, fase, padre (para subtareas), prioridad, fechas, horas estimadas, asignado - Validación: `due_date >= start_date`, proyecto accesible por usuario - Modal para crear rápido desde lista ### TaskDetail - Info completa + barra de progreso (subtareas) - Timeline de cambios (activity log) - Comentarios (thread style) - Adjuntos (media) - Subtareas inline (crear/editar/marcar done) - Botones acción: Iniciar, Completar, Reabrir, Cancelar ### TaskKanban - Columnas: Pendiente | En Progreso | Completado | Cancelado - Drag & drop para cambiar estado (Livewire `wire:model` + Alpine/SortableJS) - Tarjetas: título, prioridad (color), asignado (avatar), due date (rojo si vencida) - Límite WIP configurable por columna (opcional) ### MyTasks (Dashboard Widget) - Ya existe lógica en `routes/web.php` líneas 37-42 para `IssueTask` - Extender para incluir `Task::where('assigned_to', $user->id)->where('status', '!=', 'completed')` - Vista unificada: "Mis Tareas" (IssueTasks + Tasks) --- ## 4. Rutas (routes/web.php) ```php // Tasks - nivel proyecto Route::prefix('projects/{project}')->name('projects.')->group(function () { Route::get('tasks', \App\Livewire\Tasks\TaskManager::class)->name('tasks'); Route::get('tasks/create', \App\Livewire\Tasks\TaskForm::class)->name('tasks.create'); Route::get('tasks/{task}', \App\Livewire\Tasks\TaskDetail::class)->name('tasks.show'); Route::get('tasks/{task}/edit', \App\Livewire\Tasks\TaskForm::class)->name('tasks.edit'); Route::get('tasks/kanban', \App\Livewire\Tasks\TaskKanban::class)->name('tasks.kanban'); }); // Tasks globales (admin/manager) Route::prefix('tasks')->name('tasks.')->middleware('can:view tasks')->group(function () { Route::get('/', \App\Livewire\Tasks\TaskManager::class)->name('index'); Route::get('/kanban', \App\Livewire\Tasks\TaskKanban::class)->name('kanban'); Route::get('/calendar', \App\Livewire\Tasks\TaskCalendar::class)->name('calendar'); }); ``` --- ## 5. Notificaciones y Eventos ### Eventos - `TaskCreated` → notificar a `assigned_to` - `TaskAssigned` → notificar al nuevo asignado - `TaskStatusChanged` → notificar a `created_by` y `assigned_to` - `TaskOverdue` → job diario que notifica tareas vencidas - `TaskCommentAdded` → notificar a participantes del hilo ### Notification Classes - `TaskAssignedNotification` (implements ShouldQueue) - `TaskStatusChangedNotification` - `TaskOverdueNotification` - `TaskCommentNotification` ### Canales - Database (campana notificaciones) - Email (opcional, configurable por usuario) --- ## 6. Comentarios y Adjuntos (Polimórficos) Reutilizar patrón `Media` (ya existe morphMany). Para comentarios, crear: **Migración:** `create_comments_table.php` ```php $table->id(); $table->morphs('commentable'); // task, issue, project, etc. $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->text('body'); $table->foreignId('parent_id')->nullable()->constrained('comments')->nullOnDelete(); // respuestas $table->timestamps(); ``` **Modelo:** `Comment.php` con trait `LogsActivity` **Uso en Task:** `$task->comments()->with('user')->latest()->get()` --- ## 7. Sync Offline (Existente) Añadir `Task` a `OfflineSyncController`: - `pending` table: incluir `tasks` en modelos sincronizables - `uuid` + `client_updated_at` ya previstos en migración - Reglas de conflicto: `server_updated_at > client_updated_at` gana servidor --- ## 8. Tests (Pest/PHPUnit) | Test | Descripción | |------|-------------| | `TaskModelTest` | CRUD, scopes, accessors, relaciones | | `TaskPolicyTest` | Permisos por rol (admin, manager, viewer, client) | | `TaskManagerTest` | Filtros, paginación, vista tabla/kanban | | `TaskFormTest` | Validaciones, crear/editar, subtareas | | `TaskKanbanTest` | Drag & drop cambio estado, límites WIP | | `TaskNotificationTest` | Eventos disparan notificaciones correctas | | `TaskSyncTest` | Offline sync create/update/delete/conflict | --- ## 9. Internacionalización (i18n) Añadir keys a `lang/{en,es,fr,ru}.json`: - `tasks.*` (title, description, status.*, priority.*, due_date, assigned_to, subtasks, etc.) - Notificaciones: `notifications.task_assigned`, `notifications.task_overdue`, etc. --- ## 10. Fases de Implementación ### Fase 1 - Core (MVP) ⏱️ ~3-4 días 1. Migración + Model `Task` + relaciones 2. Permisos en seeder 3. `TaskManager` (tabla + filtros básicos) 4. `TaskForm` (create/edit modal) 5. `TaskDetail` (vista + acciones estado) 6. Rutas + navegación (sidebar) 7. Tests básicos ### Fase 2 - Kanban + UX ⏱️ ~2 días 1. `TaskKanban` con drag & drop (SortableJS) 2. `MyTasks` widget en dashboard (unificar con IssueTask) 3. Subtareas inline en TaskDetail 4. Comentarios polimórficos ### Fase 3 - Notificaciones + Avanzado ⏱️ ~2 días 1. Eventos + Notifications + Email 2. Job diario overdue 3. Vista calendario (FullCalendar) 4. Exportar CSV/Excel ### Fase 4 - Pulido ⏱️ ~1 día 1. i18n completo 2. Tests E2E (Pest + Browser) 3. Documentación en README 4. Code review + merge --- ## 11. Consideraciones de Diseño ### UI/UX - **Colores prioridad**: low=gris, medium=ámbar, high=rojo, critical=morado (consistente con Issue) - **Iconos**: Heroicons (ya usados en proyecto) - **Responsive**: Tailwind breakpoints, tabla → tarjetas en móvil - **Accesibilidad**: ARIA en Kanban, focus management en modales ### Rendimiento - Eager loading: `with(['project', 'assignee', 'phase', 'subtasks'])` - Índices compuestos en migración - Paginación 25/50/100 - Lazy loading en Kanban (columnas virtualizadas si >100 tareas) ### Seguridad - Policy `TaskPolicy` con `viewAny`, `view`, `create`, `update`, `delete`, `assign`, `restore`, `forceDelete` - Scope `accessibleBy` en Project reutilizable para Task - Validación de pertenencia a proyecto en FormRequest --- ## 12. Archivos a Crear/Modificar (Resumen) ### Nuevos - `database/migrations/xxxx_create_tasks_table.php` - `database/migrations/xxxx_create_comments_table.php` - `app/Models/Task.php` - `app/Models/Comment.php` - `app/Policies/TaskPolicy.php` - `app/Notifications/TaskAssignedNotification.php` - `app/Notifications/TaskStatusChangedNotification.php` - `app/Notifications/TaskOverdueNotification.php` - `app/Notifications/TaskCommentNotification.php` - `app/Jobs/NotifyOverdueTasks.php` - `app/Livewire/Tasks/TaskManager.php` + blade - `app/Livewire/Tasks/TaskForm.php` + blade - `app/Livewire/Tasks/TaskDetail.php` + blade - `app/Livewire/Tasks/TaskKanban.php` + blade - `app/Livewire/Tasks/MyTasks.php` + blade (widget) - `tests/Feature/Task*.php` (múltiples) ### Modificados - `database/seeders/RolesAndPermissionsSeeder.php` (permisos) - `app/Models/Project.php` (relación tasks) - `app/Models/Phase.php` (relación tasks) - `app/Models/User.php` (relaciones tasks) - `routes/web.php` (rutas tasks) - `lang/{en,es,fr,ru}.json` (traducciones) - `resources/views/layouts/navigation.blade.php` (link sidebar) - `resources/views/dashboard.blade.php` (widget MyTasks) - `app/Http/Controllers/OfflineSyncController.php` (sync tasks) --- ## Próximo Paso ¿Quieres que inicie la **Fase 1** creando la migración, el modelo Task, los permisos y el TaskManager básico?