From 3f240e527797732a0ab6eddd3c3e787576b8d56b Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 18 Jun 2026 12:12:39 +0200 Subject: [PATCH] =?UTF-8?q?feat(issues):=20incidencias=20enriquecidas=20(t?= =?UTF-8?q?areas/comentarios/fotos/verificaci=C3=B3n)=20+=20tabla=20Rappas?= =?UTF-8?q?oft=20+=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web: - IssueTask + IssueComment (modelos, migraciones, soft-deletes, campos de sync). Issue gana tasks()/comments() y accessor de % de avance derivado de tareas. - IssueDetail (página): checklist con asignado/fecha límite/progreso, hilo de comentarios con foto por comentario, galería de fotos de la incidencia y flujo de verificación open→in_review→resolved/closed (+reabrir) con notas. - Creación/edición en páginas propias (IssueForm), sin modal; al guardar redirige al detalle. Rutas projects.issues.create/edit/show. - Listado con tabla Rappasoft (IssueTable): filtros por estado/prioridad, búsqueda, barra de progreso y acciones por fila gateadas por permisos; IssueManager queda como contenedor (cabecera + stats) que embebe la tabla. - Seguridad: pertenencia al proyecto + permisos por acción (view/create/edit/delete issues, upload/delete media) en todos los componentes. API móvil (offline): - /sync: issue_task.create/update y issue_comment.create (idempotente, LWW). - /media: parent_entity issue_task / issue_comment. - bundle + tombstones incluyen issue_tasks / issue_comments. - openapi.yaml + MOBILE_SYNC_PROTOCOL.md actualizados. Tests: MobileApiTest 23 passing (+5); IssuesTablePageTest (3) smoke de la tabla. Branding: logo RTE International — MAI Group (public/images/logo-rte.png) en login y navegación; application-logo pasa de SVG por defecto a . Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Controllers/Api/V1/MediaController.php | 28 +- .../Api/V1/ProjectApiController.php | 76 +++- .../Controllers/Api/V1/SyncController.php | 115 ++++++ app/Livewire/IssueDetail.php | 240 +++++++++++ app/Livewire/IssueForm.php | 126 ++++++ app/Livewire/IssueManager.php | 185 ++------- app/Livewire/IssueTable.php | 217 ++++++++++ app/Models/Issue.php | 18 + app/Models/IssueComment.php | 20 + app/Models/IssueTask.php | 34 ++ ..._06_18_120000_create_issue_tasks_table.php | 35 ++ ..._18_120100_create_issue_comments_table.php | 30 ++ docs/MOBILE_SYNC_PROTOCOL.md | 14 + docs/openapi.yaml | 8 +- public/images/.gitkeep | 0 public/images/logo-rte.png | Bin 0 -> 94363 bytes .../components/application-logo.blade.php | 4 +- resources/views/layouts/guest.blade.php | 2 +- .../livewire/issues/issue-detail.blade.php | 304 ++++++++++++++ .../livewire/issues/issue-form.blade.php | 119 ++++++ .../livewire/issues/issue-manager.blade.php | 379 +----------------- .../livewire/layout/navigation.blade.php | 2 +- routes/web.php | 3 + tests/Feature/Api/MobileApiTest.php | 124 +++++- tests/Feature/IssuesTablePageTest.php | 87 ++++ 25 files changed, 1604 insertions(+), 566 deletions(-) create mode 100644 app/Livewire/IssueDetail.php create mode 100644 app/Livewire/IssueForm.php create mode 100644 app/Livewire/IssueTable.php create mode 100644 app/Models/IssueComment.php create mode 100644 app/Models/IssueTask.php create mode 100644 database/migrations/2026_06_18_120000_create_issue_tasks_table.php create mode 100644 database/migrations/2026_06_18_120100_create_issue_comments_table.php create mode 100644 public/images/.gitkeep create mode 100644 public/images/logo-rte.png create mode 100644 resources/views/livewire/issues/issue-detail.blade.php create mode 100644 resources/views/livewire/issues/issue-form.blade.php create mode 100644 tests/Feature/IssuesTablePageTest.php diff --git a/app/Http/Controllers/Api/V1/MediaController.php b/app/Http/Controllers/Api/V1/MediaController.php index 6c27fe1..77ecca5 100644 --- a/app/Http/Controllers/Api/V1/MediaController.php +++ b/app/Http/Controllers/Api/V1/MediaController.php @@ -5,6 +5,8 @@ namespace App\Http\Controllers\Api\V1; use App\Http\Controllers\Controller; use App\Models\Feature; use App\Models\Issue; +use App\Models\IssueComment; +use App\Models\IssueTask; use App\Models\Layer; use App\Models\Media; use App\Models\Phase; @@ -17,11 +19,13 @@ use Illuminate\Support\Str; class MediaController extends Controller { private array $map = [ - 'feature' => Feature::class, - 'issue' => Issue::class, - 'project' => Project::class, - 'phase' => Phase::class, - 'layer' => Layer::class, + 'feature' => Feature::class, + 'issue' => Issue::class, + 'issue_task' => IssueTask::class, + 'issue_comment' => IssueComment::class, + 'project' => Project::class, + 'phase' => Phase::class, + 'layer' => Layer::class, ]; /** Upload a file (multipart) and attach it to a parent record. Idempotent by uuid. */ @@ -73,12 +77,14 @@ class MediaController extends Controller private function projectOf(string $entity, $parent): ?Project { return match ($entity) { - 'project' => $parent, - 'phase' => $parent->project, - 'layer' => $parent->phase?->project, - 'feature' => $parent->layer?->phase?->project, - 'issue' => $parent->project, - default => null, + 'project' => $parent, + 'phase' => $parent->project, + 'layer' => $parent->phase?->project, + 'feature' => $parent->layer?->phase?->project, + 'issue' => $parent->project, + 'issue_task' => $parent->issue?->project, + 'issue_comment' => $parent->issue?->project, + default => null, }; } diff --git a/app/Http/Controllers/Api/V1/ProjectApiController.php b/app/Http/Controllers/Api/V1/ProjectApiController.php index fa80213..1559e98 100644 --- a/app/Http/Controllers/Api/V1/ProjectApiController.php +++ b/app/Http/Controllers/Api/V1/ProjectApiController.php @@ -7,6 +7,8 @@ use App\Models\Feature; use App\Models\Inspection; use App\Models\InspectionTemplate; use App\Models\Issue; +use App\Models\IssueComment; +use App\Models\IssueTask; use App\Models\Layer; use App\Models\Media; use App\Models\Phase; @@ -49,25 +51,35 @@ class ProjectApiController extends Controller $issues = $changed(Issue::where('project_id', $project->id))->get(); $templates = $changed(InspectionTemplate::where('project_id', $project->id))->get(); + $allIssueIds = Issue::withTrashed()->where('project_id', $project->id)->pluck('id'); + $issueTasks = $changed(IssueTask::whereIn('issue_id', $allIssueIds))->get(); + $issueComments = $changed(IssueComment::whereIn('issue_id', $allIssueIds))->get(); + $featureIds = Feature::whereIn('layer_id', $allLayerIds)->pluck('id'); $issueIds = Issue::where('project_id', $project->id)->pluck('id'); - $media = $changed(Media::where(function ($q) use ($project, $featureIds, $issueIds) { + $taskIds = IssueTask::whereIn('issue_id', $allIssueIds)->pluck('id'); + $commentIds = IssueComment::whereIn('issue_id', $allIssueIds)->pluck('id'); + $media = $changed(Media::where(function ($q) use ($project, $featureIds, $issueIds, $taskIds, $commentIds) { $q->where(fn ($w) => $w->where('mediable_type', Project::class)->where('mediable_id', $project->id)) ->orWhere(fn ($w) => $w->where('mediable_type', Feature::class)->whereIn('mediable_id', $featureIds)) - ->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds)); + ->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds)) + ->orWhere(fn ($w) => $w->where('mediable_type', IssueTask::class)->whereIn('mediable_id', $taskIds)) + ->orWhere(fn ($w) => $w->where('mediable_type', IssueComment::class)->whereIn('mediable_id', $commentIds)); }))->get(); return response()->json([ - 'server_time' => now()->toIso8601String(), - 'project' => $this->mapProject($project), - 'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(), - 'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(), - 'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(), - 'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(), - 'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(), - 'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(), - 'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(), - 'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds) : (object) [], + 'server_time' => now()->toIso8601String(), + 'project' => $this->mapProject($project), + 'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(), + 'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(), + 'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(), + 'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(), + 'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(), + 'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(), + 'issue_comments' => $issueComments->map(fn ($c) => $this->mapIssueComment($c))->values(), + 'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(), + 'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(), + 'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds, $allIssueIds) : (object) [], ]); } @@ -100,14 +112,16 @@ class ProjectApiController extends Controller ); } - private function tombstones(Carbon $since, Project $project, $allPhaseIds, $allLayerIds): array + private function tombstones(Carbon $since, Project $project, $allPhaseIds, $allLayerIds, $allIssueIds): array { return [ - 'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), - 'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(), - 'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(), - 'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), - 'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'issue_tasks' => IssueTask::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(), + 'issue_comments' => IssueComment::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(), ]; } @@ -166,6 +180,24 @@ class ProjectApiController extends Controller ]; } + private function mapIssueTask(IssueTask $t): array + { + return [ + 'id' => $t->id, 'issue_id' => $t->issue_id, 'title' => $t->title, + 'is_done' => $t->is_done, 'done_at' => $t->done_at?->toIso8601String(), 'done_by' => $t->done_by, + 'assigned_to' => $t->assigned_to, 'due_date' => $t->due_date?->toDateString(), + 'order' => $t->order, 'updated_at' => $t->updated_at?->toIso8601String(), + ]; + } + + private function mapIssueComment(IssueComment $c): array + { + return [ + 'id' => $c->id, 'issue_id' => $c->issue_id, 'user_id' => $c->user_id, 'body' => $c->body, + 'created_at' => $c->created_at?->toIso8601String(), 'updated_at' => $c->updated_at?->toIso8601String(), + ]; + } + private function mapTemplate(InspectionTemplate $t): array { return [ @@ -180,9 +212,11 @@ class ProjectApiController extends Controller private function mapMedia(Media $m): array { $entity = [ - Project::class => 'project', - Feature::class => 'feature', - Issue::class => 'issue', + Project::class => 'project', + Feature::class => 'feature', + Issue::class => 'issue', + IssueTask::class => 'issue_task', + IssueComment::class => 'issue_comment', ][$m->mediable_type] ?? class_basename($m->mediable_type); return [ diff --git a/app/Http/Controllers/Api/V1/SyncController.php b/app/Http/Controllers/Api/V1/SyncController.php index 716a1ff..91151ed 100644 --- a/app/Http/Controllers/Api/V1/SyncController.php +++ b/app/Http/Controllers/Api/V1/SyncController.php @@ -6,6 +6,8 @@ use App\Http\Controllers\Controller; use App\Models\Feature; use App\Models\Inspection; use App\Models\Issue; +use App\Models\IssueComment; +use App\Models\IssueTask; use App\Models\Phase; use App\Models\ProgressUpdate; use App\Models\Project; @@ -63,6 +65,9 @@ class SyncController extends Controller 'inspection.create' => $this->inspectionCreate($user, $uuid, $op), 'issue.create' => $this->issueCreate($user, $uuid, $op), 'issue.update' => $this->issueUpdate($user, $uuid, $op), + 'issue_task.create' => $this->issueTaskCreate($user, $uuid, $op), + 'issue_task.update' => $this->issueTaskUpdate($user, $uuid, $op), + 'issue_comment.create' => $this->issueCommentCreate($user, $uuid, $op), 'feature.update' => $this->featureUpdate($user, $uuid, $op), default => $this->error($uuid, 'unsupported entity/op: ' . $op['entity'] . '.' . $op['op']), }; @@ -245,6 +250,116 @@ class SyncController extends Controller return $this->applied($uuid, $issue->id); } + // ── issue_task.create / issue_task.update ────────────────────────────────────── + + private function issueTaskCreate(User $user, string $uuid, array $op): array + { + if ($existing = IssueTask::where('uuid', $uuid)->first()) { + return $this->duplicate($uuid, $existing->id); + } + + $v = Validator::make($op['data'], [ + 'issue_id' => ['required', 'integer', 'exists:issues,id'], + 'title' => ['required', 'string', 'max:255'], + 'assigned_to' => ['nullable', 'integer', 'exists:users,id'], + 'due_date' => ['nullable', 'date'], + 'is_done' => ['nullable', 'boolean'], + ]); + if ($v->fails()) { + return $this->error($uuid, 'validation: ' . $v->errors()->first()); + } + $d = $v->validated(); + + $issue = Issue::with('project')->findOrFail($d['issue_id']); + if (! $this->canAccess($user, $issue->project) || ! $user->can('edit issues')) { + return $this->error($uuid, 'forbidden'); + } + + $done = $d['is_done'] ?? false; + $task = IssueTask::create([ + 'uuid' => $uuid, + 'issue_id' => $issue->id, + 'title' => $d['title'], + 'assigned_to' => $d['assigned_to'] ?? null, + 'due_date' => $d['due_date'] ?? null, + 'is_done' => $done, + 'done_at' => $done ? now() : null, + 'done_by' => $done ? $user->id : null, + 'order' => ((int) $issue->tasks()->max('order')) + 1, + 'client_updated_at' => $op['client_updated_at'] ?? null, + ]); + + return $this->applied($uuid, $task->id); + } + + private function issueTaskUpdate(User $user, string $uuid, array $op): array + { + $v = Validator::make($op['data'], [ + 'id' => ['required', 'integer', 'exists:issue_tasks,id'], + 'title' => ['nullable', 'string', 'max:255'], + 'assigned_to' => ['nullable', 'integer', 'exists:users,id'], + 'due_date' => ['nullable', 'date'], + 'is_done' => ['nullable', 'boolean'], + ]); + if ($v->fails()) { + return $this->error($uuid, 'validation: ' . $v->errors()->first()); + } + $d = $v->validated(); + + $task = IssueTask::with('issue.project')->findOrFail($d['id']); + if (! $this->canAccess($user, $task->issue?->project) || ! $user->can('edit issues')) { + return $this->error($uuid, 'forbidden'); + } + + if ($conflict = $this->conflict($uuid, $task, $op)) { + return $conflict; + } + + if (array_key_exists('is_done', $d)) { + $task->is_done = (bool) $d['is_done']; + $task->done_at = $d['is_done'] ? ($task->done_at ?? now()) : null; + $task->done_by = $d['is_done'] ? ($task->done_by ?? $user->id) : null; + } + $task->fill(collect($d)->only('title', 'assigned_to', 'due_date')->toArray()); + $task->client_updated_at = $op['client_updated_at'] ?? null; + $task->save(); + + return $this->applied($uuid, $task->id); + } + + // ── issue_comment.create ──────────────────────────────────────────────────────── + + private function issueCommentCreate(User $user, string $uuid, array $op): array + { + if ($existing = IssueComment::where('uuid', $uuid)->first()) { + return $this->duplicate($uuid, $existing->id); + } + + $v = Validator::make($op['data'], [ + 'issue_id' => ['required', 'integer', 'exists:issues,id'], + 'body' => ['required', 'string', 'max:5000'], + ]); + if ($v->fails()) { + return $this->error($uuid, 'validation: ' . $v->errors()->first()); + } + $d = $v->validated(); + + $issue = Issue::with('project')->findOrFail($d['issue_id']); + if (! $this->canAccess($user, $issue->project) || ! $user->can('view issues')) { + return $this->error($uuid, 'forbidden'); + } + + $comment = IssueComment::create([ + 'uuid' => $uuid, + 'issue_id' => $issue->id, + 'user_id' => $user->id, + 'body' => $d['body'], + 'client_updated_at' => $op['client_updated_at'] ?? null, + ]); + + return $this->applied($uuid, $comment->id); + } + // ── feature.update ──────────────────────────────────────────────────────────── private function featureUpdate(User $user, string $uuid, array $op): array diff --git a/app/Livewire/IssueDetail.php b/app/Livewire/IssueDetail.php new file mode 100644 index 0000000..08b38ca --- /dev/null +++ b/app/Livewire/IssueDetail.php @@ -0,0 +1,240 @@ +project_id === $project->id, 404); + $this->project = $project; + $this->issue = $issue; + abort_unless($this->canAccessProject() && Auth::user()->can('view issues'), 403); + + $this->resolutionNotes = $issue->resolution_notes ?? ''; + $this->projectUsers = $project->users()->orderBy('name')->get(); + $this->refreshIssue(); + } + + private function canAccessProject(): bool + { + $user = Auth::user(); + return $user->can('manage all') + || $this->project->users()->where('user_id', $user->id)->exists(); + } + + private function canEdit(): bool + { + return Auth::user()->can('edit issues'); + } + + public function refreshIssue(): void + { + $this->issue->load([ + 'reporter', 'assignee', 'feature', + 'tasks.assignee', 'tasks.completer', + 'comments.user', 'comments.media', + 'media.uploader', + ]); + } + + // ── Checklist ──────────────────────────────────────────────────────────────── + + public function addTask(): void + { + abort_unless($this->canEdit(), 403); + $this->validate([ + 'newTaskTitle' => 'required|string|max:255', + 'newTaskAssignee' => 'nullable|exists:users,id', + 'newTaskDue' => 'nullable|date', + ]); + + $this->issue->tasks()->create([ + 'title' => $this->newTaskTitle, + 'assigned_to' => $this->newTaskAssignee ?: null, + 'due_date' => $this->newTaskDue ?: null, + 'order' => ((int) $this->issue->tasks()->max('order')) + 1, + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + ]); + + $this->reset(['newTaskTitle', 'newTaskAssignee', 'newTaskDue']); + $this->refreshIssue(); + $this->dispatch('notify', 'Tarea añadida'); + } + + public function toggleTask($taskId): void + { + abort_unless($this->canEdit(), 403); + $task = $this->issue->tasks()->findOrFail($taskId); + $done = ! $task->is_done; + $task->update([ + 'is_done' => $done, + 'done_at' => $done ? now() : null, + 'done_by' => $done ? Auth::id() : null, + ]); + $this->refreshIssue(); + } + + public function deleteTask($taskId): void + { + abort_unless($this->canEdit(), 403); + $this->issue->tasks()->findOrFail($taskId)->delete(); + $this->refreshIssue(); + $this->dispatch('notify', 'Tarea eliminada'); + } + + // ── Comments + photos ────────────────────────────────────────────────────────── + + public function addComment(): void + { + // Anyone who can view the issue (and is a member) may comment / report progress. + $this->validate([ + 'newComment' => 'nullable|string|max:5000', + 'commentPhoto' => 'nullable|image|max:20480', // 20 MB + ]); + + if (trim($this->newComment) === '' && ! $this->commentPhoto) { + throw ValidationException::withMessages([ + 'newComment' => 'Escribe un comentario o adjunta una foto.', + ]); + } + + $comment = $this->issue->comments()->create([ + 'user_id' => Auth::id(), + 'body' => trim($this->newComment) ?: '(foto)', + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + ]); + + if ($this->commentPhoto) { + abort_unless(Auth::user()->can('upload media'), 403); + $this->storeUpload($this->commentPhoto, $comment, 'comment'); + } + + $this->reset(['newComment', 'commentPhoto']); + $this->refreshIssue(); + $this->dispatch('notify', 'Comentario añadido'); + } + + public function uploadIssuePhotos(): void + { + abort_unless(Auth::user()->can('upload media'), 403); + $this->validate(['issuePhotos.*' => 'required|image|max:20480']); + + foreach ($this->issuePhotos as $photo) { + $this->storeUpload($photo, $this->issue, 'issue'); + } + + $this->reset('issuePhotos'); + $this->refreshIssue(); + $this->dispatch('notify', 'Fotos subidas'); + } + + public function deleteMedia($mediaId): void + { + $media = \App\Models\Media::findOrFail($mediaId); + $user = Auth::user(); + abort_unless($user->can('delete media') || $media->uploaded_by === $user->id, 403); + $media->delete(); + $this->refreshIssue(); + $this->dispatch('notify', 'Foto eliminada'); + } + + /** Store an uploaded file on the public disk and attach it to a parent model. */ + private function storeUpload($file, $parent, string $entity): void + { + $mime = $file->getMimeType(); + $path = $file->store("uploads/issues/{$this->issue->id}/{$entity}", 'public'); + + $parent->media()->create([ + 'name' => $file->getClientOriginalName(), + 'file_path' => $path, + 'file_type' => $mime, + 'file_extension' => $file->getClientOriginalExtension(), + 'file_size' => $file->getSize(), + 'category' => str_starts_with($mime, 'image/') ? 'image' : 'document', + 'uploaded_by' => Auth::id(), + 'uuid' => (string) \Illuminate\Support\Str::uuid(), + ]); + } + + // ── Status workflow (verification) ─────────────────────────────────────────────── + + public function sendToReview(): void + { + abort_unless($this->canEdit(), 403); + $this->issue->update(['status' => 'in_review']); + $this->refreshIssue(); + $this->dispatch('notify', 'Incidencia enviada a revisión'); + } + + public function verifyResolve(): void + { + abort_unless($this->canEdit(), 403); + $this->issue->update([ + 'status' => 'resolved', + 'resolved_at' => $this->issue->resolved_at ?? now(), + 'resolution_notes' => $this->resolutionNotes ?: null, + ]); + $this->refreshIssue(); + $this->dispatch('notify', 'Incidencia validada y resuelta'); + } + + public function closeIssue(): void + { + abort_unless($this->canEdit(), 403); + $this->issue->update([ + 'status' => 'closed', + 'resolved_at' => $this->issue->resolved_at ?? now(), + ]); + $this->refreshIssue(); + $this->dispatch('notify', 'Incidencia cerrada'); + } + + public function reopen(): void + { + abort_unless($this->canEdit(), 403); + $this->issue->update(['status' => 'open', 'resolved_at' => null]); + $this->refreshIssue(); + $this->dispatch('notify', 'Incidencia reabierta'); + } + + public function render() + { + return view('livewire.issues.issue-detail', [ + 'canEdit' => $this->canEdit(), + ]); + } +} diff --git a/app/Livewire/IssueForm.php b/app/Livewire/IssueForm.php new file mode 100644 index 0000000..e335423 --- /dev/null +++ b/app/Livewire/IssueForm.php @@ -0,0 +1,126 @@ +project = $project; + + if ($issue) { + abort_unless($issue->project_id === $project->id, 404); + } + abort_unless($this->canAccessProject() && Auth::user()->can($issue ? 'edit issues' : 'create issues'), 403); + + $this->projectUsers = $project->users()->orderBy('name')->get(); + + if ($issue) { + $this->issue = $issue; + $this->title = $issue->title; + $this->description = $issue->description ?? ''; + $this->status = $issue->status; + $this->priority = $issue->priority; + $this->assignedTo = $issue->assigned_to ?? ''; + $this->resolutionNotes = $issue->resolution_notes ?? ''; + $this->featureId = $issue->feature_id; + $this->inspectionId = $issue->inspection_id; + } + } + + private function canAccessProject(): bool + { + $user = Auth::user(); + return $user->can('manage all') + || $this->project->users()->where('user_id', $user->id)->exists(); + } + + protected function rules(): array + { + return [ + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'status' => 'required|in:' . implode(',', Issue::STATUSES), + 'priority' => 'required|in:' . implode(',', Issue::PRIORITIES), + 'assignedTo' => 'nullable|exists:users,id', + 'resolutionNotes' => 'nullable|string', + ]; + } + + public function save() + { + abort_unless(Auth::user()->can($this->issue ? 'edit issues' : 'create issues'), 403); + $this->validate(); + + $payload = [ + 'title' => $this->title, + 'description' => $this->description, + 'status' => $this->status, + 'priority' => $this->priority, + 'feature_id' => $this->featureId, + 'inspection_id' => $this->inspectionId, + 'assigned_to' => $this->assignedTo ?: null, + 'resolution_notes' => $this->resolutionNotes ?: null, + ]; + + // Keep resolved_at in sync with the status + $payload['resolved_at'] = in_array($this->status, ['resolved', 'closed']) ? now() : null; + + if ($this->issue) { + // Don't overwrite an existing resolved date + if ($this->issue->resolved_at && in_array($this->status, ['resolved', 'closed'])) { + unset($payload['resolved_at']); + } + $this->issue->update($payload); + $issue = $this->issue; + } else { + $issue = Issue::create(array_merge($payload, [ + 'project_id' => $this->project->id, + 'reported_by' => Auth::id(), + ])); + + $issue->load(['feature', 'assignee']); + $creator = $this->project->creator; + if ($creator && $creator->id !== Auth::id()) { + $creator->notify(new IssueReportedNotification($issue)); + } + if ($issue->assignee && $issue->assignee->id !== Auth::id()) { + $issue->assignee->notify(new IssueReportedNotification($issue)); + } + } + + session()->flash('message', $this->issue ? 'Incidencia actualizada' : 'Incidencia creada'); + + return $this->redirectRoute('projects.issues.show', ['project' => $this->project, 'issue' => $issue], navigate: true); + } + + public function render() + { + return view('livewire.issues.issue-form'); + } +} diff --git a/app/Livewire/IssueManager.php b/app/Livewire/IssueManager.php index 5359224..730d133 100644 --- a/app/Livewire/IssueManager.php +++ b/app/Livewire/IssueManager.php @@ -4,188 +4,51 @@ namespace App\Livewire; use Livewire\Component; use Livewire\Attributes\Layout; +use Livewire\Attributes\On; use Illuminate\Support\Facades\Auth; use App\Models\Project; use App\Models\Issue; -use App\Notifications\IssueReportedNotification; #[Layout('layouts.app')] class IssueManager extends Component { public Project $project; - public $issues = []; - public $projectUsers = []; - - // Form / modal state - public $showForm = false; - public $editingIssue = null; // issue id when editing, null when creating - - // Form fields - public $title = ''; - public $description = ''; - public $status = 'open'; - public $priority = 'medium'; - public $assignedTo = ''; - public $resolutionNotes = ''; - - // Optional context (e.g. when reporting from a map feature) - public $featureId = null; - public $inspectionId = null; - public function mount(Project $project) { $this->project = $project; - $this->loadProjectUsers(); - $this->loadIssues(); + abort_unless($this->canAccessProject() && Auth::user()->can('view issues'), 403); } - public function loadIssues() + /** The current user must be a project member (or super-admin) to touch issues. */ + private function canAccessProject(): bool { - $this->issues = Issue::where('project_id', $this->project->id) - ->with(['feature', 'reporter', 'assignee']) - ->orderBy('created_at', 'desc') - ->get(); + $user = Auth::user(); + return $user->can('manage all') + || $this->project->users()->where('user_id', $user->id)->exists(); } - public function loadProjectUsers() + /** Re-render the stats bar after the embedded table changes an issue. */ + #[On('issuesChanged')] + public function refreshStats(): void { - $this->projectUsers = $this->project->users()->orderBy('name')->get(); - } - - protected function rules(): array - { - return [ - 'title' => 'required|string|max:255', - 'description' => 'nullable|string', - 'status' => 'required|in:' . implode(',', Issue::STATUSES), - 'priority' => 'required|in:' . implode(',', Issue::PRIORITIES), - 'assignedTo' => 'nullable|exists:users,id', - 'resolutionNotes' => 'nullable|string', - ]; - } - - public function openForm($issueId = null) - { - $this->resetForm(); - - if ($issueId) { - $issue = Issue::where('project_id', $this->project->id)->findOrFail($issueId); - $this->editingIssue = $issue->id; - $this->title = $issue->title; - $this->description = $issue->description ?? ''; - $this->status = $issue->status; - $this->priority = $issue->priority; - $this->assignedTo = $issue->assigned_to ?? ''; - $this->resolutionNotes = $issue->resolution_notes ?? ''; - $this->featureId = $issue->feature_id; - $this->inspectionId = $issue->inspection_id; - } - - $this->showForm = true; - } - - public function closeForm() - { - $this->showForm = false; - $this->resetForm(); - } - - private function resetForm(): void - { - $this->reset([ - 'title', 'description', 'assignedTo', 'resolutionNotes', - 'featureId', 'inspectionId', 'editingIssue', - ]); - $this->status = 'open'; - $this->priority = 'medium'; - $this->resetErrorBag(); - } - - public function save() - { - $this->validate(); - - $payload = [ - 'title' => $this->title, - 'description' => $this->description, - 'status' => $this->status, - 'priority' => $this->priority, - 'feature_id' => $this->featureId, - 'inspection_id' => $this->inspectionId, - 'assigned_to' => $this->assignedTo ?: null, - 'resolution_notes' => $this->resolutionNotes ?: null, - ]; - - // Keep resolved_at in sync with the status - if (in_array($this->status, ['resolved', 'closed'])) { - $payload['resolved_at'] = now(); - } else { - $payload['resolved_at'] = null; - } - - if ($this->editingIssue) { - $issue = Issue::where('project_id', $this->project->id)->findOrFail($this->editingIssue); - // Don't overwrite an existing resolved date if it was already resolved - if ($issue->resolved_at && in_array($this->status, ['resolved', 'closed'])) { - unset($payload['resolved_at']); - } - $issue->update($payload); - } else { - $issue = Issue::create(array_merge($payload, [ - 'project_id' => $this->project->id, - 'reported_by' => Auth::id(), - ])); - - if ($issue->wasRecentlyCreated) { - $issue->load(['feature', 'assignee']); - $creator = $this->project->creator; - if ($creator && $creator->id !== Auth::id()) { - $creator->notify(new IssueReportedNotification($issue)); - } - if ($issue->assignee && $issue->assignee->id !== Auth::id()) { - $issue->assignee->notify(new IssueReportedNotification($issue)); - } - } - } - - $this->closeForm(); - $this->loadIssues(); - $this->dispatch('notify', 'Issue guardado correctamente'); - } - - public function resolve($issueId) - { - $issue = Issue::where('project_id', $this->project->id)->findOrFail($issueId); - $issue->update([ - 'status' => 'resolved', - 'resolved_at' => $issue->resolved_at ?? now(), - ]); - $this->loadIssues(); - $this->dispatch('notify', 'Issue marcado como resuelto'); - } - - public function close($issueId) - { - $issue = Issue::where('project_id', $this->project->id)->findOrFail($issueId); - $issue->update([ - 'status' => 'closed', - 'resolved_at' => $issue->resolved_at ?? now(), - ]); - $this->loadIssues(); - $this->dispatch('notify', 'Issue cerrado'); - } - - public function delete($issueId) - { - $issue = Issue::where('project_id', $this->project->id)->findOrFail($issueId); - $issue->delete(); - $this->loadIssues(); - $this->dispatch('notify', 'Issue eliminado'); + // No state to mutate — the listener simply triggers a re-render so the + // stat counters recompute from the database in render(). } public function render() { - return view('livewire.issues.issue-manager'); + $counts = Issue::where('project_id', $this->project->id) + ->selectRaw('status, count(*) as c') + ->groupBy('status') + ->pluck('c', 'status'); + + return view('livewire.issues.issue-manager', [ + 'countOpen' => (int) ($counts['open'] ?? 0), + 'countInReview' => (int) ($counts['in_review'] ?? 0), + 'countResolved' => (int) ($counts['resolved'] ?? 0), + 'countClosed' => (int) ($counts['closed'] ?? 0), + 'countTotal' => (int) $counts->sum(), + ]); } } diff --git a/app/Livewire/IssueTable.php b/app/Livewire/IssueTable.php new file mode 100644 index 0000000..22eb4d4 --- /dev/null +++ b/app/Livewire/IssueTable.php @@ -0,0 +1,217 @@ +setPrimaryKey('id') + ->setDefaultSort('created_at', 'desc') + ->setSortingPillsEnabled(false) + ->setAdditionalSelects(['issues.id as id', 'issues.created_at as created_at']); + } + + public function builder(): Builder + { + // Defence in depth: only members (or super-admin) may list a project's issues. + $user = Auth::user(); + abort_unless( + $user->can('view issues') && ( + $user->can('manage all') + || \App\Models\Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists() + ), + 403 + ); + + return Issue::where('issues.project_id', $this->projectId) + ->with(['feature', 'reporter', 'assignee']) + ->withCount([ + 'comments', + 'media', + 'tasks', + 'tasks as tasks_done_count' => fn (Builder $q) => $q->where('is_done', true), + ]); + } + + public function columns(): array + { + return [ + Column::make('Prioridad', 'priority') + ->sortable() + ->format(function ($value, $row) { + $label = ['low' => 'Bajo', 'medium' => 'Medio', 'high' => 'Alto', 'critical' => 'Crítico'][$value] ?? ucfirst($value); + $textColor = in_array($value, ['critical', 'high']) ? '#fff' : '#1f2937'; + return ''.$label.''; + }) + ->html(), + + Column::make('Título', 'title') + ->sortable() + ->searchable() + ->format(function ($value, $row) { + $url = route('projects.issues.show', [$this->projectId, $row->id]); + $html = ''.e($value).''; + if ($row->description) { + $html .= '
'.e(Str::limit($row->description, 60)).'
'; + } + $meta = []; + if ($row->reporter) $meta[] = 'Reportado por '.e($row->reporter->name); + if ($row->comments_count) $meta[] = '💬 '.$row->comments_count; + if ($row->media_count) $meta[] = '📷 '.$row->media_count; + if ($meta) { + $html .= '
'.implode(' · ', $meta).'
'; + } + if ($row->tasks_count) { + $pct = (int) round($row->tasks_done_count / $row->tasks_count * 100); + $html .= '
+ + '.$row->tasks_done_count.'/'.$row->tasks_count.' tareas +
'; + } + return $html; + }) + ->html(), + + Column::make('Feature') + ->label(fn ($row) => $row->feature + ? ''.e($row->feature->name).'' + : '') + ->html(), + + Column::make('Estado', 'status') + ->sortable() + ->format(function ($value, $row) { + $label = ['open' => 'Abierto', 'in_review' => 'En revisión', 'resolved' => 'Resuelto', 'closed' => 'Cerrado'][$value] ?? ucfirst($value); + return ''.$label.''; + }) + ->html(), + + Column::make('Asignado a') + ->label(fn ($row) => $row->assignee + ? ''.e($row->assignee->name).'' + : 'Sin asignar') + ->html(), + + Column::make('Fecha', 'created_at') + ->sortable() + ->format(function ($value, $row) { + $html = $row->created_at->format('d/m/Y'); + if ($row->resolved_at) { + $html .= '
Res. '.$row->resolved_at->format('d/m/Y').'
'; + } + return $html; + }) + ->html(), + + Column::make('Acciones') + ->label(function ($row) { + $user = Auth::user(); + $detail = route('projects.issues.show', [$this->projectId, $row->id]); + $edit = route('projects.issues.edit', [$this->projectId, $row->id]); + + $html = '
'; + $html .= ' + + '; + + if ($user->can('edit issues')) { + $html .= ' + + '; + if (in_array($row->status, ['open', 'in_review'])) { + $html .= ''; + } + if ($row->status !== 'closed') { + $html .= ''; + } + } + + if ($user->can('delete issues')) { + $html .= ''; + } + + $html .= '
'; + return $html; + }) + ->html(), + ]; + } + + public function filters(): array + { + return [ + SelectFilter::make('Estado', 'status') + ->options([ + '' => 'Estado: todos', + 'open' => 'Abierto', + 'in_review' => 'En revisión', + 'resolved' => 'Resuelto', + 'closed' => 'Cerrado', + ]) + ->filter(fn (Builder $query, string $value) => $query->where('issues.status', $value)), + + SelectFilter::make('Prioridad', 'priority') + ->options([ + '' => 'Prioridad: todas', + 'critical' => 'Crítica', + 'high' => 'Alta', + 'medium' => 'Media', + 'low' => 'Baja', + ]) + ->filter(fn (Builder $query, string $value) => $query->where('issues.priority', $value)), + ]; + } + + // ── Row actions ──────────────────────────────────────────────────────────────── + + private function findIssue(int $id): Issue + { + return Issue::where('project_id', $this->projectId)->findOrFail($id); + } + + public function resolve(int $id): void + { + abort_unless(Auth::user()->can('edit issues'), 403); + $issue = $this->findIssue($id); + $issue->update(['status' => 'resolved', 'resolved_at' => $issue->resolved_at ?? now()]); + $this->dispatch('issuesChanged'); + $this->dispatch('notify', 'Incidencia marcada como resuelta'); + } + + public function close(int $id): void + { + abort_unless(Auth::user()->can('edit issues'), 403); + $issue = $this->findIssue($id); + $issue->update(['status' => 'closed', 'resolved_at' => $issue->resolved_at ?? now()]); + $this->dispatch('issuesChanged'); + $this->dispatch('notify', 'Incidencia cerrada'); + } + + public function deleteIssue(int $id): void + { + abort_unless(Auth::user()->can('delete issues'), 403); + $this->findIssue($id)->delete(); + $this->dispatch('issuesChanged'); + $this->dispatch('notify', 'Incidencia eliminada'); + } +} diff --git a/app/Models/Issue.php b/app/Models/Issue.php index 7a774c9..75d3e82 100644 --- a/app/Models/Issue.php +++ b/app/Models/Issue.php @@ -28,10 +28,28 @@ class Issue extends Model public function reporter() { return $this->belongsTo(User::class, 'reported_by'); } public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); } public function media() { return $this->morphMany(Media::class, 'mediable'); } + public function tasks() { return $this->hasMany(IssueTask::class)->orderBy('order')->orderBy('id'); } + public function comments() { return $this->hasMany(IssueComment::class)->orderBy('created_at'); } public function scopeOpen($q) { return $q->where('status', 'open'); } public function scopeCritical($q) { return $q->where('priority', 'critical'); } + /** Resolution progress derived from the checklist: done tasks / total. */ + public function getProgressAttribute(): int + { + $total = $this->tasks->count(); + if ($total === 0) { + return in_array($this->status, ['resolved', 'closed'], true) ? 100 : 0; + } + return (int) round($this->tasks->where('is_done', true)->count() / $total * 100); + } + + /** True when there is at least one task and all of them are done. */ + public function getTasksCompleteAttribute(): bool + { + return $this->tasks->count() > 0 && $this->tasks->every(fn ($t) => $t->is_done); + } + public function getPriorityColorAttribute(): string { return match($this->priority) { diff --git a/app/Models/IssueComment.php b/app/Models/IssueComment.php new file mode 100644 index 0000000..0cb50eb --- /dev/null +++ b/app/Models/IssueComment.php @@ -0,0 +1,20 @@ +belongsTo(Issue::class); } + public function user() { return $this->belongsTo(User::class); } + public function media() { return $this->morphMany(Media::class, 'mediable'); } +} diff --git a/app/Models/IssueTask.php b/app/Models/IssueTask.php new file mode 100644 index 0000000..bb3f51c --- /dev/null +++ b/app/Models/IssueTask.php @@ -0,0 +1,34 @@ + 'boolean', + 'done_at' => 'datetime', + 'due_date' => 'date', + ]; + + public function issue() { return $this->belongsTo(Issue::class); } + public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); } + public function completer() { return $this->belongsTo(User::class, 'done_by'); } + public function media() { return $this->morphMany(Media::class, 'mediable'); } + + /** Overdue = has a due date in the past and not yet done. */ + public function getIsOverdueAttribute(): bool + { + return ! $this->is_done && $this->due_date && $this->due_date->isPast(); + } +} diff --git a/database/migrations/2026_06_18_120000_create_issue_tasks_table.php b/database/migrations/2026_06_18_120000_create_issue_tasks_table.php new file mode 100644 index 0000000..4aa50cc --- /dev/null +++ b/database/migrations/2026_06_18_120000_create_issue_tasks_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete(); + $table->string('title'); + $table->boolean('is_done')->default(false); + $table->timestamp('done_at')->nullable(); + $table->foreignId('done_by')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('assigned_to')->nullable()->constrained('users')->nullOnDelete(); + $table->date('due_date')->nullable(); + $table->integer('order')->default(0); + + // Offline sync + $table->uuid('uuid')->nullable()->unique(); + $table->timestamp('client_updated_at')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('issue_tasks'); + } +}; diff --git a/database/migrations/2026_06_18_120100_create_issue_comments_table.php b/database/migrations/2026_06_18_120100_create_issue_comments_table.php new file mode 100644 index 0000000..de7074b --- /dev/null +++ b/database/migrations/2026_06_18_120100_create_issue_comments_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users'); + $table->text('body'); + + // Offline sync + $table->uuid('uuid')->nullable()->unique(); + $table->timestamp('client_updated_at')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('issue_comments'); + } +}; diff --git a/docs/MOBILE_SYNC_PROTOCOL.md b/docs/MOBILE_SYNC_PROTOCOL.md index 45b755e..02afe20 100644 --- a/docs/MOBILE_SYNC_PROTOCOL.md +++ b/docs/MOBILE_SYNC_PROTOCOL.md @@ -110,3 +110,17 @@ Respuesta por operación: - **Fase C — PUSH:** `/sync` idempotente con validación/autorización/conflictos (recoge y endurece la lógica actual). - **Fase D — Media:** subida multipart + descarga. - **Fase E — Endurecimiento + Docs:** rate-limit, `sync_logs`, OpenAPI/Swagger como contrato para el equipo móvil. + +--- + +## Addendum (2026-06-18): Incidencias enriquecidas — tareas, comentarios y fotos + +El detalle de una incidencia incluye ahora un **checklist de tareas** y un **hilo de comentarios**, ambos con fotos. Todo es sincronizable offline: + +- **Nuevas entidades de PULL** en el `bundle` (y en `deleted`): `issue_tasks`, `issue_comments`. +- **Nuevas operaciones de PUSH** en `/sync` (idempotentes por `uuid`): + - `issue_task.create` — `data`: `issue_id`, `title`, `assigned_to?`, `due_date?`, `is_done?`. Requiere `edit issues`. + - `issue_task.update` — `data`: `id`, y cualquiera de `title`/`assigned_to`/`due_date`/`is_done`. Last-write-wins por `client_updated_at`. Requiere `edit issues`. + - `issue_comment.create` — `data`: `issue_id`, `body`. Requiere `view issues`. +- **Fotos**: `POST /media` admite `parent_entity` = `issue_task` y `issue_comment` (además de `issue`). Requiere `upload media`. +- El **% de avance** de la incidencia se deriva de las tareas completadas (no se almacena ni se sincroniza). diff --git a/docs/openapi.yaml b/docs/openapi.yaml index b2eee13..30e168f 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -130,7 +130,7 @@ paths: required: [uuid, parent_entity, parent_id, file] properties: uuid: { type: string, format: uuid } - parent_entity: { type: string, enum: [feature, issue, project, phase, layer] } + parent_entity: { type: string, enum: [feature, issue, issue_task, issue_comment, project, phase, layer] } parent_id: { type: integer } file: { type: string, format: binary } category: { type: string, enum: [image, document, other] } @@ -156,7 +156,7 @@ components: type: object required: [entity, op, uuid, data] properties: - entity: { type: string, enum: [progress_update, inspection, issue, feature] } + entity: { type: string, enum: [progress_update, inspection, issue, issue_task, issue_comment, feature] } op: { type: string, enum: [create, update] } uuid: { type: string, format: uuid, description: client-generated idempotency key } client_updated_at: { type: string, format: date-time } @@ -185,6 +185,8 @@ components: features: { type: array, items: { type: object } } inspections: { type: array, items: { type: object } } issues: { type: array, items: { type: object } } + issue_tasks: { type: array, items: { type: object } } + issue_comments: { type: array, items: { type: object } } templates: { type: array, items: { type: object } } media: { type: array, items: { type: object } } deleted: @@ -196,3 +198,5 @@ components: features: { type: array, items: { type: integer } } inspections: { type: array, items: { type: integer } } issues: { type: array, items: { type: integer } } + issue_tasks: { type: array, items: { type: integer } } + issue_comments: { type: array, items: { type: integer } } diff --git a/public/images/.gitkeep b/public/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/images/logo-rte.png b/public/images/logo-rte.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d4744d9ea977b9b972bba821d366bf9eacc021 GIT binary patch literal 94363 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�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 - - +RTE International — MAI Group diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php index b4aac35..ff92e42 100644 --- a/resources/views/layouts/guest.blade.php +++ b/resources/views/layouts/guest.blade.php @@ -21,7 +21,7 @@

diff --git a/resources/views/livewire/issues/issue-detail.blade.php b/resources/views/livewire/issues/issue-detail.blade.php new file mode 100644 index 0000000..27ac511 --- /dev/null +++ b/resources/views/livewire/issues/issue-detail.blade.php @@ -0,0 +1,304 @@ +
+ + {{-- ================================================================ + BACK + HEADER + ================================================================ --}} + @php + $statusLabel = [ + 'open' => 'Abierto', 'in_review' => 'En revisión', + 'resolved' => 'Resuelto', 'closed' => 'Cerrado', + ][$issue->status] ?? $issue->status; + $priorityLabel = [ + 'low' => 'Baja', 'medium' => 'Media', 'high' => 'Alta', 'critical' => 'Crítica', + ][$issue->priority] ?? $issue->priority; + $doneCount = $issue->tasks->where('is_done', true)->count(); + $totalCount = $issue->tasks->count(); + @endphp + + + Volver a incidencias + + +
+
+

{{ $issue->title }}

+
+ {{ $statusLabel }} + Prioridad: {{ $priorityLabel }} + @if($issue->feature) + {{ $issue->feature->name }} + @endif +
+
+ Reportado por {{ $issue->reporter?->name ?? '—' }} + el {{ $issue->created_at->format('d/m/Y H:i') }} + @if($issue->assignee) · Asignado a {{ $issue->assignee->name }} @endif +
+
+ + {{-- Resolution progress --}} + @if($totalCount > 0) +
+
+ {{ $issue->progress }}% +
+
{{ $doneCount }}/{{ $totalCount }} tareas
+
+ @endif +
+ + @if($issue->description) +
{{ $issue->description }}
+ @endif + + {{-- ================================================================ + STATUS WORKFLOW BAR + ================================================================ --}} + @if($canEdit) +
+ Acciones: + + @if(in_array($issue->status, ['open'])) + + @if($totalCount > 0 && $doneCount < $totalCount) + Completa todas las tareas para enviar a revisión + @endif + @endif + + @if(in_array($issue->status, ['open', 'in_review'])) + + @endif + + @if($issue->status !== 'closed') + + @endif + + @if(in_array($issue->status, ['resolved', 'closed'])) + + @endif +
+ @endif + +
+ + {{-- ================================================================ + LEFT: CHECKLIST + COMMENTS + ================================================================ --}} +
+ + {{-- CHECKLIST --}} +
+
+

+ + Tareas para resolver + {{ $doneCount }}/{{ $totalCount }} +

+ + @if($totalCount > 0) + + @endif + +
    + @forelse($issue->tasks as $task) +
  • + is_done) + @disabled(! $canEdit) + wire:click="toggleTask({{ $task->id }})" /> +
    +
    {{ $task->title }}
    +
    + @if($task->assignee)👤 {{ $task->assignee->name }}@endif + @if($task->due_date) + 📅 {{ $task->due_date->format('d/m/Y') }} + @endif + @if($task->is_done && $task->completer) + ✓ {{ $task->completer->name }} · {{ $task->done_at?->format('d/m/Y') }} + @endif +
    +
    + @if($canEdit) + + @endif +
  • + @empty +
  • Aún no hay tareas. Añade la primera abajo.
  • + @endforelse +
