====== Capas de Arquitectura: Presentación, Dominio y Datos ====== ===== Visión general ===== 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. ===== Capa de Presentación ===== ==== Responsabilidades ==== * Renderizar la UI * Capturar interacciones del usuario * Mostrar estado actual * Delegar acciones al dominio * NO contiene lógica de negocio ==== Machine - Presentación ==== **Ubicación**: ''Machine/src/main/java/co/ada/paemachine/'' Componentes: * **Screens**: pantallas principales * ''LaboratoryScreen'': captura de entregas * ''ShiftSelectionScreen'': selección de jornada * **Views/Composables**: componentes reutilizables * **ViewModels**: estado de UI **Flujo típico**: Button("Capturar") → ViewModel.startCapture() → Domain ↑ └── ViewModel.stateFlow observa cambios ==== RutaPAE - Presentación ==== **Ubicación**: ''RutaPAE/src/main/java/co/ada/rutapae/'' Componentes: * **Screens**: * ''MachineListScreen'': lista de máquinas * ''MachineDetailScreen'': detalle de máquina * ''MachineConfigurationScreen'': configuración * **Components**: tarjetas, listas, indicadores * **Workers**: tareas en segundo plano (WorkManager) **Flujo típico**: Button("Sincronizar") → enqueueSync() → DeliverySyncWorker → Domain ↑ └── DeliverySyncUiEmitter observa progreso ===== Capa de Dominio ===== ==== Responsabilidades ==== * Implementar casos de uso * Aplicar reglas de negocio * Orquestar flujos complejos * Coordinar entre presentación y datos * Manejar reintentos y errores ==== Machine - Dominio ==== **Ubicación**: ''MachineDomain/src/main/java/co/ada/domain/'' Componentes clave: === StateManager === Orquesta la máquina de estados de entregas ├── WaitingForWeight ├── CaptureImages ├── CaptureFace ├── ComparingWeights ├── GenerateEmbedding ├── VerifyInDatabase ├── SaveDelivery └── WaitForWeightRemoved === P2PManager === Gestiona sincronización P2P ├── connect(peer) ├── syncDeliveries() ├── getMachineConfiguration() └── updateMachineConfiguration() === Services === * ''SyncDeliveries'': subeentregas al backend HTTP * ''SyncBeneficiaries'': descarga beneficiarios * ''SyncMachineEnrollment'': sincroniza jornadas * ''HealthCheck'': verifica estado del sistema === Hardware === * ''ScaleManager'': interfaz con balanza * ''Camera2Service'': captura de imágenes * ''BoardLedManager'': indicadores LED ==== RutaPAE - Dominio ==== **Ubicación**: ''RutaPAEDomain/src/main/java/co/ada/rutapaedomain/'' Componentes clave: === P2PManager === Gestiona conexión P2P con máquinas ├── discoverableMachineIds() ├── discoveredMachineCandidates() ├── connectMachine(id) ├── connectMachineHotspot(ssid) └── syncDeliveriesFromMachine() === DomainManager === Inicializa y coordina servicios ├── init(context) ├── close(context) └── p2pManager acceso ===== Capa de Datos ===== ==== Responsabilidades ==== * Acceso a persistencia local * Operaciones CRUD * Mapeo de entidades * Caché y estrategias de consistencia * NO contiene lógica de negocio ==== Machine - Datos ==== **Ubicación**: ''MachineData/src/main/java/co/ada/data/'' === Repository === class Repository { fun getDeliveryById(id: Long): Delivery? fun saveDelivery(delivery: Delivery) fun getAllBeneficiaries(): List fun createBeneficiary(beneficiary: Beneficiary) fun getMachineEnrollmentShift(id: Long): MachineEnrollmentShift? } === Services === * ''DeliveryService'': CRUD de entregas * ''BeneficiaryService'': CRUD de beneficiarios * ''MachineEnrollmentService'': CRUD de jornadas === Entidades === Delivery ├── 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 === Base de datos === * **Tipo**: SQLite + VectorDB ORM * **Índices**: índice vectorial para embeddings * **Ubicación**: ''/data/data/co.ada.paemachine/database.db'' ==== RutaPAE - Datos ==== **Ubicación**: ''RutaPAEData/src/main/java/co/ada/rutapaedata/'' === Repository === class Repository { fun getMachineById(id: Long): Machine? fun getAllMachines(): List fun saveDelivery(delivery: Delivery) fun getDeliveriesByMachineId(machineId: Long): List } === Services === * ''MachineService'': gestión de máquinas locales * ''DeliveryService'': gestión de entregas sincronizadas === Entidades === Machine ├── 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) === Base de datos === * **Tipo**: SQLite + VectorDB ORM * **Ubicación**: ''/data/data/co.ada.rutapae/database.db'' ===== Flujo de datos completo ===== ==== Escenario: Entrega en Machine ==== 1. 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 ==== Escenario: Sincronización en RutaPAE ==== 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) ===== Comunicación entre capas ===== ==== De Presentación a Dominio ==== // Machine Button("Capturar") { // UI llama Domain stateManager.init(context) } // RutaPAE Button("Sincronizar") { // UI delega a Worker DeliverySyncScheduler.enqueue(context, machineId) } ==== De Dominio a Datos ==== // 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) } ==== De Dominio a Presentación ==== // Machine: mediante Emitters StateNameEmitter.emit("CapturingFace") // UI observa // RutaPAE: mediante WorkManager + Emitters DeliverySyncUiEmitter.update(state) // UI observa ===== Inversión de dependencias ===== Las capas siempre se comunican hacia abajo: Presentación ↓ (usa) Dominio ↓ (usa) Datos No hay dependencia inversa. La comunicación hacia arriba ocurre mediante: * **Callbacks**: ''next()'', ''retry()'' en ''State.run()'' * **Emitters**: ''StateFlow'' y publicación de eventos * **WorkManager**: delega desde Presentación a Worker ===== Inyección de dependencias ===== ==== Machine ==== * **Dagger2/Hilt** * Inyección automática en Activities/ViewModels * Scope: Activity, Singleton ==== RutaPAE ==== * **Inyección manual** * ''DomainManager'' singleton * Servicios accesibles globalmente ==== MachineDomain / RutaPAEDomain ==== * **Inyección por constructor** * Dependencias pasadas explícitamente ===== Ventajas de esta arquitectura ===== * **Testabilidad**: cada capa se prueba independientemente * **Mantenibilidad**: responsabilidades claras * **Reusabilidad**: módulos reutilizables en otros proyectos * **Escalabilidad**: fácil agregar nuevas funcionalidades * **Desacoplamiento**: cambios en una capa no afectan otras