'required|file|mimes:geojson,kmz,kml,shp,dwg,zip|max:51200', 'layerName' => 'required|string|max:255', 'layerColor' => 'nullable|string|size:7', ]; public function mount(Project $project, Phase $phase) { $this->project = $project; $this->phase = $phase; $this->loadLayers(); if ($this->phase->project_id !== $this->project->id) { abort(404); } // Por defecto todas visibles $this->visibleLayers = $this->layers->pluck('id')->toArray(); $this->emitInitialLayersData(); } public function loadLayers() { $this->layers = Layer::with('features')->where('phase_id', $this->phase->id)->latest()->get(); $this->visibleLayers = array_intersect($this->visibleLayers, $this->layers->pluck('id')->toArray()); } private function emitInitialLayersData() { $layersData = $this->layers->map(function($layer) { // Construir FeatureCollection a partir de los features de esta capa $features = $layer->features->map(function($feature) { return [ 'type' => 'Feature', 'id' => $feature->id, 'geometry' => $feature->geometry, 'properties' => [ 'name' => $feature->name, 'progress' => $feature->progress, 'responsible' => $feature->responsible, 'template_id' => $feature->template_id, ] ]; })->values()->toArray(); $geojson = [ 'type' => 'FeatureCollection', 'features' => $features, 'style' => ['color' => $this->layerColor ?: '#3b82f6'] // Podrías guardar el color en la tabla layers ]; return [ 'id' => $layer->id, 'geojson' => $geojson, 'color' => $geojson['style']['color'], ]; }); $this->dispatch('initialLayersData', [ 'layers' => $layersData, 'visibleLayers' => $this->visibleLayers, 'selectedLayerId' => $this->selectedLayer?->id, ]); } public function toggleLayerVisibility($layerId) { if ($this->selectedLayer && $this->selectedLayer->id == $layerId) { session()->flash('info', 'No puedes ocultar la capa que estás editando.'); return; } if (in_array($layerId, $this->visibleLayers)) { $this->visibleLayers = array_diff($this->visibleLayers, [$layerId]); } else { $this->visibleLayers[] = $layerId; } $this->dispatch('visibilityChanged', $this->visibleLayers); } public function selectLayer($layerId) { $this->selectedLayer = Layer::with('features')->find($layerId); if (!$this->selectedLayer) return; if (!in_array($layerId, $this->visibleLayers)) { $this->visibleLayers[] = $layerId; $this->dispatch('visibilityChanged', $this->visibleLayers); } // Construir el GeoJSON desde los features de la capa seleccionada $features = $this->selectedLayer->features->map(function($feature) { return [ 'type' => 'Feature', 'id' => $feature->id, 'geometry' => $feature->geometry, 'properties' => [ 'name' => $feature->name, 'progress' => $feature->progress, 'responsible' => $feature->responsible, 'template_id' => $feature->template_id, ] ]; })->values()->toArray(); $geojson = [ 'type' => 'FeatureCollection', 'features' => $features, 'style' => ['color' => $this->layerColor ?: '#3b82f6'] ]; $this->dispatch('layerSelectedForEdit', [ 'layerId' => $layerId, 'geojson' => $geojson, 'color' => $geojson['style']['color'], ]); session()->flash('info', 'Editando capa: ' . $this->selectedLayer->name); } public function importFile() { $user = Auth::user(); if (!$user->can('upload layers') && !$user->hasRole('Admin')) { session()->flash('error', 'Sin permisos.'); return; } // Validar campos obligatorios y tamaño máximo $this->validate([ 'uploadFile' => 'required|file|max:51200', 'layerName' => 'required|string|max:255', 'layerColor' => 'nullable|string|size:7', ]); $extension = strtolower($this->uploadFile->getClientOriginalExtension()); $mime = $this->uploadFile->getMimeType(); $allowedExtensions = ['geojson', 'kmz', 'kml', 'shp', 'dwg', 'zip']; $allowedMimes = [ 'application/vnd.google-earth.kml+xml', 'application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip-compressed', 'application/x-shapefile', 'image/vnd.dwg', 'application/acad', 'application/geo+json', 'text/xml', // ✅ Aceptar KML con text/xml 'application/xml', // ✅ Alternativa ]; if (!in_array($extension, $allowedExtensions) && !in_array($mime, $allowedMimes)) { session()->flash('error', 'Tipo de archivo no permitido. Extensiones válidas: ' . implode(', ', $allowedExtensions)); return; } $projectDir = "uploads/projects/{$this->project->id}/layers"; $originalPath = $this->uploadFile->store($projectDir, 'public'); $geojson = SpatialFileConverter::convertToGeoJson($this->uploadFile); if (!$geojson) { session()->flash('error', 'Conversión fallida. Asegúrate de que el archivo sea válido (KML, GeoJSON, etc.).'); return; } $geojson['style'] = ['color' => $this->layerColor ?: '#3b82f6']; $layer = Layer::create([ 'project_id' => $this->project->id, 'phase_id' => $this->phase->id, 'name' => $this->layerName, //'geojson_data' => $geojson, 'original_file' => $originalPath, 'uploaded_by' => $user->id, ]); // Crear features a partir del GeoJSON if (isset($geojson['features'])) { foreach ($geojson['features'] as $featureData) { Feature::create([ 'layer_id' => $layer->id, 'name' => $featureData['properties']['name'] ?? null, 'geometry' => $featureData['geometry'], 'properties' => $featureData['properties'] ?? [], 'template_id' => $featureData['properties']['template_id'] ?? null, 'progress' => $featureData['properties']['progress'] ?? 0, 'responsible' => $featureData['properties']['responsible'] ?? null, ]); } } $this->loadLayers(); $this->visibleLayers[] = $layer->id; $this->reset(['uploadFile', 'layerName']); $this->emitInitialLayersData(); session()->flash('message', 'Capa importada correctamente.'); } public function createEmptyLayer() { $user = Auth::user(); $layer = Layer::create([ 'project_id' => $this->project->id, 'phase_id' => $this->phase->id, 'name' => $this->layerName ?: 'Nueva capa', 'original_file' => null, 'uploaded_by' => $user->id, // Opcional: guarda el color en una columna 'color' de la tabla layers ]); $this->loadLayers(); $this->visibleLayers[] = $layer->id; $this->selectLayer($layer->id); $this->emitInitialLayersData(); session()->flash('message', 'Capa vacía creada. Usa el editor para añadir elementos.'); } public function saveManualGeojson($geojsonString) { if (!$this->selectedLayer) { session()->flash('error', 'No hay capa seleccionada.'); return; } $geojson = json_decode($geojsonString, true); if (json_last_error() !== JSON_ERROR_NONE || !isset($geojson['features'])) { session()->flash('error', 'GeoJSON inválido.'); return; } // Eliminar todos los features existentes de esta capa $this->selectedLayer->features()->delete(); // Crear nuevos features a partir del GeoJSON foreach ($geojson['features'] as $featureData) { Feature::create([ 'layer_id' => $this->selectedLayer->id, 'name' => $featureData['properties']['name'] ?? null, 'geometry' => $featureData['geometry'], 'properties' => $featureData['properties'] ?? [], 'template_id' => $featureData['properties']['template_id'] ?? null, 'progress' => $featureData['properties']['progress'] ?? 0, 'responsible' => $featureData['properties']['responsible'] ?? null, ]); } $this->loadLayers(); $this->selectLayer($this->selectedLayer->id); $this->emitInitialLayersData(); session()->flash('message', 'Capa guardada con ' . count($geojson['features']) . ' elementos.'); } public function deleteLayer($layerId) { $user = Auth::user(); if (!$user->can('delete layers') && !$user->hasRole('Admin')) abort(403); $layer = Layer::find($layerId); if (!$layer) return; if ($layer->original_file) Storage::disk('public')->delete($layer->original_file); $layer->features()->delete(); // opcional, si no usas cascade $layer->delete(); $this->loadLayers(); if ($this->selectedLayer && $this->selectedLayer->id == $layerId) { $this->selectedLayer = null; } $this->emitInitialLayersData(); session()->flash('message', 'Capa eliminada.'); } public function cancelEditing() { $this->selectedLayer = null; $this->dispatch('layerSelectedForEdit', null); } public function render() { return view('livewire.layers.layer-manager'); } }