+ + @if($canEdit) +
+
+ + @error('newTaskTitle'){{ $message }}@enderror +
+ + + +
+ @endif +
+
+ + {{-- COMMENTS / SEGUIMIENTO --}} +
+
+

+ + Seguimiento y comentarios + {{ $issue->comments->count() }} +

+ +
+ @forelse($issue->comments as $comment) +
+ + {{ strtoupper(substr($comment->user?->name ?? '?', 0, 1)) }} + +
+
+ {{ $comment->user?->name ?? 'Usuario' }} + · {{ $comment->created_at->diffForHumans() }} +
+
{{ $comment->body }}
+ @if($comment->media->count()) +
+ @foreach($comment->media as $m) + + + + @endforeach +
+ @endif +
+
+ @empty +

Sin comentarios todavía.

+ @endforelse +
+ + {{-- New comment --}} +
+ + @error('newComment'){{ $message }}@enderror +
+ + +
+ @error('commentPhoto'){{ $message }}@enderror +
+
+
+
+ + {{-- ================================================================ + RIGHT: PHOTOS + RESOLUTION + ================================================================ --}} +
+ + {{-- ISSUE PHOTOS --}} +
+
+

+ Fotos de la incidencia +

+ + @if($issue->media->count()) +
+ @foreach($issue->media as $m) +
+ + + + @can('delete media') + + @endcan +
+ @endforeach +
+ @else +

