Saltar a contenido

Documento del prototipo, no del producto

Refleja los modelos Kotlin tal como están hoy. Para equivalencias con el modelo de datos del backend, ver datos/modelo-datos.md y datos/equivalencias-estados.md.


Modelos de dominio

Detalle de todas las data classes, enums, DTOs y mapeadores definidos en la APK.


Resumen

Capa Archivo Tipo Usado en
domain User.kt data class No se usa todavía
domain LockerAddress.kt (dentro de User.kt) data class Dashboard, Perfil, LockerAddressSection
domain Reporte.kt data class @Serializable 4 pantallas, CustomNavTypes
domain StatusStep.kt (dentro de Reporte.kt) data class @Serializable PackageTimeline, ReporteDetalleScreen
domain Shipment.kt data class No se usa (componentes de envíos están huérfanos)
domain Contact.kt data class No se usa todavía
domain SampleModel.kt data class No se usa (placeholder)
domain SampleRepository.kt interface No implementado
core Resource.kt sealed class No se usa todavía
data UserDto.kt + LockerInfoDto @Serializable No se usa
data ReporteDto.kt + ReporteResponseDto @Serializable No se usa
data ShipmentDto.kt @Serializable No se usa
data ContactDto.kt @Serializable No se usa

Userdomain/model/User.kt

data class User(
    val id: String,
    val firstName: String,
    val lastName: String,
    val email: String,
    val phone: String,
    val lockerId: String,
    val address: LockerAddress
)

data class LockerAddress(
    val line1: String = "1940 NW 70th Ave",   // 1940 NW 70th Ave, Miami FL
    val line2: String = "ISLABOX",              // Marca
    val city: String = "Miami",
    val state: String = "Florida",
    val zipCode: String = "33126",
    val phone: String = "+1 (786) 651-0451"
)

Análisis

  • LockerAddress tiene valores por defecto de muestra (Miami).
  • ❌ No tiene province ni municipality (esos son para destinatarios en Cuba).
  • ❌ No tiene country (la doc dice address_type con country default según tipo).
  • ❌ No es @Serializable (el modelo User no se pasa por navegación, pero LockerAddress sí viaja en UserDto).
  • ⚠️ phone está en LockerAddress pero también está en User. Hay duplicación.

Discrepancias con la doc (modelo-datos.mdaddresses)

Campo doc Campo app Notas
address_type (shipping/billing/recipient) ❌ No existe Se necesita cuando hay múltiples destinatarios
full_name ❌ No existe Está separado en User.firstName + User.lastName
phone, alternate_phone Solo phone en User y LockerAddress Falta alternate_phone
address_line1 line1 Nombre distinto, mismo concepto
address_line2 line2 Nombre distinto, mismo concepto
city, state, zip_code city, state, zipCode camelCase en la app, snake_case en la doc
country (default según address_type) ❌ No existe
province (Cuba) ❌ No existe Es para destinatarios
municipality (Cuba) ❌ No existe Es para destinatarios
delivery_references ❌ No existe Notas de entrega
is_default ❌ No existe Para múltiples direcciones
created_at, updated_at ❌ No existe

Reportedomain/model/Reporte.kt

Es el modelo más complejo de la app. Mezcla conceptos que la doc tiene separados.

@Serializable
data class Reporte(
    val tienda: String = "",                      // → purchase_reports.marketplace
    val numeroOrden: String = "",                 // → purchase_reports.order_number
    val numeroTracking: String = "",              // → tracking_numbers[0].tracking_number
    val fechaCompra: String = "",                 // → purchase_reports.created_at
    val fechaRecepcion: String = "",              // → physical_packages.received_at
    val peso: String = "",                        // → physical_packages.weight_kg
    val dimensiones: String = "",                 // → physical_packages.{l,w,h}_cm
    val estadoEmbalaje: String = "",              // → physical_packages.notes_internal
    val fotos: List<String> = emptyList(),        // → physical_packages + package_photos
    val categoria: String = "",                   // → customs_categories.code
    val descripcion: String = "",                 // → purchase_items.description
    val cantidad: Int = 1,                        // → purchase_items.quantity
    val valorPagado: String = "",                 // → purchase_items.unit_value
    val moneda: String = "USD",                   // → purchase_reports.total_declared_currency
    val remitenteId: String? = null,              // → senders.id
    val destinatarioId: String? = null,           // → recipients.id
    val deseaConsolidar: Boolean? = null,         // preference
    val capturaUri: String? = null,               // → order-evidence
    val enlaceProducto: String = "",              // → purchase_items.product_url
    val instruccionesEspeciales: String = "",     // → notes
    val serviciosAdicionales: List<String> = emptyList(), // → service_requests
    val estado: String = "Reportada",             // ❌ String, no enum
    val diasEnAlmacen: Int = 0,                   // → physical_packages.storage_days
    val fechaLimiteSinRecargo: String = "",       // calculado
    val costoAlmacenaje: Double = 0.0,            // → physical_packages.storage_charge
    val timeline: List<StatusStep> = emptyList()  // → package_events
)

