====== Guía de referencia para desarrolladores ====== ===== Acceso rápido a componentes clave ===== ==== Machine: Flujo de captura de entrega ==== **Archivo de entrada**: ''Machine/src/main/java/co/ada/paemachine/screens/LaboratoryScreen.kt'' // Usuario presiona botón Button("Capturar") { viewModel.startCapture() } // ViewModel delega fun startCapture() { stateManager.init(context) // Inicia máquina de estados } **Máquina de estados**: ''MachineDomain/src/main/java/co/ada/domain/state/'' StateManager.kt ├── init() : Boolean ├── cycle() : StateResult ├── getCurrentState(): String └── states.toMutableList() ├── WaitingForWeightState.kt ├── CaptureImagesState.kt ├── CaptureFaceState.kt ├── ComparingWeightsState.kt ├── GenerateEmbeddingState.kt ├── VerifyInDatabaseState.kt ├── SaveDeliveryState.kt └── WaitForWeightRemovedState.kt **Cambios de estado emitidos**: ''MachineDomain/src/main/java/co/ada/domain/emitters/'' StateNameEmitter.emit(currentState) // "CapturingFace" StateMessageEmitter.emit(message) // "Por favor espere..." **UI observa en Composable**: LaunchedEffect { StateNameEmitter.collect { stateName -> updateUI(stateName) } } ---- ==== RutaPAE: Sincronización de máquinas ==== **Punto de entrada**: ''RutaPAE/src/main/java/co/ada/rutapae/screens/MachineListScreen.kt'' Button("Sincronizar") { onSyncClicked() // enqueue worker } private fun onSyncClicked() { DeliverySyncScheduler.enqueue( context, machineId = 1L, machineDatabase = machineForeignId ) } **Worker**: ''RutaPAE/src/main/java/co/ada/rutapae/workers/DeliverySyncWorker.kt'' class DeliverySyncWorker : CoroutineWorker() { override suspend fun doWork(): Result { // Inicializa dominio val initialized = DomainManager.init(context) // Accede a P2PManager val manager = DomainManager.p2pManager ?: return Result.retry() // Sincroniza entregas val result = manager.syncDeliveriesFromMachine( machineId, machineDatabaseId ) return if (result.success) Result.success() else Result.retry() } } **Dominio**: ''RutaPAEDomain/src/main/java/co/ada/rutapaedomain/'' DomainManager.kt (singleton) ├── init(context): Boolean ├── close(context): Boolean └── p2pManager: P2PManager P2PManager.kt ├── discoverableMachineIds(): Set ├── discoveredMachineCandidates(): Set ├── connectMachine(machineId): P2PGestor? ├── connectMachineHotspot(ssid): P2PGestor? └── syncDeliveriesFromMachine(): SyncDeliveriesResult **Persistencia**: ''RutaPAEData/src/main/java/co/ada/rutapaedata/'' DeliveryService.createDeliveries(deliveries: List) { // Repository.deliveries.create(delivery) // INSERT INTO Delivery(...) } MachineService.updateMachine(machine: Machine) { // Repository.machines.update(machine) } ---- ===== Servicios de datos ===== ==== MachineData ==== **Ubicación**: ''MachineData/src/main/java/co/ada/data/services/'' object DeliveryService { fun getAll(): List fun create(delivery: Delivery): Delivery fun update(delivery: Delivery): Boolean fun delete(id: Long): Boolean } object BeneficiaryService { fun getAll(): List fun create(beneficiary: Beneficiary): Beneficiary fun findSimilar(embedding: FloatArray, similarity: Float = 0.7f): List fun update(beneficiary: Beneficiary): Boolean } object MachineEnrollmentShiftService { fun getById(id: Long): MachineEnrollmentShift? fun create(shift: MachineEnrollmentShift): MachineEnrollmentShift } ==== RutaPAEData ==== **Ubicación**: ''RutaPAEData/src/main/java/co/ada/rutapaedata/services/'' object MachineService { fun getAll(): List fun getById(id: Long): Machine? fun create(machine: Machine): Machine fun update(machine: Machine): Boolean } object DeliveryService { fun getAll(): List fun getByMachineId(machineId: Long): List fun create(delivery: Delivery): Delivery fun createMany(deliveries: List): List } ---- ===== Conexión P2P ===== ==== Descubrimiento de máquinas ==== RutaPAE DirectLink Machine (cliente) (broadcast) (servidor) │ │ │ ├─ P2PManager │ │ │ .discoverableMachineIds() ├───► broadcast peers ───► │ │ │ │ List ◄─── Device A, B, C ◄──┤ │◄───────────────────────────┤ │ ==== Conexión a máquina ==== // RutaPAE val p2pGestor = p2pManager.connectMachine(machineId = 1L) ?: return // conexión fallida // Ahora disponible p2pGestor.connection.peer.get("/p2p/machine") ==== Endpoints disponibles en Machine P2P ==== GET /p2p/machine ├─ P2PMachineState (máquina actual) GET /p2p/machine/deliveries/{campusId}/{shiftId} ├─ P2PDeliveriesPageResponse (entregas paginadas) GET /p2p/machine/configuration ├─ P2PMachineConfiguration (config de máquina) POST /p2p/machine/configuration └─ actualiza configuración DELETE /p2p/machine/deliveries/{deliveryId} └─ marca entrega como confirmada en remoto ---- ===== Modelos principales ===== ==== Entidades de base de datos ==== === Delivery === @DataTable("Delivery") data class Delivery( @PrimaryKey val id: Long = 0, val weight: Float = 0f, val beneficiaryPhotoPath: String? = null, val alimentPhotoPath: String? = null, val similarity: Float = 0f, val processTimeMs: Long = 0L, val serverDeliveryId: String? = null, val synchronized: Boolean = false, val createdAt: Long = 0L ) === Beneficiary === @DataTable("Beneficiary") data class Beneficiary( @PrimaryKey val id: Long = 0, @VectorColumn(dimensions = 512, distanceMetric = DistanceMetric.COSINE) val embedding: FloatArray, val name: String, val remoteBeneficiaryId: String? = null, val enrollmentId: String? = null, val lastRecognitionAtMs: Long = 0L ) === Machine (RutaPAE) === @DataTable("Machine") data class Machine( @PrimaryKey val id: Long = 0, val machineId: String, val name: String, val machineStatus: P2PMachineStatus = P2PMachineStatus.Inactive, val unsyncedDeliveries: Long = 0, val localSyncedDeliveries: Long = 0 ) ==== Modelos P2P (Contract) ==== data class P2PMachineState( val id: String = "", val name: String = "", val unsyncedDeliveries: Long = 0, val serverSyncedDeliveries: Long = 0, val status: String = "Inactive" // Active, Inactive, Error ) data class P2PDeliveryData( val id: String, val weight: Float, val beneficiaryPhoto: String? = null, // URL val alimentPhoto: String? = null, // URL val similarity: Float = 0f, val processTimeMs: Long = 0L ) ---- ===== Configuración de proyecto ===== ==== Settings ==== **Ubicación**: ''settings.gradle.kts'' include( ":Machine", ":RutaPAE", ":MachineDomain", ":MachineData", ":RutaPAEDomain", ":RutaPAEData", ":Contract", ":DirectLink", ":VectorialDB", ":ComputerVision", ":Core" ) ==== Versiones ==== **Ubicación**: ''gradle/libs.versions.toml'' [versions] kotlin = "2.3.10" compose = "1.5.4" min-sdk = "27" compile-sdk = "36" jvm-target = "17" ==== Android (min/target) ==== * **minSdk**: 27 (Android 8.1) * **targetSdk**: 36 (Android 15) * **compileSdk**: 36.1 ---- ===== Testing ===== ==== Estructura recomendada ==== Machine/src/ ├── main/ │ └── java/co/ada/paemachine/ │ ├── screens/ │ ├── viewmodels/ │ └── di/ │ ├── test/ │ └── java/co/ada/paemachine/ │ ├── viewmodels/ │ └── ... │ └── androidTest/ └── java/co/ada/paemachine/ ├── screens/ └── ... ==== Testing del StateManager ==== @Test fun testStateTransition() { val manager = StateManager( repository = mockRepository, hardware = mockHardware ) manager.init(mockContext) // assert state is WaitingForWeight } ==== Testing de servicios ==== @Test fun testBeneficiarySearch() { val service = BeneficiaryService val results = service.findSimilar(embedding, similarity = 0.8f) assertTrue(results.isNotEmpty()) } ---- ===== Debugging ===== ==== Logs ==== **Core logging**: ''Core/src/main/java/co/ada/core/logging/'' Logger.d("StateManager", "Transitioning to ${state.name}") Logger.e("P2PManager", "Connection failed: $error") Logger.i("DeliveryService", "Delivery saved: $deliveryId") ==== Debug points ==== **Machine**: Observa emisores en Logcat adb logcat | grep StateNameEmitter adb logcat | grep DeliverySyncUiEmitter **RutaPAE**: Verifica logs de Worker adb logcat | grep DeliverySyncWorker adb logcat | grep P2PManager ---- ===== Common Tasks ===== ==== Agregar nuevo estado a Machine ==== - Crear: ''MachineDomain/src/.../state/MyNewState.kt'' - Implementar interfaz ''State'' - Agregar a lista en ''StateManager.workflow'' - Emitir cambios vía ''StateNameEmitter'' ==== Agregar nuevo servicio de datos ==== - Crear: ''MachineData/src/.../services/MyService.kt'' - Implementar CRUD - Usar ''Repository.myTable.create/update/delete()'' - Mapear entidades si es necesario ==== Cambiar endpoints HTTP ==== - Editar: ''MachineDomain/src/.../endpoints/MachineEndpoints.kt'' - Actualizar URL base si es necesario (Fuel) - Actualizar modelos de respuesta si cambia schema - Actualizar mapping en servicios ==== Agregar nueva ruta P2P ==== - En Machine, agregar endpoint: ''Machine/src/.../p2p/P2PMachine.kt'' - En Contract, agregar modelo: ''Contract/src/.../models/'' - Usar en RutaPAE: ''P2PManager.syncXxx()'' ---- ===== Referencias rápidas ===== ^ Tarea ^ Archivo ^ Función ^ | Ver estado actual | ''MachineDomain/.../StateManager.kt'' | ''getCurrentState()'' | | Emitir evento UI | ''MachineDomain/.../emitters/StateNameEmitter.kt'' | ''emit(name)'' | | Buscar beneficiarios | ''MachineData/.../BeneficiaryService.kt'' | ''findSimilar()'' | | Guardar entrega | ''MachineData/.../DeliveryService.kt'' | ''create()'' | | Conectar P2P | ''RutaPAEDomain/.../P2PManager.kt'' | ''connectMachine()'' | | Sincronizar entregas | ''RutaPAEDomain/.../P2PManager.kt'' | ''syncDeliveriesFromMachine()'' | | Configurar Database | ''gradle/libs.versions.toml'' | versiones SDK | | Ver modelos P2P | ''Contract/src/.../models/'' | estructuras P2P | | Emitir progreso sync | ''RutaPAE/.../emitters/DeliverySyncUiEmitter.kt'' | ''update()'' | | Enqueue sync worker | ''RutaPAE/.../DeliverySyncScheduler.kt'' | ''enqueue()'' |