# Regla: Patrones de Arquitectura Kotlin

Esta regla define los patrones obligatorios para estructurar código Kotlin en Android
y backend (Ktor/Spring). La arquitectura limpia con coroutines y Flow es el estándar.

---

## Coroutines y Flow para async/reactive

- Todo código asíncrono usa `suspend` functions o `Flow`. Sin `AsyncTask`, `Thread` ni callbacks.
- `Flow` para streams de datos que cambian en el tiempo (lista de items, estados de UI).
- `StateFlow` para estado que siempre tiene un valor actual (reemplaza `LiveData`).
- `SharedFlow` para eventos de un solo disparo (navegación, mensajes de error).

```kotlin
// MAL — LiveData expone detalles de Android en el dominio
class PedidoRepository {
    fun obtenerPedidos(): LiveData<List<Pedido>> = ...
}

// BIEN — Flow es independiente del framework
class PedidoRepository {
    fun obtenerPedidos(): Flow<List<Pedido>> = flow {
        emit(dao.obtenerTodos())
    }
}
```

---

## Repository pattern con interfaces

Toda fuente de datos se abstrae detrás de una interfaz.
El dominio depende de la interfaz, no de la implementación concreta.

```kotlin
// Interfaz en la capa de dominio
interface PedidoRepository {
    suspend fun obtenerPorId(id: UUID): Pedido?
    fun obtenerTodos(): Flow<List<Pedido>>
    suspend fun guardar(pedido: Pedido)
}

// Implementación en la capa de datos
class PedidoRepositoryImpl(
    private val dao: PedidoDao,
    private val api: PedidoApi,
) : PedidoRepository {
    override suspend fun obtenerPorId(id: UUID): Pedido? =
        dao.buscarPorId(id)?.toDomain()
}
```

---

## Use cases / interactors para lógica de negocio

Cada acción de negocio tiene su propio use case. Un use case = una responsabilidad.
El ViewModel llama al use case; no contiene lógica de negocio.

```kotlin
// BIEN — use case con responsabilidad única
class CrearPedidoUseCase(
    private val pedidoRepo: PedidoRepository,
    private val inventarioRepo: InventarioRepository,
) {
    suspend operator fun invoke(items: List<ItemPedido>): Result<Pedido> {
        if (items.isEmpty()) return Result.failure(IllegalArgumentException("Sin items"))
        val disponible = inventarioRepo.verificarDisponibilidad(items)
        if (!disponible) return Result.failure(StockInsuficienteException())
        val pedido = Pedido.crear(items)
        pedidoRepo.guardar(pedido)
        return Result.success(pedido)
    }
}

// MAL — lógica de negocio en el ViewModel
class PedidoViewModel(private val repo: PedidoRepository) : ViewModel() {
    fun crearPedido(items: List<ItemPedido>) {
        if (items.isEmpty()) { /* lógica aquí — MAL */ }
        viewModelScope.launch { repo.guardar(...) }
    }
}
```

---

## Sealed classes para estados de UI

```kotlin
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val mensaje: String, val causa: Throwable? = null) : UiState<Nothing>()
}

// ViewModel expone StateFlow de UiState
class PedidoViewModel(private val obtenerPedidos: ObtenerPedidosUseCase) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<List<Pedido>>>(UiState.Loading)
    val uiState: StateFlow<UiState<List<Pedido>>> = _uiState.asStateFlow()

    init {
        cargarPedidos()
    }

    private fun cargarPedidos() {
        viewModelScope.launch {
            obtenerPedidos()
                .onStart { _uiState.value = UiState.Loading }
                .catch { e -> _uiState.value = UiState.Error(e.message ?: "Error") }
                .collect { pedidos -> _uiState.value = UiState.Success(pedidos) }
        }
    }
}
```

---

## Dependency Injection con Hilt (Android) o Koin

Preferir **Hilt** en proyectos Android nuevos por su integración con el compilador.
**Koin** es aceptable en proyectos multiplatforma o Ktor.

```kotlin
// Hilt — módulo de provisión
@Module
@InstallIn(SingletonComponent::class)
object RepositorioModule {
    @Provides
    @Singleton
    fun providePedidoRepository(dao: PedidoDao): PedidoRepository =
        PedidoRepositoryImpl(dao)
}

// ViewModel con Hilt
@HiltViewModel
class PedidoViewModel @Inject constructor(
    private val obtenerPedidos: ObtenerPedidosUseCase,
) : ViewModel()
```

---

## Jetpack Compose: remember, LaunchedEffect, derivedStateOf

```kotlin
@Composable
fun PantallaPedidos(viewModel: PedidoViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // remember: cálculos costosos que no deben recalcularse en cada recomposición
    val pedidosOrdenados = remember(uiState) {
        if (uiState is UiState.Success) {
            (uiState as UiState.Success).data.sortedByDescending { it.fecha }
        } else emptyList()
    }

    // derivedStateOf: valor derivado de otro estado, evita recomposiciones en cascada
    val hayPedidos by remember { derivedStateOf { pedidosOrdenados.isNotEmpty() } }

    // LaunchedEffect: efectos de un solo disparo atados al ciclo de vida del composable
    LaunchedEffect(Unit) {
        viewModel.eventos.collect { evento ->
            when (evento) {
                is Evento.NavegacionDetalle -> navController.navigate(...)
            }
        }
    }
}
```

---

## Clean Architecture: capas y dependencias

```
presentation/   — Composables, ViewModels, UiState
    |
    v
domain/         — UseCases, interfaces de Repository, modelos de dominio
    |
    v
data/           — implementaciones de Repository, DAOs, API clients, mappers
```

La capa `domain` NO importa nada de `presentation` ni `data`.
Los modelos de datos de Room o Retrofit NO se exponen a la capa de presentación.
Usar mappers explícitos entre capas.

---

## Checklist de patrones Kotlin antes de abrir PR

- [ ] Sin callbacks: todo async usa suspend o Flow
- [ ] Repositories tienen interfaz en dominio e implementación en datos
- [ ] Lógica de negocio en use cases, no en ViewModels ni Repositories
- [ ] Estados de UI representados con sealed class (Loading/Success/Error)
- [ ] ViewModels exponen StateFlow, no MutableStateFlow público
- [ ] DI configurada con Hilt o Koin, sin instanciación manual de dependencias
- [ ] Composables con remember/derivedStateOf para evitar recomposiciones
- [ ] Capas de Clean Architecture sin dependencias inversas
