El proyecto sigue Clean Architecture con tres anillos:
P2PManager, Hardware, Camera2ServiceDirectLink, ScaleManager, Camera2Service
Ubicación: MachineDomain/state/StateManager.kt
Estado: interfaz común ├── WaitingForWeight ├── CaptureImages ├── CaptureFace ├── ComparingWeights ├── GenerateEmbedding ├── VerifyInDatabase ├── SaveDelivery └── WaitForWeightRemoved StateManager: gestor que ejecuta transiciones
Ventajas:
Ubicación: MachineData/repository/, RutaPAEData/repository/
Repository (contrato)
├── MachineData.Repository
│ ├── DeliveryService
│ ├── BeneficiaryService
│ └── MachineEnrollmentService
│
└── RutaPAEData.Repository
├── DeliveryService
└── MachineService
Ventajas:
Implementación: Kotlin Flow y Emisores
// Machine: emite cambios de estado StateNameEmitter.emit("CapturingFace") // RutaPAE: emite progreso de sincronización DeliverySyncUiEmitter.update(estado) // UI observa LaunchedEffect { StateNameEmitter.collect { nombre -> // actualizar UI } }
Ventajas:
StateManager: crea y ejecuta estados
val states = listOf( WaitingForWeight(), CaptureImages(), CaptureFace(), // ... ) while (isRunning) { val state = workflow[currentIndex] // factory state.run(next, retry) }
Ventajas:
DomainManager en RutaPAE:
object DomainManager : Initializer, Closeable { var p2pManager: P2PManager? = null fun init(context: Context): Boolean { if (initialized) return true // ... } }
Ventajas:
Contract: construcción de modelos
data class P2PMachineState( val id: String = "", val name: String = "", val unsyncedDeliveries: Long = 0, // ... ) // Creación flexible P2PMachineState( id = "123", name = "Máquina 1" )
StateManager: solo orquesta máquina de estados DeliveryService: solo gestiona entregas BeneficiaryService: solo gestiona beneficiarios
// Abierto para extensión interface State { suspend fun run(next: () -> Unit, retry: (String) -> Unit) } // Cerrado para modificación class NewState : State { ... }
// Cualquier State puede ocupar el lugar de otro val states: List<State> = listOf( WaitingForWeight(), CaptureImages(), // ... )
// Interfaces pequeñas y específicas interface Initializer { fun init(context: Context): Boolean } interface Closeable { fun close(context: Context): Boolean } // vs una sola interfaz grande
// Domain depende de abstracción (Repository) class StateManager(val repository: Repository) // Data implementa la abstracción class RepositoryImpl : Repository
Machine Domain:
// State.run() ejecuta en contexto de corrutina suspend fun run(next: () -> Unit, retry: (String) -> Unit)
RutaPAE Domain:
// P2PManager usa corrutinas para operaciones P2P suspend fun syncDeliveriesFromMachine(machineDatabaseId: Long)
// P2PManager mantiene state reactivo val machines: StateFlow<Set<P2PPeer>> val peers: StateFlow<Set<P2PPeer>> // UI observa LaunchedEffect { machines.collect { peers -> // actualizar UI } }
@DataTable("Beneficiary") data class Beneficiary( @VectorColumn(dimensions = 512, distanceMetric = DistanceMetric.COSINE) val embedding: FloatArray, // ... ) // Búsqueda vectorial repository.beneficiaries .nearestNeighbors(embeddings, queryVector)
// Data layer abstraction class Repository { val beneficiaries: Table<Beneficiary> val deliveries: Table<Delivery> } // Service expone API object BeneficiaryService { fun findSimilar(embedding: FloatArray): List<Beneficiary> }
try { captureImage() next() // éxito } catch (e: Exception) { retry("Error capturando imagen: ${e.message}") // retrocede a estado anterior }
suspend fun syncDeliveriesFromMachine(): SyncDeliveriesResult { return SyncDeliveriesResult( success = true, message = "Completado", fetched = 10, failed = 0 ) }
// WorkManager reintentatrabajos automáticamente Result.retry() // reintentar Result.failure() // fallar Result.success() // éxito
Machine:
StateNameEmitter.emit(stateName) // event StateMessageEmitter.emit(message) // event
RutaPAE:
DeliverySyncUiEmitter.update(state) // event SyncRunningEmitter.emit(running) // event
P2P:
// Machine recibe GET /p2p/machine/deliveries/1/1 // responde con P2PDeliveriesPageResponse
// StateNameEmitter (publicador) Flow<String> (suscriptores) // detalles: 1 a N // desacoplamiento: productor no conoce suscriptores
// Facilita mock class StateManager( val repository: Repository, // inyectable val hardware: Hardware // inyectable )
// Fácil probar cada servicio DeliveryService.getAll() // no depende de UI BeneficiaryService.findSimilar() // no depende de P2P
// Contract proporciona modelos testables data class P2PMachineState(...) // serializable, comparable
// Services class MachineService { } class DeliveryService { } // Managers class StateManager { } class P2PManager { } class ScaleManager { } // Emitters object StateNameEmitter { } object DeliverySyncUiEmitter { } // Interfaces interface State { } interface Repository { } interface Initializer { } // Data classes data class Delivery(...) { } data class P2PMachineState(...) { }
co.ada.paemachine/ ├── screens/ # UI Composable ├── viewmodels/ # State holders ├── di/ # Inyección └── ... co.ada.domain/ ├── state/ # State pattern ├── services/ # Business logic ├── hardware/ # Hardware abstractions └── ... co.ada.data/ ├── entities/ # Data models ├── repository/ # Repository pattern └── services/ # Data access