Sin fotos.

+ @endif + + @can('upload media') +
+ + @error('issuePhotos.*'){{ $message }}@enderror + @if($issuePhotos) + + @endif +
+ @endcan +
+
+ + {{-- RESOLUTION NOTES --}} + @if($canEdit) +
+
+

+ Notas de resolución +

+ + +
+
+ @elseif($issue->resolution_notes) +
+
+

Notas de resolución

+

{{ $issue->resolution_notes }}

+
+
+ @endif +
+
+
diff --git a/resources/views/livewire/issues/issue-form.blade.php b/resources/views/livewire/issues/issue-form.blade.php new file mode 100644 index 0000000..10f9af8 --- /dev/null +++ b/resources/views/livewire/issues/issue-form.blade.php @@ -0,0 +1,119 @@ +
+ + + Volver a incidencias + + +
+
+

+ {{ $issue ? 'Editar incidencia' : 'Nueva incidencia' }} +

+

{{ $project->name }}

+ +
+ + {{-- Título --}} +
+ + + @error('title') + + @enderror +
+ + {{-- Descripción --}} +
+ + + @error('description') + + @enderror +
+ + {{-- Prioridad + Estado --}} +
+
+ + + @error('priority') + + @enderror +
+ +
+ + + @error('status') + + @enderror +
+
+ + {{-- Asignado a --}} +
+ + + @error('assignedTo') + + @enderror +
+ + {{-- Notas de resolución (visible when status = resolved or closed) --}} + @if(in_array($status, ['resolved', 'closed'])) +
+ + + @error('resolutionNotes') + + @enderror +
+ @endif + + {{-- Footer --}} +
+ Cancelar + +
+
+
+
+
diff --git a/resources/views/livewire/issues/issue-manager.blade.php b/resources/views/livewire/issues/issue-manager.blade.php index a6388b2..4888334 100644 --- a/resources/views/livewire/issues/issue-manager.blade.php +++ b/resources/views/livewire/issues/issue-manager.blade.php @@ -4,28 +4,24 @@ ================================================================ --}}
-

