Tabla de Contenidos

Decisiones arquitectónicas del proyecto PAE

ADR: Architecture Decision Records

Documento que registra las decisiones de arquitectura clave y su justificación.


ADR-001: Arquitectura Multicapa (Clean Architecture)

Fecha: Inicial

Decisión

El proyecto PAE utiliza Clean Architecture con separación clara entre:

Contexto

Alternativas consideradas

  1. MVP/MVVM simple: menos testeable, acoplamiento UI-DB
  2. Arquitectura monolítica: difícil de escalar
  3. Arquitectura hexagonal: más compleja, innecesaria para este scope

Consecuencias

Positivas:

Negativas:


ADR-002: Máquina de Estados para Entregas

Decisión

El flujo de captura de entrega en Machine se implementa como una máquina de estados lineal con 8 estados predefinidos.

WaitingForWeight → CaptureImages → CaptureFace → ComparingWeights → 
GenerateEmbedding → VerifyInDatabase → SaveDelivery → WaitForWeightRemoved

Contexto

Alternativas consideradas

  1. Procedimientos lineales: sin recuperación de errores
  2. Callbacks anidados: “callback hell”, difícil mantener
  3. Eventos (event bus): desorden de eventos async
  4. Corrutinas simples: sin persistencia de estado

Consecuencias

Positivas:

Negativas:


ADR-003: Repository Pattern para acceso a datos

Decisión

Se implementa el Repository Pattern en MachineData y RutaPAEData para abstraer acceso a persistencia.

interface Repository {
    val deliveries: Table<Delivery>
    val beneficiaries: Table<Beneficiary>
    fun create(entity: Entity): Entity
    fun update(entity: Entity): Boolean
    fun delete(entity: Entity): Boolean
}

Contexto

Alternativas consideradas

  1. Acceso directo a BD: acoplamiento fuerte
  2. ORM genérico: framework heavy (Room sería más pesado)
  3. DAO simple: menos abstracción
  4. SQLite directo: sin abstracción

Consecuencias

Positivas:

Negativas:


Decisión

La comunicación entre Machine y RutaPAE usa DirectLink (Wi-Fi Direct/hotspot) como transporte primario.

Contexto

Alternativas consideradas

  1. Solo HTTP: depende de conectividad
  2. Bluetooth: rango limitado
  3. Mesh networks: complejidad innecesaria
  4. Firebase/Cloud Messaging: requiere internet
  5. Bluetooth + HTTP: peor que P2P + HTTP

Consecuencias

Positivas:

Negativas:


ADR-005: VectorDB con embeddings 512d

Decisión

Se implementa VectorDB ORM con índices vectoriales para almacenar embeddings de beneficiarios.

@VectorColumn(dimensions = 512, distanceMetric = DistanceMetric.COSINE)
val embedding: FloatArray

Contexto

Alternativas consideradas

  1. Solo búsqueda por nombre: impreciso
  2. Fotos y búsqueda remota: viola privacidad
  3. Hashing de imágenes: pérdida de información
  4. Face API remota: depende de conectividad
  5. Almacenar embeddings sin índice: búsqueda O(n)

Consecuencias

Positivas:

Negativas:


ADR-006: Corrutinas de Kotlin para concurrencia

Decisión

Se usa Kotlin Coroutines para todas las operaciones asíncronas.

suspend fun syncDeliveriesFromMachine() { ... }
 
LaunchedEffect {
    StateNameEmitter.collect { state -> ... }
}

Contexto

Alternativas consideradas

  1. Callbacks: callback hell
  2. RxJava/RxKotlin: framework pesado
  3. Threads: manual tedioso, error-prone
  4. AsyncTask: deprecated
  5. Futures/CompletableFuture: verboso

Consecuencias

Positivas:

Negativas:


ADR-007: Emisores para comunicación inter-módulos

Decisión

Se usa patrón Observer con Flow/Emitters para comunicación entre módulos.

StateNameEmitter.emit("CapturingFace")
StateNameEmitter.collect { name -> ... }

