# Swift — Keychain y Property Wrappers

## Keychain Wrapper Completo

```swift
// Wrapper type-safe para Keychain — NUNCA UserDefaults para tokens
struct KeychainStorage {
    enum Key: String {
        case authToken = "com.miapp.authToken"
        case refreshToken = "com.miapp.refreshToken"
    }

    enum KeychainError: Error {
        case guardadoFallido(OSStatus)
        case noEncontrado
        case datosInvalidos
    }

    static func guardar(_ valor: String, para clave: Key) throws {
        let data = Data(valor.utf8)
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: clave.rawValue,
            kSecValueData: data,
            kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw KeychainError.guardadoFallido(status)
        }
    }

    static func leer(_ clave: Key) throws -> String {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: clave.rawValue,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess,
              let data = item as? Data,
              let valor = String(data: data, encoding: .utf8) else {
            throw KeychainError.noEncontrado
        }
        return valor
    }

    static func eliminar(_ clave: Key) {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: clave.rawValue
        ]
        SecItemDelete(query as CFDictionary)
    }
}
```

---

## Property Wrappers

### @AppStorage

```swift
// Solo para preferencias de UI — NUNCA para tokens o datos sensibles
@AppStorage("tema_oscuro") private var temaOscuro = false
@AppStorage("idioma") private var idioma = "es"
@AppStorage("notificaciones_activas") private var notificaciones = true
```

### Property Wrapper personalizado

```swift
// @Clamped — limita un valor a un rango
@propertyWrapper
struct Clamped<T: Comparable> {
    private var valor: T
    let rango: ClosedRange<T>

    init(wrappedValue: T, _ rango: ClosedRange<T>) {
        self.rango = rango
        self.valor = min(max(wrappedValue, rango.lowerBound), rango.upperBound)
    }

    var wrappedValue: T {
        get { valor }
        set { valor = min(max(newValue, rango.lowerBound), rango.upperBound) }
    }
}

// @Trimmed — elimina espacios automáticamente
@propertyWrapper
struct Trimmed {
    private var valor: String = ""
    var wrappedValue: String {
        get { valor }
        set { valor = newValue.trimmingCharacters(in: .whitespaces) }
    }
    init(wrappedValue: String) { self.wrappedValue = wrappedValue }
}

// Uso combinado
struct Configuracion {
    @Clamped(1...100) var tamanoPagina = 20
    @Trimmed var nombreEmpresa = ""
}
```
