# Regla: Patrones de Arquitectura Swift / iOS

Esta regla define los patrones obligatorios para estructurar código Swift en iOS.
La orientación a protocolos, MVVM con `@Observable` y la concurrencia estructurada
son el estándar para proyectos nuevos con Swift 5.9+.

---

## Protocol-oriented programming sobre herencia

Swift favorece composición de protocolos sobre jerarquías de herencia profundas.
Los protocolos definen capacidades; los tipos las adoptan independientemente.

```swift
// MAL — jerarquía de herencia que crece y se vuelve frágil
class Animal { func hacerSonido() {} }
class Perro: Animal { override func hacerSonido() { } }
class PerroGuardian: Perro { /* dependencia de toda la cadena */ }

// BIEN — protocolos que componen capacidades independientes
protocol Localizable {
    var coordenadas: CLLocationCoordinate2D { get }
}

protocol Notificable {
    func enviarAlerta(_ mensaje: String) async
}

struct DispositivoIoT: Localizable, Notificable {
    let coordenadas: CLLocationCoordinate2D
    func enviarAlerta(_ mensaje: String) async { ... }
}
```

---

## Dependency injection via protocolos

Las dependencias se declaran como protocolos. En producción se inyectan implementaciones
reales; en tests, mocks. Nunca instanciar dependencias dentro de un tipo.

```swift
// Protocolo en la capa de dominio
protocol PedidoRepository {
    func obtenerPorId(_ id: UUID) async throws -> Pedido
    func guardar(_ pedido: Pedido) async throws
}

// ViewModel recibe el protocolo — no sabe si es real o mock
@Observable
final class PedidoViewModel {
    private let repositorio: PedidoRepository

    init(repositorio: PedidoRepository) {
        self.repositorio = repositorio
    }
}

// En produccion
let vm = PedidoViewModel(repositorio: PedidoRepositoryImpl(apiClient: client))

// En tests
let vm = PedidoViewModel(repositorio: MockPedidoRepository())
```

---

## MVVM con @Observable (iOS 17+)

`@Observable` (macro de Swift 5.9+) reemplaza `ObservableObject` + `@Published`.
Elimina boilerplate y mejora el rendimiento de SwiftUI al rastrear solo las
propiedades realmente accedidas en cada vista.

```swift
// MAL — ObservableObject con @Published (iOS 16 y anterior)
class PedidoViewModel: ObservableObject {
    @Published var pedidos: [Pedido] = []
    @Published var isLoading = false
    @Published var error: String?
}

// BIEN — @Observable: mas limpio, mejor rendimiento de recomposicion
@Observable
final class PedidoViewModel {
    var pedidos: [Pedido] = []
    var isLoading = false
    var error: String?
    private let repositorio: PedidoRepository

    init(repositorio: PedidoRepository) {
        self.repositorio = repositorio
    }

    func cargarPedidos() async {
        isLoading = true
        defer { isLoading = false }
        do {
            pedidos = try await repositorio.obtenerTodos()
        } catch {
            self.error = error.localizedDescription
        }
    }
}

// Vista — limpia, sin @StateObject/@ObservedObject
struct PantallaPedidos: View {
    @State private var viewModel = PedidoViewModel(repositorio: ...)

    var body: some View {
        Group {
            if viewModel.isLoading { ProgressView() }
            else { ListaPedidos(pedidos: viewModel.pedidos) }
        }
        .task { await viewModel.cargarPedidos() }
    }
}
```

---

## NavigationStack con NavigationPath para navegación programática

```swift
// MAL — NavigationView deprecado en iOS 16+
NavigationView {
    NavigationLink("Ver detalle", destination: DetallePedidoView())
}

// BIEN — NavigationStack con rutas tipadas
enum RutaApp: Hashable {
    case detallePedido(UUID)
    case editarPerfil
}

struct AppRootView: View {
    @State private var ruta = NavigationPath()

    var body: some View {
        NavigationStack(path: $ruta) {
            ListaPedidosView(onSeleccion: { id in
                ruta.append(RutaApp.detallePedido(id))
            })
            .navigationDestination(for: RutaApp.self) { ruta in
                switch ruta {
                case .detallePedido(let id): DetallePedidoView(pedidoId: id)
                case .editarPerfil: EditarPerfilView()
                }
            }
        }
    }
}
```

---

## SwiftUI: @State, @Binding, @Environment

```swift
// @State — estado local del componente, no se comparte hacia arriba
struct ContadorView: View {
    @State private var contador = 0
    var body: some View {
        Button("Contar: \(contador)") { contador += 1 }
    }
}

// @Binding — estado que viene del padre, el hijo puede modificarlo
struct SelectorFecha: View {
    @Binding var fecha: Date
    var body: some View {
        DatePicker("Fecha", selection: $fecha, displayedComponents: .date)
    }
}

// @Environment — dependencias globales inyectadas por SwiftUI
struct Vista: View {
    @Environment(\.colorScheme) private var colorScheme
    @Environment(GestorAutenticacion.self) private var auth
}
```

---

## Ejemplo: Massive ViewController vs MVVM

```swift
// MAL — UIViewController con 500+ líneas que hace todo
class PedidosViewController: UIViewController {
    private var pedidos: [Pedido] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data else { return }
            self.pedidos = try! JSONDecoder().decode([Pedido].self, from: data)
            DispatchQueue.main.async { self.tableView.reloadData() }
        }.resume()
    }
    // ... 400 líneas más de UITableViewDataSource, lógica de negocio, formato, etc.
}

// BIEN — MVVM con responsabilidades separadas
@Observable
final class PedidosViewModel {
    private(set) var pedidos: [Pedido] = []
    private(set) var isLoading = false
    private let repositorio: PedidoRepository

    func cargar() async {
        isLoading = true
        defer { isLoading = false }
        pedidos = (try? await repositorio.obtenerTodos()) ?? []
    }
}

struct PedidosView: View {
    @State private var vm = PedidosViewModel(repositorio: PedidoRepositoryImpl())
    var body: some View {
        List(vm.pedidos) { pedido in FilaPedido(pedido: pedido) }
            .task { await vm.cargar() }
    }
}
```

---

## Checklist de patrones Swift antes de abrir PR

- [ ] Dependencias declaradas como protocolos, inyectadas en el inicializador
- [ ] ViewModels usan `@Observable` (iOS 17+) o `ObservableObject` (iOS 16-)
- [ ] Sin lógica de negocio en vistas SwiftUI (va en ViewModel o use case)
- [ ] Navegación con `NavigationStack` y rutas tipadas
- [ ] Concurrencia con `async/await`; actores para estado compartido
- [ ] Sin `Massive ViewController`: UIKit con MVVM si se usa UIKit
- [ ] Protocolos para composicion de capacidades, no herencia profunda