Issues del proyecto

+

Incidencias del proyecto

Gestión de incidencias y problemas

- + @can('create issues') + + + Nueva incidencia + + @endcan
{{-- ================================================================ STATS BAR ================================================================ --}} - @php - $countOpen = $issues->where('status', 'open')->count(); - $countInReview = $issues->where('status', 'in_review')->count(); - $countResolved = $issues->where('status', 'resolved')->count(); - $countClosed = $issues->where('status', 'closed')->count(); - @endphp -
Abiertos
@@ -45,359 +41,12 @@
Total
-
{{ $issues->count() }}
+
{{ $countTotal }}
{{-- ================================================================ - ISSUES TABLE + ISSUES TABLE (Rappasoft) ================================================================ --}} - @if($issues->isEmpty()) -
- -

Sin issues registrados

-

Crea el primer issue con el botón "Nuevo Issue".

-
- @else -
- - - - - - - - - - - - - - @foreach($issues as $issue) - - {{-- Prioridad --}} - - - {{-- Título + descripción breve --}} - - - {{-- Feature --}} - - - {{-- Estado --}} - - - {{-- Asignado a --}} - - - {{-- Fecha --}} - - - {{-- Acciones --}} - - - @endforeach - -
PrioridadTítuloEstadoAcciones
- @php - $pClass = match($issue->priority) { - 'critical' => 'badge-purple', - 'high' => 'badge-error', - 'medium' => 'badge-warning', - 'low' => 'badge-ghost', - default => 'badge-ghost', - }; - $pLabel = match($issue->priority) { - 'critical' => 'Crítico', - 'high' => 'Alto', - 'medium' => 'Medio', - 'low' => 'Bajo', - default => ucfirst($issue->priority), - }; - @endphp - - {{ $pLabel }} - - -
{{ $issue->title }}
- @if($issue->description) -
{{ Str::limit($issue->description, 60) }}
- @endif - @if($issue->reporter) -
- Reportado por {{ $issue->reporter->name }} -
- @endif -
- @php - $sLabel = match($issue->status) { - 'open' => 'Abierto', - 'in_review' => 'En revisión', - 'resolved' => 'Resuelto', - 'closed' => 'Cerrado', - default => ucfirst($issue->status), - }; - @endphp - - {{ $sLabel }} - - -
- {{-- Editar --}} - - - {{-- Resolver --}} - @if(in_array($issue->status, ['open', 'in_review'])) - - @endif - - {{-- Cerrar --}} - @if($issue->status !== 'closed') - - @endif - - {{-- Eliminar --}} - -
-
-
- @endif - - {{-- ================================================================ - MODAL FORM (create / edit) - ================================================================ --}} - @if($showForm) - {{-- Overlay --}} -
- - {{-- Modal panel --}} - - @endif + diff --git a/resources/views/livewire/layout/navigation.blade.php b/resources/views/livewire/layout/navigation.blade.php index a96fa9e..3071626 100644 --- a/resources/views/livewire/layout/navigation.blade.php +++ b/resources/views/livewire/layout/navigation.blade.php @@ -26,7 +26,7 @@ new class extends Component diff --git a/routes/web.php b/routes/web.php index ffdc385..bd81dbc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -119,6 +119,9 @@ Route::get('/reports/dashboard', ReportsDashboard::class)->name('reports.dashboa // Issues del proyecto Route::get('/projects/{project}/issues', \App\Livewire\IssueManager::class)->name('projects.issues'); + Route::get('/projects/{project}/issues/create', \App\Livewire\IssueForm::class)->name('projects.issues.create'); + Route::get('/projects/{project}/issues/{issue}', \App\Livewire\IssueDetail::class)->name('projects.issues.show'); + Route::get('/projects/{project}/issues/{issue}/edit', \App\Livewire\IssueForm::class)->name('projects.issues.edit'); // Dashboard por proyecto Route::get('/projects/{project}/dashboard', \App\Livewire\ProjectDashboard::class)->name('projects.dashboard'); diff --git a/tests/Feature/Api/MobileApiTest.php b/tests/Feature/Api/MobileApiTest.php index a7825b5..4710d88 100644 --- a/tests/Feature/Api/MobileApiTest.php +++ b/tests/Feature/Api/MobileApiTest.php @@ -6,6 +6,8 @@ use App\Models\Feature; use App\Models\Inspection; use App\Models\InspectionTemplate; use App\Models\Issue; +use App\Models\IssueComment; +use App\Models\IssueTask; use App\Models\Layer; use App\Models\Phase; use App\Models\Project; @@ -28,7 +30,7 @@ class MobileApiTest extends TestCase protected function setUp(): void { parent::setUp(); - foreach (['update progress', 'manage all', 'create inspections', 'create issues', 'edit issues', 'upload media'] as $p) { + foreach (['update progress', 'manage all', 'create inspections', 'view issues', 'create issues', 'edit issues', 'upload media'] as $p) { Permission::findOrCreate($p); } } @@ -407,6 +409,126 @@ class MobileApiTest extends TestCase $this->assertEquals(1, \App\Models\Media::where('uuid', $uuid)->count()); } + // ── Issue tasks / comments (enriquecimiento incidencias) ───────────────────── + + private function makeIssue(Project $project, ?User $reporter = null): Issue + { + return Issue::create([ + 'project_id' => $project->id, + 'title' => 'Incidencia test', + 'status' => 'open', + 'priority' => 'medium', + 'reported_by' => $reporter?->id ?? $project->created_by, + ]); + } + + public function test_sync_creates_and_updates_an_issue_task(): void + { + $user = User::factory()->create(); + $user->givePermissionTo('edit issues'); + $project = $this->makeProject($user); + $issue = $this->makeIssue($project, $user); + + Sanctum::actingAs($user, ['mobile-sync']); + + // create task + $uuid = (string) Str::uuid(); + $this->postJson('/api/v1/sync', ['operations' => [[ + 'entity' => 'issue_task', 'op' => 'create', 'uuid' => $uuid, + 'data' => ['issue_id' => $issue->id, 'title' => 'Sanear zona'], + ]]])->assertOk()->assertJsonPath('results.0.status', 'applied'); + + $task = IssueTask::where('uuid', $uuid)->firstOrFail(); + $this->assertFalse($task->is_done); + + // mark done via update + $this->postJson('/api/v1/sync', ['operations' => [[ + 'entity' => 'issue_task', 'op' => 'update', 'uuid' => (string) Str::uuid(), + 'data' => ['id' => $task->id, 'is_done' => true], + ]]])->assertOk()->assertJsonPath('results.0.status', 'applied'); + + $task->refresh(); + $this->assertTrue($task->is_done); + $this->assertEquals($user->id, $task->done_by); + $this->assertNotNull($task->done_at); + } + + public function test_sync_issue_task_is_forbidden_without_edit_permission(): void + { + $user = User::factory()->create(); // member but no 'edit issues' + $project = $this->makeProject($user); + $issue = $this->makeIssue($project, $user); + + Sanctum::actingAs($user, ['mobile-sync']); + $this->postJson('/api/v1/sync', ['operations' => [[ + 'entity' => 'issue_task', 'op' => 'create', 'uuid' => (string) Str::uuid(), + 'data' => ['issue_id' => $issue->id, 'title' => 'X'], + ]]])->assertOk()->assertJsonPath('results.0.status', 'error'); + + $this->assertDatabaseCount('issue_tasks', 0); + } + + public function test_sync_creates_an_issue_comment(): void + { + $user = User::factory()->create(); + $user->givePermissionTo('view issues'); + $project = $this->makeProject($user); + $issue = $this->makeIssue($project, $user); + + Sanctum::actingAs($user, ['mobile-sync']); + $uuid = (string) Str::uuid(); + $this->postJson('/api/v1/sync', ['operations' => [[ + 'entity' => 'issue_comment', 'op' => 'create', 'uuid' => $uuid, + 'data' => ['issue_id' => $issue->id, 'body' => 'Revisado en obra'], + ]]])->assertOk()->assertJsonPath('results.0.status', 'applied'); + + $this->assertDatabaseHas('issue_comments', [ + 'uuid' => $uuid, 'issue_id' => $issue->id, 'user_id' => $user->id, 'body' => 'Revisado en obra', + ]); + } + + public function test_media_uploads_to_issue_task_and_comment(): void + { + Storage::fake('public'); + + $user = User::factory()->create(); + $user->givePermissionTo('upload media'); + $project = $this->makeProject($user); + $issue = $this->makeIssue($project, $user); + $task = $issue->tasks()->create(['title' => 'T', 'uuid' => (string) Str::uuid()]); + $comment = $issue->comments()->create(['user_id' => $user->id, 'body' => 'c', 'uuid' => (string) Str::uuid()]); + + Sanctum::actingAs($user, ['mobile-sync']); + + $this->post('/api/v1/media', [ + 'uuid' => (string) Str::uuid(), 'parent_entity' => 'issue_task', 'parent_id' => $task->id, + 'file' => UploadedFile::fake()->image('a.jpg'), + ])->assertOk()->assertJsonPath('status', 'applied'); + + $this->post('/api/v1/media', [ + 'uuid' => (string) Str::uuid(), 'parent_entity' => 'issue_comment', 'parent_id' => $comment->id, + 'file' => UploadedFile::fake()->image('b.jpg'), + ])->assertOk()->assertJsonPath('status', 'applied'); + + $this->assertDatabaseHas('media', ['mediable_type' => IssueTask::class, 'mediable_id' => $task->id]); + $this->assertDatabaseHas('media', ['mediable_type' => IssueComment::class, 'mediable_id' => $comment->id]); + } + + public function test_bundle_includes_issue_tasks_and_comments(): void + { + $user = User::factory()->create(); + $project = $this->makeProject($user); + $issue = $this->makeIssue($project, $user); + $task = $issue->tasks()->create(['title' => 'Tarea bundle', 'uuid' => (string) Str::uuid()]); + $comment = $issue->comments()->create(['user_id' => $user->id, 'body' => 'Comentario bundle', 'uuid' => (string) Str::uuid()]); + + Sanctum::actingAs($user, ['mobile-sync']); + $res = $this->getJson("/api/v1/projects/{$project->id}/bundle")->assertOk(); + + $this->assertTrue(collect($res->json('issue_tasks'))->pluck('id')->contains($task->id)); + $this->assertTrue(collect($res->json('issue_comments'))->pluck('id')->contains($comment->id)); + } + public function test_sync_operation_is_idempotent_via_sync_logs(): void { $user = User::factory()->create(); diff --git a/tests/Feature/IssuesTablePageTest.php b/tests/Feature/IssuesTablePageTest.php new file mode 100644 index 0000000..6891112 --- /dev/null +++ b/tests/Feature/IssuesTablePageTest.php @@ -0,0 +1,87 @@ +create(); + $user->givePermissionTo(['view issues', 'edit issues', 'delete issues']); + + $project = Project::create([ + 'reference' => 'TBL-1', + 'name' => 'Proyecto Tabla', + 'address' => 'Calle Falsa 123', + 'lat' => 40.0, + 'lng' => -3.0, + 'start_date' => now()->toDateString(), + 'end_date_estimated' => now()->addMonths(6)->toDateString(), + 'status' => 'in_progress', + 'created_by' => $user->id, + ]); + $project->users()->attach($user->id, ['role_in_project' => 'supervisor']); + + $issue = Issue::create([ + 'project_id' => $project->id, + 'title' => 'Grieta en muro', + 'status' => 'open', + 'priority' => 'high', + 'reported_by' => $user->id, + ]); + + return [$user, $project, $issue]; + } + + public function test_issue_manager_renders_stats_and_table(): void + { + [$user, $project, $issue] = $this->memberWithIssue(); + + Livewire::actingAs($user) + ->test(IssueManager::class, ['project' => $project]) + ->assertOk() + ->assertSee('Incidencias del proyecto') + ->assertSeeLivewire(IssueTable::class); + } + + public function test_issue_table_lists_and_resolves_an_issue(): void + { + [$user, $project, $issue] = $this->memberWithIssue(); + + Livewire::actingAs($user) + ->test(IssueTable::class, ['projectId' => $project->id]) + ->assertOk() + ->assertSee('Grieta en muro') + ->call('resolve', $issue->id); + + $this->assertEquals('resolved', $issue->fresh()->status); + } + + public function test_issue_table_forbidden_for_non_member(): void + { + [, $project] = $this->memberWithIssue(); + + $outsider = User::factory()->create(); + $outsider->givePermissionTo('view issues'); + + Livewire::actingAs($outsider) + ->test(IssueTable::class, ['projectId' => $project->id]) + ->assertForbidden(); + } +}