Contexto

Alternativas consideradas

  1. Callbacks directos: acoplamiento fuerte
  2. Live Data: deprecado, Framework Android
  3. Event Bus (EventBus/Otto): reflexión, overhead
  4. StateFlow global: estado mutable shared
  5. Intent (broadcast): para cross-process, innecesario aquí

Consecuencias

Positivas:

Negativas:


ADR-008: Inyección de dependencias diferenciada

Decisión

Contexto

Alternativas consideradas

  1. Hilt en todas partes: overhead para RutaPAE
  2. Service Locator: anti-pattern
  3. Singleton global: testing difícil
  4. Factory methods: boilerplate

Consecuencias

Positivas:

Negativas:


ADR-009: Modelos P2P en Contract separado

Decisión

Se crea módulo Contract con modelos P2P compartidos entre Machine y RutaPAE.

P2PMachineState
P2PDeliveryData
P2PMachineConfiguration
// ...

Contexto

ados entre apps

Alternativas consideradas

  1. Modelos en Machine, RutaPAE importa: acoplamiento
  2. Modelos duplicados: desincronización futura
  3. Serialización ad-hoc: error-prone
  4. GraphQL schema: overhead innecesario

Consecuencias

Positivas:

Negativas:


ADR-010: WorkManager para sync en background

Decisión

Se usa WorkManager en RutaPAE para sincronización de entregas en background.

DeliverySyncWorker : CoroutineWorker {
    override suspend fun doWork(): Result
}
 
DeliverySyncScheduler.enqueue(context, machineId)

Contexto

Alternativas consideradas

  1. Foreground Service: requiere notificación
  2. Handler/Timer: impreciso
  3. Alarm Manager: timing inflexible
  4. Firebase Job Dispatcher: obsoleto
  5. Sync Adapter: para providers

Consecuencias

Positivas:

Negativas:


ADR-011: UI con Jetpack Compose

Decisión

Ambas apps usan Jetpack Compose exclusivamente para UI.

@Composable
fun LaboratoryScreen(viewModel: LaboratoryViewModel) { ... }

Contexto

Alternativas consideradas

  1. XML layouts: más verbose
  2. Flutter: cambio de stack
  3. SwiftUI: solo iOS
  4. Hybrid (Compose+XML): inconsistencia

Consecuencias

Positivas:

Negativas:


ADR-012: Dos aplicaciones separadas (Machine + RutaPAE)

Decisión

Se mantienen dos aplicaciones Android separadas con sus propios builds y releases.

Contexto

Alternativas consideradas

  1. Una app con modos: complejidad, conflicto de UX
  2. Shared codebase con build variants: difícil mantener
  3. PWA + Android: scope diferente

Consecuencias

Positivas:

Negativas:


ADR-013: SQLite + VectorDB ORM

Decisión

Se usa SQLite con VectorDB ORM en lugar de Room o Firebase.

Contexto

Alternativas consideradas

  1. Room: no tiene soporte vectorial
  2. Firebase: requiere conectividad
  3. Realm: propietario
  4. Raw SQLite: sin ORM
  5. PostgreSQL embeddo: demasiado pesado

Consecuencias

Positivas:

Negativas:


Principios guía

Estos son los principios que guían las decisiones arquitectónicas en PAE:

  1. Offline-first: el sistema funciona sin conectividad
  2. Privacy-first: datos sensibles nunca salen del dispositivo
  3. Responsabilidad única: cada módulo hace una cosa bien
  4. Inversión de dependencias: abstractas, no concretas
  5. Open/Closed: abierto para extensión, cerrado para modificación
  6. DRY (Don't Repeat Yourself): evitar duplicación
  7. KISS (Keep It Simple, Stupid): no over-engineer
  8. Testeable: todo debe testearse fácilmente
  9. Performance: observar impacto en batería y memoria
  10. User experience: la arquitectura debe servir al usuario, no al revés

Futuras decisiones pendientes