Tabla de Contenidos

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

Machine - Presentación

Ubicación: Machine/src/main/java/co/ada/paemachine/

Componentes:

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:

Flujo típico:

Button("Sincronizar") → enqueueSync() → DeliverySyncWorker → Domain
     ↑
     └── DeliverySyncUiEmitter observa progreso

Capa de Dominio

Responsabilidades

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

Hardware

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

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<Beneficiary>
    fun createBeneficiary(beneficiary: Beneficiary)
    fun getMachineEnrollmentShift(id: Long): MachineEnrollmentShift?
}

Services

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

RutaPAE - Datos

Ubicación: RutaPAEData/src/main/java/co/ada/rutapaedata/

Repository

class Repository {
    fun getMachineById(id: Long): Machine?
    fun getAllMachines(): List<Machine>
    fun saveDelivery(delivery: Delivery)
    fun getDeliveriesByMachineId(machineId: Long): List<Delivery>
}

Services

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

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:

Inyección de dependencias

Machine

RutaPAE

MachineDomain / RutaPAEDomain

Ventajas de esta arquitectura