is_system = $state; return $this; } public function markAsEncrypted(bool $state = true): static { $this->is_encrypted = $state; return $this; } public function markAsSensitive(bool $state = true): static { $this->is_sensitive = $state; return $this; } public function markAsEditable(bool $state = true): static { $this->is_editable = $state; return $this; } public function markAsActive(bool $state = true): static { $this->is_active = $state; return $this; } // ========== Settings Operations ========== public function set(string $key, mixed $value): ?Setting { $this->validateKey($key); //$this->user = $fullKey = $this->generateCacheKey($key); $columns = [ 'namespace' => $this->namespace, 'environment' => app()->environment(), 'scope' => $this->scope, 'component' => $this->component, 'module' => $this->module, 'group' => $this->group, 'sub_group' => $this->subGroup, 'key_name' => $key, 'user_id' => $userId, 'is_system' => $this->is_system, 'is_encrypted' => $this->is_encrypted, 'is_sensitive' => $this->is_sensitive, 'is_editable' => $this->is_editable, 'is_active' => $this->is_active, ]; $this->assignValueColumns($columns, $value); $setting = Setting::updateOrCreate( ['key' => $fullKey], $columns ); Event::dispatch(new SettingChanged($fullKey, $this->namespace, $userId)); return $setting; } public function get(string $key): mixed { $fullKey = $this->generateCacheKey($key); return $this->rememberCache($key, fn() => $this->query($fullKey)); } public function delete(string $key): bool { $fullKey = $this->generateCacheKey($key); $deleted = Setting::where('key', $fullKey)->delete(); if ($deleted) { Event::dispatch(new SettingChanged($fullKey)); } return $deleted > 0; } public function exists(string $key): bool { return Setting::where('key', $this->generateCacheKey($key))->exists(); } // ===================== BÚSQUEDA AVANZADA ===================== public function getScoped(string $scope, ?int $userId = null): array { return Setting::where('scope', $scope) ->when($userId, fn($q) => $q->where('user_id', $userId)) ->get()->toArray(); } public function getComponent(string $component, ?int $userId = null): array { return Setting::where('component', $component) ->when($userId, fn($q) => $q->where('user_id', $userId)) ->get()->toArray(); } public function getGroup(string $group, ?int $userId = null): array { return Setting::where('group', $group) ->when($userId, fn($q) => $q->where('user_id', $userId)) ->get()->toArray(); } public function getSubGroup(string $subGroup, ?int $userId = null): array { return Setting::where('sub_group', $subGroup) ->when($userId, fn($q) => $q->where('user_id', $userId)) ->get()->toArray(); } // ===================== ELIMINACIÓN ===================== public function deleteScoped(string $scope): int { return Setting::where('scope', $scope)->delete(); } public function deleteComponent(string $component): int { return Setting::where('component', $component)->delete(); } public function deleteGroup(string $group): int { return Setting::where('group', $group)->delete(); } public function deleteSubGroup(string $subGroup): int { return Setting::where('sub_group', $subGroup)->delete(); } // ===================== LISTADOS ===================== public function listComponents(): array { return Setting::select('component')->distinct()->pluck('component')->toArray(); } public function listGroups(): array { return Setting::select('group')->distinct()->pluck('group')->toArray(); } public function listSubGroups(): array { return Setting::select('sub_group')->distinct()->pluck('sub_group')->toArray(); } // ===================== Internal Helpers ===================== /** * Obtiene un setting por su clave calificada. */ protected function query(string $key): mixed { if (!Schema::hasTable('settings')) return null; $setting = $this->settingModel::where('key', $key)->first(); return $setting ? $this->decode($setting) : null; } /** * Decodifica el valor de un setting. */ protected function decode(Setting $setting, bool $asArray = true): mixed { // Orden de prioridad: JSON largo, texto simple, luego tipos básicos $value = $setting->value_text ?? $setting->value_string ?? $setting->value_integer ?? $setting->value_boolean ?? $setting->value_float ?? $setting->value_binary ?? null; if (is_string($value)) { $value = trim($value); // Limpieza de caracteres invisibles si es string $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value); // Intentar decodificar si parece JSON if (Str::startsWith($value, ['[', '{'])) { $decoded = json_decode($value, $asArray); if (json_last_error() === JSON_ERROR_NONE) { return $decoded; } logger()->warning('⚠️ JSON decode failed', [ 'key' => $setting->key, 'value_preview' => Str::limit($value, 200), 'error' => json_last_error_msg(), ]); } } return $value; } protected function assignValueColumns(array &$columns, mixed $value): void { foreach (SettingValueType::cases() as $type) { $columns["value_{$type->value}"] = null; } match (true) { is_string($value) => $columns[strlen($value) > 250 ? 'value_text' : 'value_string'] = $value, is_int($value) => $columns['value_integer'] = $value, is_bool($value) => $columns['value_boolean'] = $value, is_float($value) => $columns['value_float'] = $value, is_array($value), is_object($value) => $columns['value_text'] = json_encode($value, JSON_UNESCAPED_UNICODE), default => null }; } }