@Serializable
data class StatusStep(
    val status: String,
    val date: String,
    val time: String,
    val isCompleted: Boolean = false
)

Constantes del archivo

val CATEGORIAS_PRODUCTO = listOf(
    "Ropa y Calzado", "Electrónicos", "Hogar", "Juguetes",
    "Repuestos", "Medicinas", "Alimentos", "Otros"
)

val SERVICIOS_ADICIONALES = listOf(
    "Inspección Visual", "Prueba Funcional", "Reempaque"
)

Valores de estado encontrados en el código (hardcoded)

Valor Aparece en
"Reportada" Default del modelo
"Esperando llegada al almacén" ReportesScreen ejemplo 1
"Esperando tracking" ReportesScreen ejemplo 2
"Tracking recibido" ReportesScreen ejemplo 3
"Requiere corrección" ReportesScreen ejemplo 4
"En almacén" PaquetesScreen ejemplo 1, ReporteDetalleScreen
"Listo para enviar" PaquetesScreen ejemplo 2, ReporteDetalleScreen
"En tránsito" PaquetesScreen ejemplo 3, ReporteDetalleScreen
"En servicio adicional" ReporteDetalleScreen
"Listo para consolidar" ReporteDetalleScreen
"Consolidado" ReporteDetalleScreen
"Incidencia" ReporteDetalleScreen

Discrepancias con la doc

16 estados de paquete físico (en estados.md):

prealertado, en_camino_al_almacen, recibido_sin_identificar, recibido_identificado,
pendiente_conciliacion, en_almacen, esperando_instrucciones, en_servicio_adicional,
listo_para_consolidar, reservado_para_consolidacion, consolidado, en_transito,
entregado, retenido_por_incidencia, devuelto_a_tienda, descartado_o_cerrado

8 estados de envío (en estados.md):

borrador, pendiente_kyc, pendiente_destinatario, pendiente_cotizacion, cotizado,
pendiente_pago, pagado, en_preparacion, despachado_desde_almacen, en_transito_a_cuba,
en_aduana, liberado_de_aduana, en_reparto, entregado, incidencia, cancelado

8 estados de prealerta (en estados.md):

borrador, reportada, reportada_sin_tracking, tracking_actualizado,
vinculada_a_paquete, cancelada_por_usuario, cancelada_por_operador, requiere_correccion

Estados que la app usa que NO existen en la doc: - "Esperando llegada al almacén" → debería ser "prealertado" o "en_camino_al_almacen" - "Esperando tracking" → debería ser "reportada_sin_tracking" - "Tracking recibido" → debería ser "tracking_actualizado" - "En tránsito" → debería ser "en_transito" (con guión bajo) o "en_transito_a_cuba" - "Listo para enviar" → no existe como estado, podría ser "esperando_instrucciones" o "listo_para_consolidar"

Estados de la doc que la app no usa: - 11 de 16 estados de paquete físico no aparecen - 14 de 16 estados de envío no aparecen - 4 de 8 estados de prealerta no aparecen

Decisión recomendada: migrar estado: String a enum class ReporteStatus con valores que coincidan con la doc. O mejor: separar en PurchaseReportStatus y PhysicalPackageStatus.


Contactdomain/model/Contact.kt

data class Contact(
    val id: String,
    val name: String,
    val province: String,
    val address: String,
    val phone: String,
    val identityCard: String? = null
)

