La arquitectura de PAE separa responsabilidades en tres capas claramente definidas. Cada capa comunica con la siguiente a través de interfaces y contratos bien definidos.
Ubicación: Machine/src/main/java/co/ada/paemachine/
Componentes:
LaboratoryScreen: captura de entregasShiftSelectionScreen: selección de jornadaFlujo típico:
Button("Capturar") → ViewModel.startCapture() → Domain ↑ └── ViewModel.stateFlow observa cambios
Ubicación: RutaPAE/src/main/java/co/ada/rutapae/
Componentes:
MachineListScreen: lista de máquinasMachineDetailScreen: detalle de máquinaMachineConfigurationScreen: configuraciónFlujo típico:
Button("Sincronizar") → enqueueSync() → DeliverySyncWorker → Domain ↑ └── DeliverySyncUiEmitter observa progreso
Ubicación: MachineDomain/src/main/java/co/ada/domain/
Componentes clave:
Orquesta la máquina de estados de entregas ├── WaitingForWeight ├── CaptureImages ├── CaptureFace ├── ComparingWeights ├── GenerateEmbedding ├── VerifyInDatabase ├── SaveDelivery └── WaitForWeightRemoved
Gestiona sincronización P2P ├── connect(peer) ├── syncDeliveries() ├── getMachineConfiguration() └── updateMachineConfiguration()
SyncDeliveries: subeentregas al backend HTTPSyncBeneficiaries: descarga beneficiariosSyncMachineEnrollment: sincroniza jornadasHealthCheck: verifica estado del sistemaScaleManager: interfaz con balanzaCamera2Service: captura de imágenesBoardLedManager: indicadores LED
Ubicación: RutaPAEDomain/src/main/java/co/ada/rutapaedomain/
Componentes clave:
Gestiona conexión P2P con máquinas ├── discoverableMachineIds() ├── discoveredMachineCandidates() ├── connectMachine(id) ├── connectMachineHotspot(ssid) └── syncDeliveriesFromMachine()
Inicializa y coordina servicios ├── init(context) ├── close(context) └── p2pManager acceso
Ubicación: MachineData/src/main/java/co/ada/data/
class Repository { fun getDeliveryById(id: Long): Delivery? fun saveDelivery(delivery: Delivery) fun getAllBeneficiaries(): List<Beneficiary> fun createBeneficiary(beneficiary: Beneficiary) fun getMachineEnrollmentShift(id: Long): MachineEnrollmentShift? }
DeliveryService: CRUD de entregasBeneficiaryService: CRUD de beneficiariosMachineEnrollmentService: CRUD de jornadasDelivery ├── weight ├── beneficiaryPhotoPath (ruta local) ├── alimentPhotoPath (ruta local) ├── similarity (0-1) ├── processTimeMs └── serverDeliveryId Beneficiary ├── embedding (512 dimensiones) ├── name ├── enrollmentId ├── remoteBeneficiaryId └── lastRecognitionAtMs MachineEnrollmentShift ├── campusId / campusName ├── modalityId / modalityTypeName ├── shiftId / shiftName └── gradeId / gradeName
/data/data/co.ada.paemachine/database.db
Ubicación: RutaPAEData/src/main/java/co/ada/rutapaedata/
class Repository { fun getMachineById(id: Long): Machine? fun getAllMachines(): List<Machine> fun saveDelivery(delivery: Delivery) fun getDeliveriesByMachineId(machineId: Long): List<Delivery> }
MachineService: gestión de máquinas localesDeliveryService: gestión de entregas sincronizadasMachine ├── machineId (identificador único) ├── name ├── machineStatus (Active/Inactive/Error) ├── unsyncedDeliveries └── localSyncedDeliveries Delivery ├── weight ├── latitude / longitude (GPS) ├── benefitPhoto (URL remota) ├── beneficiaryPhoto (URL remota) ├── idLocalDeliveryT (ID en máquina) ├── machineId (FK) └── synchronized (bandera)
/data/data/co.ada.rutapae/database.db1. Usuario presiona "Capturar" └─ Presentación (LaboratoryScreen) 2. ViewModel delega a Domain └─ StateManager.init() 3. StateManager ejecuta WaitingForWeight └─ espera input de balanza 4. Estado avanza a CaptureImages └─ Camera2Service captura foto 5. Estado avanza a GenerateEmbedding └─ ComputerVision genera embedding 512d 6. Estado avanza a VerifyInDatabase └─ Data busca en base de datos local └─ BeneficiaryService.findSimilar() 7. Estado avanza a SaveDelivery └─ DeliveryService.saveDelivery() └─ Capa de Datos persiste en SQLite 8. UI observa cambios via StateNameEmitter └─ muestra progreso al usuario
1. Usuario presiona "Sincronizar"
└─ Presentación (MachineListScreen)
2. WorkManager enqueue("DeliverySyncWorker")
└─ Presentación (UI)
3. Worker inicia DomainManager
└─ se inicializa P2PManager
│
└─ Domain P2P Manager
4. P2PManager.syncDeliveriesFromMachine()
└─ consulta P2P a máquina remota
│
└─ Domain P2P Manager
5. GET /p2p/machine/deliveries/1/1
└─ recibe P2PDeliveryData
│
└─ Domain P2P Manager
6. DeliveryService.create(delivery)
└─ persiste entrega sincronizada
│
└─ Datos (RutaPAEData)
7. DeliverySyncUiEmitter emite estado
└─ UI observa y muestra progreso
│
└─ Presentación (RutaPAE)
// Machine Button("Capturar") { // UI llama Domain stateManager.init(context) } // RutaPAE Button("Sincronizar") { // UI delega a Worker DeliverySyncScheduler.enqueue(context, machineId) }
// Machine Domain state.run( next = { // Domain accede a datos val beneficiaries = BeneficiaryService.findSimilar(embedding) DeliveryService.save(delivery) } ) // RutaPAE Domain suspend fun syncDeliveriesFromMachine(machineId: Long) { val machine = MachineService.getById(machineId) val deliveries = p2pGestor.connection.peer.get(...) DeliveryService.create(deliveries) }
// Machine: mediante Emitters StateNameEmitter.emit("CapturingFace") // UI observa // RutaPAE: mediante WorkManager + Emitters DeliverySyncUiEmitter.update(state) // UI observa
Las capas siempre se comunican hacia abajo:
Presentación
↓ (usa)
Dominio
↓ (usa)
Datos
No hay dependencia inversa. La comunicación hacia arriba ocurre mediante:
next(), retry() en State.run()StateFlow y publicación de eventosDomainManager singleton