templates = $this->project->inspectionTemplates() ->orderBy('inspection_templates.name') ->get(); } public function loadInspectionHistory() { if (! $this->selectedFeature) { $this->inspectionHistory = []; return; } $this->inspectionHistory = Inspection::where('feature_id', $this->selectedFeature->id) ->with('user', 'template') ->orderBy('created_at', 'desc') ->get(); } public function resetInspectionForm() { $this->inspectionFormData = []; $this->inspectionResult = ''; $this->inspectionNotes = ''; $this->inspectionPhotos = []; if ($this->selectedTemplateId) { $template = InspectionTemplate::find($this->selectedTemplateId); if ($template) { foreach ($template->fields as $field) { $this->inspectionFormData[$field['name']] = ''; } } } } public function saveInspection() { if (! $this->selectedFeature || ! $this->selectedTemplateId) { $this->dispatch('notify', 'Selecciona un elemento y un template.'); return; } // Verificar permiso if (! auth()->user()->can('create inspections')) { $this->dispatch('notify', 'Sin permisos para crear inspecciones.'); return; } $feature = Feature::with('layer.phase')->find($this->selectedFeature->id); if (! $feature || $feature->layer->phase->project_id !== $this->project->id) { abort(403); } $this->validate([ 'selectedTemplateId' => 'required|exists:inspection_templates,id', 'inspectionPhotos.*' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:10240', ]); $template = InspectionTemplate::find($this->selectedTemplateId); foreach ($template->fields as $field) { if (($field['required'] ?? false) && empty($this->inspectionFormData[$field['name']])) { $this->dispatch('notify', "El campo {$field['label']} es obligatorio."); return; } } $inspection = Inspection::create([ 'project_id' => $this->project->id, 'layer_id' => $this->selectedFeature->layer_id, 'feature_id' => $this->selectedFeature->id, 'template_id' => $this->selectedTemplateId, 'user_id' => auth()->id(), 'inspector_user_id' => auth()->id(), 'status' => 'completed', 'completed_at' => now(), 'result' => $this->inspectionResult ?: null, 'notes' => $this->inspectionNotes ?: null, 'data' => $this->inspectionFormData, ]); // Fotos adjuntas a la inspección foreach ($this->inspectionPhotos as $photo) { $mime = $photo->getMimeType(); $path = $photo->store("uploads/inspections/{$inspection->id}", 'public'); $inspection->media()->create([ 'name' => $photo->getClientOriginalName(), 'file_path' => $path, 'file_type' => $mime, 'file_extension' => $photo->getClientOriginalExtension(), 'file_size' => $photo->getSize(), 'category' => str_starts_with($mime, 'image/') ? 'image' : 'document', 'uploaded_by' => auth()->id(), 'uuid' => (string) Str::uuid(), ]); } if ($this->inspectionResult === 'fail') { Issue::create([ 'project_id' => $this->project->id, 'feature_id' => $this->selectedFeature->id, 'inspection_id' => $inspection->id, 'title' => 'Fallo en inspección: '.($template->name ?? 'Sin nombre'), 'description' => $this->inspectionNotes, 'priority' => 'high', 'status' => 'open', 'reported_by' => auth()->id(), ]); $this->openIssuesCount = Issue::where('project_id', $this->project->id) ->where('status', 'open')->count(); $this->dispatch('notify', 'Inspección fallida — Issue creado automáticamente'); } else { if (isset($this->inspectionFormData['progress'])) { $this->updateProgress($this->selectedFeature->id, (int) $this->inspectionFormData['progress'], 'Inspección registrada'); } $this->dispatch('notify', 'Inspección guardada correctamente'); } // Notificar a usuarios del proyecto (excepto creador) $usersToNotify = $this->project->users() ->where('user_id', '!=', auth()->id()) ->get(); foreach ($usersToNotify as $user) { $user->notify(new InspectionCompletedNotification($inspection)); } // Reload global list $this->allInspections = Inspection::where('project_id', $this->project->id) ->with(['feature.layer.phase', 'template', 'user']) ->orderBy('created_at', 'desc') ->get(); $this->loadInspectionHistory(); $this->resetInspectionForm(); } public function assignTemplateToFeature($templateId) { if (! $this->selectedFeature) { return; } $template = InspectionTemplate::where('id', $templateId) ->where('project_id', $this->project->id)->first(); if (! $template) { abort(403); } $feature = Feature::findOrFail($this->selectedFeature->id); $feature->template_id = $templateId; $feature->save(); $this->selectedFeature = $feature; $this->selectedTemplateId = $templateId; $this->resetInspectionForm(); $this->dispatch('notify', 'Template asignado al elemento'); } // ─── Inspection viewer ────────────────────────────────────────────────────── #[On('map-view-inspection')] public function viewInspection($id) { $ins = Inspection::where('project_id', $this->project->id) ->with(['feature.layer.phase', 'template', 'user', 'media']) ->find($id); if (! $ins) { return; } $this->viewingInspection = [ 'id' => $ins->id, 'feature_name' => $ins->feature?->name ?? '—', 'layer_name' => $ins->feature?->layer?->name ?? '—', 'phase_name' => $ins->feature?->layer?->phase?->name ?? '—', 'template_name' => $ins->template?->name ?? '—', 'user_name' => $ins->user?->name ?? '—', 'date' => $ins->created_at->format('d/m/Y H:i'), 'status' => $ins->status, 'result' => $ins->result, 'notes' => $ins->notes, 'data' => $ins->data ?? [], 'fields' => $ins->template?->fields ?? [], 'photos' => $ins->media->map(fn ($m) => ['url' => $m->url, 'name' => $m->name, 'id' => $m->id])->values()->all(), ]; } public function closeViewInspection() { $this->viewingInspection = null; } // ─── Inspection Editor (editar inspección existente) ───────────────────────── #[On('edit-inspection')] public function editInspection($id) { $ins = Inspection::where('project_id', $this->project->id) ->with(['feature.layer.phase', 'template', 'user', 'media']) ->find($id); if (! $ins) { return; } $this->editingInspection = $ins; $this->editInspectionFormData = $ins->data ?? []; $this->editInspectionResult = $ins->result ?? ''; $this->editInspectionNotes = $ins->notes ?? ''; $this->editInspectionPhotos = []; $this->editInspectionPhotosToDelete = []; $this->selectedTemplateId = $ins->template_id; $this->dispatch('openEditInspectionModal'); } public function closeEditInspection() { $this->editingInspection = null; $this->editInspectionFormData = []; $this->editInspectionResult = ''; $this->editInspectionNotes = ''; $this->editInspectionPhotos = []; $this->editInspectionPhotosToDelete = []; } public function deleteEditPhoto($mediaIndex) { if (! $this->editingInspection) { return; } $media = $this->editingInspection->media; if (isset($media[$mediaIndex])) { $m = $media[$mediaIndex]; if (! in_array($m->id, $this->editInspectionPhotosToDelete)) { $this->editInspectionPhotosToDelete[] = $m->id; } } } public function saveEditInspection() { if (! $this->editingInspection) { return; } // Verificar permiso if (! auth()->user()->can('edit inspections')) { $this->dispatch('notify', 'Sin permisos para editar inspecciones.'); return; } $this->validate([ 'selectedTemplateId' => 'required|exists:inspection_templates,id', 'editInspectionPhotos.*' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:10240', 'editInspectionPhotosToDelete' => 'array', 'editInspectionPhotosToDelete.*' => 'exists:media,id', ]); $ins = $this->editingInspection; if ($ins->project_id !== $this->project->id) { abort(403); } $template = InspectionTemplate::find($this->selectedTemplateId); foreach ($template->fields as $field) { if (($field['required'] ?? false) && empty($this->editInspectionFormData[$field['name']])) { $this->dispatch('notify', "El campo {$field['label']} es obligatorio."); return; } } // Eliminar fotos marcadas if (! empty($this->editInspectionPhotosToDelete)) { $mediaToDelete = \App\Models\Media::whereIn('id', $this->editInspectionPhotosToDelete) ->where('mediable_type', Inspection::class) ->where('mediable_id', $ins->id) ->get(); foreach ($mediaToDelete as $m) { $m->delete(); } } // Actualizar datos $ins->update([ 'template_id' => $this->selectedTemplateId, 'result' => $this->editInspectionResult ?: null, 'notes' => $this->editInspectionNotes ?: null, 'data' => $this->editInspectionFormData, ]); // Añadir nuevas fotos foreach ($this->editInspectionPhotos as $photo) { $mime = $photo->getMimeType(); $path = $photo->store("uploads/inspections/{$ins->id}", 'public'); $ins->media()->create([ 'name' => $photo->getClientOriginalName(), 'file_path' => $path, 'file_type' => $mime, 'file_extension' => $photo->getClientOriginalExtension(), 'file_size' => $photo->getSize(), 'category' => str_starts_with($mime, 'image/') ? 'image' : 'document', 'uploaded_by' => auth()->id(), 'uuid' => (string) Str::uuid(), ]); } // Refresh $this->allInspections = Inspection::where('project_id', $this->project->id) ->with(['feature.layer.phase', 'template', 'user']) ->orderBy('created_at', 'desc') ->get(); $this->loadInspectionHistory(); $this->closeEditInspection(); $this->dispatch('notify', 'Inspección actualizada correctamente'); } // ─── Delete Inspection ────────────────────────────────────────────────────── #[On('delete-inspection')] public function deleteInspection($id) { \Log::info('deleteInspection: START', ['id' => $id]); if (! auth()->user()->can('delete inspections')) { $this->dispatch('notify', 'Sin permisos para eliminar inspecciones.'); \Log::info('deleteInspection: permission denied'); return; } $ins = Inspection::where('project_id', $this->project->id) ->with(['feature', 'media']) ->find($id); if (! $ins) { \Log::info('deleteInspection: inspection not found', ['id' => $id]); $this->dispatch('notify', 'Inspección no encontrada'); return; } \Log::info('deleteInspection: BEFORE delete', ['id' => $ins->id, 'deleted_at' => $ins->deleted_at, 'project_id' => $ins->project_id]); // Delete associated media files (booted event deletes physical files) $ins->media()->get()->each->delete(); $result = $ins->delete(); \Log::info('deleteInspection: delete() returned', ['result' => $result]); $fresh = $ins->fresh(); \Log::info('deleteInspection: AFTER delete', ['id' => $id, 'fresh_deleted_at' => $fresh ? $fresh->deleted_at : 'null', 'fresh_exists' => $fresh ? 'yes' : 'no']); // Refresh lists $this->allInspections = Inspection::where('project_id', $this->project->id) ->with(['feature.layer.phase', 'template', 'user']) ->orderBy('created_at', 'desc') ->get(); $this->loadInspectionHistory(); $this->dispatch('notify', 'Inspección eliminada correctamente'); \Log::info('deleteInspection: dispatched notify SUCCESS'); } }