val PROVINCIAS_CUBA = listOf(
    "Pinar del Río", "Artemisa", "La Habana", "Mayabeque", "Matanzas",
    "Cienfuegos", "Villa Clara", "Sancti Spíritus", "Ciego de Ávila",
    "Camagüey", "Las Tunas", "Holguín", "Granma", "Santiago de Cuba",
    "Guantánamo", "Isla de la Juventud"
)

Análisis

  • ✅ Las 16 provincias coinciden con la realidad cubana.
  • identityCard opcional (no todos los destinatarios tienen CI en el sistema).
  • ❌ No tiene municipality (la doc lo pide).
  • ❌ No tiene alternate_phone.
  • ❌ No tiene deliveryReferences / notas de entrega.
  • ❌ No tiene isSameAsUser (la doc lo pide para destinatarios).
  • ❌ No es @Serializable (no se pasa por navegación).
  • ❌ No se usa en ninguna pantalla (está en domain/model pero no se importa en ui/).

Discrepancias con la doc (modelo-datos.mdrecipients)

Campo doc Campo app Notas
user_id (FK) ❌ No existe
full_name name
identity_document identityCard camelCase distinto
phone phone
alternate_phone
address_line1 address (campo único libre) La app lo trata como string completo
province province
municipality
delivery_references
is_same_as_user
created_at, updated_at

Shipmentdomain/model/Shipment.kt

data class Shipment(
    val id: String,
    val status: String,
    val estimatedDelivery: String,
    val platform: String,
    val shippingType: String? = null,    // "Aéreo" / "Marítimo"
    val weight: String? = null,
    val daysInWarehouse: Int? = null
)

Análisis

  • No se usa en ninguna pantalla (los componentes de envíos que lo consumen — EnvioCard, ShipmentTimeline — están huérfanos).
  • ⚠️ shippingType como String en vez de enum (Aereo / Maritimo o air / sea según doc).
  • ❌ No tiene recipientId, consolidationId, master_guide (lo que pide modelo-datos.md).
  • ❌ No tiene timestamps de eventos.
  • ⚠️ status: String — debería ser enum ShipmentStatus (ya existe en ShipmentTimeline.kt).

SampleModeldomain/model/SampleModel.kt

data class SampleModel(val id: Int, val name: String)

Estado: placeholder muerto. Se puede eliminar.


SampleRepositorydomain/repository/SampleRepository.kt

interface SampleRepository {
    fun getSampleData(): Flow<List<SampleModel>>
}

Estado: interfaz sin implementación. Se puede eliminar junto con SampleModel.


Resourcecore/util/Resource.kt

sealed class Resource<T>(val data: T? = null, val message: String? = null) {
    class Success<T>(data: T) : Resource<T>(data)
    class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
    class Loading<T>(data: T? = null) : Resource<T>(data)
}

Estado: ✅ bien diseñado, pero no se usa en ningún lado. Es el patrón correcto para cuando se conecte backend.


DTOs — data/remote/dto/

UserDto.kt

@Serializable
data class UserDto(
    val uid: String,
    val first_name: String,
    val last_name: String,
    val email_address: String,
    val phone_number: String,
    val locker_id_code: String,
    val auth_token: String? = null,
    val locker_info: LockerInfoDto? = null
)

@Serializable
data class LockerInfoDto(
    val address_line: String,
    val company: String,
    val city: String,
    val state: String,
    val zip: String,
    val phone: String
)

fun UserDto.toDomain(): User { ... }

ReporteDto.kt

@Serializable
data class ReporteDto(
    val tienda: String,
    val numeroOrden: String,
    val numeroTracking: String,
    val fechaCompra: String,
    val categoria: String,
    val descripcion: String,
    val cantidad: Int,
    val valorPagado: Double,              // ❌ Mismatch con Reporte.valorPagado: String
    val moneda: String = "USD",
    val remitenteId: String,
    val destinatarioId: String,
    val deseaConsolidar: Boolean,
    val capturaBase64: String? = null,     // ❌ Reporte tiene capturaUri: String?
    val enlaceProducto: String? = null,
    val instruccionesEspeciales: String? = null,
    val serviciosAdicionales: List<String> = emptyList()
)

@Serializable
data class ReporteResponseDto(
    val id: String,
    val status: String,
    val message: String
)

Sin mapeador toDomain().

ShipmentDto.kt

