# TCA — Ejemplo Completo con FacturasFeature

## Reducer, State, Action y View

```swift
// PointFree/swift-composable-architecture
import ComposableArchitecture

struct FacturasFeature: Reducer {

    @ObservableState
    struct State: Equatable {
        var facturas: [Factura] = []
        var cargando = false
        var error: String? = nil
        var query: String = ""

        var facturasFiltradas: [Factura] {
            query.isEmpty ? facturas
            : facturas.filter { $0.folio.localizedCaseInsensitiveContains(query) }
        }
    }

    enum Action {
        case cargarFacturas
        case facturasResponse(Result<[Factura], Error>)
        case queryChanged(String)
        case facturaEliminada(id: String)
    }

    @Dependency(\.facturasClient) var facturasClient

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .cargarFacturas:
                state.cargando = true
                state.error = nil
                return .run { send in
                    await send(.facturasResponse(
                        Result { try await facturasClient.obtener() }
                    ))
                }

            case .facturasResponse(.success(let facturas)):
                state.cargando = false
                state.facturas = facturas
                return .none

            case .facturasResponse(.failure(let error)):
                state.cargando = false
                state.error = error.localizedDescription
                return .none

            case .queryChanged(let q):
                state.query = q
                return .none

            case .facturaEliminada(let id):
                state.facturas.removeAll { $0.id == id }
                return .run { _ in
                    try await facturasClient.eliminar(id)
                }
            }
        }
    }
}

struct FacturasView: View {
    let store: StoreOf<FacturasFeature>

    var body: some View {
        WithViewStore(store, observe: { $0 }) { viewStore in
            List(viewStore.facturasFiltradas) { factura in
                FacturaRow(factura: factura)
                    .swipeActions {
                        Button(role: .destructive) {
                            viewStore.send(.facturaEliminada(id: factura.id))
                        } label: { Label("Eliminar", systemImage: "trash") }
                    }
            }
            .searchable(
                text: viewStore.binding(
                    get: \.query,
                    send: FacturasFeature.Action.queryChanged
                )
            )
            .task { viewStore.send(.cargarFacturas) }
        }
    }
}
```

## Testing de Reducer TCA

```swift
@Test("Reducer: cargarFacturas transiciona a exito")
func reducerCargaExitosa() async {
    let store = TestStore(initialState: FacturasFeature.State()) {
        FacturasFeature()
    } withDependencies: {
        $0.facturasClient.obtener = { [Factura(id: "1", folio: "F-001")] }
    }

    await store.send(.cargarFacturas) { state in
        state.cargando = true
    }
    await store.receive(.facturasResponse(.success([...]))) { state in
        state.cargando = false
        state.facturas = [Factura(id: "1", folio: "F-001")]
    }
}
```