@Serializable
data class ShipmentDto(
    val tracking_number: String,
    val status_label: String,
    val est_delivery_date: String,
    val source_platform: String,
    val transport_type: String? = null,
    val parcel_weight: Double? = null,     // ❌ Mismatch con Shipment.weight: String?
    val storage_days: Int? = null
)

fun ShipmentDto.toDomain(): Shipment { ... }

ContactDto.kt

@Serializable
data class ContactDto(
    val contact_id: String,
    val full_name: String,
    val cuban_province: String,
    val full_address: String,
    val mobile_phone: String,
    val ci_number: String? = null
)

fun ContactDto.toDomain(): Contact { ... }

Observaciones de DTOs

  1. Convención inconsistente: UserDto y ContactDto usan snake_case, ReporteDto usa camelCase.
  2. Mismatch de tipos:
  3. ReporteDto.valorPagado: Double vs Reporte.valorPagado: String
  4. ShipmentDto.parcel_weight: Double vs Shipment.weight: String
  5. ReporteDto.capturaBase64: String? vs Reporte.capturaUri: String?
  6. DTOs incompletos: ReporteDto no incluye los campos de physical_packages (peso, dimensiones, estado, días en almacén, etc.).
  7. Sin mapeador para ReporteDto: los otros 3 sí lo tienen.
  8. Dtos son read-only en la app: ningún consumidor.

Lo que dice la doc sobre el modelo (datos/modelo-datos.md)

⚠️ Esta es la lista de entidades que la doc define. La app actual no modela ninguna explícitamente, pero el Reporte aplana varias:

Entidad doc ¿Modelada en app? Cómo se aplana
users Parcial User (sin campos de auth)
addresses Parcial LockerAddress (sin tipo, sin provincia, sin municipio)
locker_codes Solo User.lockerId: String
kyc_records No existe
purchase_reports Parcial Mezclado en Reporte
purchase_items Parcial Reporte.cantidad, descripcion, valorPagado, categoria, enlaceProducto
tracking_numbers Reporte.numeroTracking (1:1, doc dice 1:N)
customs_categories CATEGORIAS_PRODUCTO (8 strings sin metadata)
physical_packages Parcial Campos con prefijo fechaRecepcion, peso, dimensiones, etc.
package_events Parcial StatusStep (sin operador, sin visible_to_user)
package_photos Reporte.fotos: List<String>
warehouse_locations No existe
consolidations No existe
consolidation_packages No existe
shipments Parcial Shipment (sin FKs, sin master_guide)
quotes No existe
payments No existe
payment_attempts No existe
charges No existe
invoices No existe
service_requests Reporte.serviciosAdicionales: List<String>
incidents No existe
notifications No existe
audit_logs No existe
referrals No existe
credit_balances No existe
credit_movements No existe
promo_cards No existe
file_attachments No existe (las URLs son strings directos)
api_tokens UserDto.auth_token: String?
device_tokens No existe
senders Parcial Reporte.remitenteId: String? (1:1, doc dice 1:N)
recipients Parcial Contact (sin usar en UI)

Tareas de modelos

# Tarea Prioridad
1 Decidir: ¿separar Reporte en PurchaseReport + PhysicalPackage? Alta
2 Cambiar Reporte.estado: String por enum class ReporteStatus con valores de la doc Alta
3 Crear LockerCode data class con prefix, sequential, checkDigit, status Media
4 Crear KycRecord data class con 9 estados y campos de doc Media
5 Crear ServiceRequest data class con 9 estados Media
6 Crear CustomsCategory con metadata (accepted, risk_level, allows_air, etc.) Media
7 Crear Consolidation + ConsolidationPackage Media
8 Crear Shipment extendido con FKs y master_guide Media
9 Crear Quote (con expiración) y Payment (con payment_method_mix) Media
10 Crear Recipient y Sender extendidos (con alternate_phone, municipality, etc.) Alta
11 Crear Notification con canal (push/email/whatsapp) Media
12 Eliminar SampleModel y SampleRepository (código muerto) Baja
13 Usar Resource<T> en al menos una pantalla para sentar el patrón Baja
14 Unificar convención de nombres en DTOs (todo snake_case o todo camelCase) Baja
15 Arreglar mismatch de tipos: valorPagado: Double en DTO, String en domain Alta