E1 — Modelado Técnico UML¶
Competencia CE0214 · Diagramas estructurales y de comportamiento
Proyecto: LOGYX — Sistema Operativo Logístico Colaborativo para PYMEs
Equipo: Jorge Gutiérrez Miranda · Fabrizio Sanchez Saravia · Alex Coila Jarita
Herramienta: Mermaid (exportar a imagen para entrega formal)
Versión: 1.0 · Junio 2026
1. Diagrama de Casos de Uso¶
1.1 Sistema Completo — Vista General¶
graph LR
subgraph Sistema LOGYX
UC1[Registrarse y verificar RUC]
UC2[Iniciar sesión]
UC3[Publicar solicitud de carga]
UC4[Ver subasta y recibir ofertas]
UC5[Negociar precio]
UC6[Aceptar oferta]
UC7[Ver tracking del envío]
UC8[Calificar contraparte]
UC9[Gestionar documentos]
UC10[Reportar incidencia]
UC11[Ver marketplace de cargas]
UC12[Ofertar en subasta]
UC13[Publicar viaje disponible]
UC14[Ver Smart Load Plan]
UC15[Ver cargas de retorno]
UC16[Actualizar estado del envío]
UC17[Subir evidencia de entrega]
UC18[Ver dashboard global]
UC19[Asignar carrier manualmente]
UC20[Gestionar incidencias]
UC21[Ver ruta asignada]
UC22[Actualizar parada]
UC23[Capturar firma y foto]
end
PYME((PYME)) --> UC1
PYME --> UC2
PYME --> UC3
PYME --> UC4
PYME --> UC5
PYME --> UC6
PYME --> UC7
PYME --> UC8
PYME --> UC9
PYME --> UC10
Carrier((Transportista)) --> UC1
Carrier --> UC2
Carrier --> UC11
Carrier --> UC12
Carrier --> UC13
Carrier --> UC14
Carrier --> UC15
Carrier --> UC5
Carrier --> UC8
Carrier --> UC9
Driver((Conductor)) --> UC2
Driver --> UC21
Driver --> UC22
Driver --> UC23
Driver --> UC10
Operator((Operador)) --> UC18
Operator --> UC19
Operator --> UC20
Operator --> UC16
1.2 Casos de Uso — Módulo Marketplace y Subastas (detalle)¶
graph LR
subgraph Marketplace y Subastas
A[Publicar solicitud de carga]
B[Sistema calcula precio sugerido]
C[Sistema abre subasta automática]
D[Ver solicitudes en marketplace]
E[Ofertar en subasta]
F[Validar price floor]
G[Validar capacidad disponible]
H[Ver ranking de ofertas]
I[Negociar con contra-oferta]
J[Aceptar oferta]
K[Sistema cierra subasta y notifica]
end
PYME((PYME)) --> A
A -.include.-> B
A -.include.-> C
D -.include.-> F
D -.include.-> G
Carrier((Transportista)) --> D
Carrier --> E
E -.include.-> F
E -.include.-> G
PYME --> H
PYME --> I
Carrier --> I
PYME --> J
J -.include.-> K
1.3 Casos de Uso — Módulo Tracking y Driver (detalle)¶
graph LR
subgraph Tracking y Ejecución
T1[Ver ruta asignada]
T2[Navegar a parada]
T3[Marcar estado de parada]
T4[Tomar foto de entrega]
T5[Capturar firma digital]
T6[Reportar incidencia en ruta]
T7[Ver tracking en mapa]
T8[Ver historial de estados]
end
Driver((Conductor)) --> T1
Driver --> T2
Driver --> T3
T3 -.include.-> T4
T3 -.include.-> T5
Driver --> T6
PYME((PYME)) --> T7
PYME --> T8
Operator((Operador)) --> T7
Operator --> T8
2. Diagrama de Clases (Alto Nivel)¶
classDiagram
class Organization {
+UUID id
+String name
+String ruc
+OrgType type
+Boolean verified
+LocalDateTime verifiedAt
+Integer trustScore
+LocalDateTime createdAt
+verify() void
+calculateTrustScore() Integer
}
class Profile {
+UUID id
+UUID organizationId
+String fullName
+String phone
+ProfileRole role
+LocalDateTime createdAt
}
class Vehicle {
+UUID id
+UUID carrierOrgId
+String plate
+VehicleType vehicleType
+Double maxWeightKg
+Double maxVolumeM3
+Double currentWeightKg
+VehicleStatus status
+Double availableCapacity()
}
class Driver {
+UUID id
+UUID carrierOrgId
+String fullName
+String licenseNumber
+UUID currentVehicleId
+DriverStatus status
}
class ShipmentRequest {
+UUID id
+UUID requesterOrgId
+String originAddress
+Double originLat
+Double originLng
+String destinationAddress
+Double destinationLat
+Double destinationLng
+Double weightKg
+CargoType cargoType
+LocalDate requiredDate
+RequestStatus status
+Double suggestedPrice
+LocalDateTime createdAt
+open() void
+match() void
}
class Auction {
+UUID id
+UUID requestId
+LocalDateTime opensAt
+LocalDateTime closesAt
+Integer maxBids
+AuctionStatus status
+close() void
+isExpired() Boolean
}
class Bid {
+UUID id
+UUID auctionId
+UUID carrierOrgId
+UUID vehicleId
+Double amount
+String notes
+BidStatus status
+LocalDateTime submittedAt
+accept() void
+reject() void
+validatePriceFloor(Double minPrice) Boolean
}
class CounterOffer {
+UUID id
+UUID bidId
+UUID offeredByOrgId
+Double amount
+String message
+CounterOfferStatus status
+LocalDateTime createdAt
+accept() void
}
class CarrierTrip {
+UUID id
+UUID carrierOrgId
+UUID vehicleId
+String originAddress
+String destinationAddress
+LocalDateTime departureAt
+Double availableWeightKg
+Double pricePerKg
+TripStatus status
+addBooking(TripBooking) void
+remainingCapacity() Double
}
class Shipment {
+UUID id
+UUID requestId
+UUID bidId
+UUID carrierOrgId
+UUID vehicleId
+UUID driverId
+String trackingCode
+ShipmentStatus status
+LocalDateTime startedAt
+LocalDateTime deliveredAt
+advanceStatus(ShipmentStatus) void
+generateTrackingCode() String
}
class ShipmentStop {
+UUID id
+UUID shipmentId
+Integer sequence
+String location
+Double lat
+Double lng
+LocalDateTime eta
+LocalDateTime actualArrival
+StopStatus status
+UUID requestId
+confirm(String photoUrl, String signatureUrl) void
}
class Message {
+UUID id
+UUID requestId
+UUID carrierOrgId
+UUID senderOrgId
+String body
+Double proposedPrice
+LocalDateTime createdAt
+containsContactInfo() Boolean
}
class Review {
+UUID id
+UUID shipmentId
+UUID reviewerOrgId
+UUID reviewedOrgId
+Integer rating
+Integer punctuality
+String comment
+Boolean deliverySuccess
+Integer delayMinutes
+LocalDateTime createdAt
}
class ReputationScore {
+UUID orgId
+Double deliverySuccessRate
+Double avgDelayMinutes
+Double cargoDamageRate
+Double cancellationRate
+Double avgRating
+Integer compositeScore
+LocalDateTime updatedAt
+recalculate() void
}
class Document {
+UUID id
+UUID shipmentId
+UUID stopId
+DocumentType type
+String fileUrl
+UUID uploadedBy
+LocalDateTime createdAt
}
class Notification {
+UUID id
+UUID orgId
+NotificationType type
+String title
+String body
+UUID relatedId
+Boolean read
+LocalDateTime createdAt
+markAsRead() void
}
class Incident {
+UUID id
+UUID shipmentId
+UUID reportedByOrgId
+IncidentCategory category
+String description
+IncidentStatus status
+LocalDateTime createdAt
+resolve(ShipmentStatus finalStatus) void
}
class CostCalculation {
+UUID id
+UUID requestId
+Double distanceKm
+Double durationMin
+Double tollCost
+Double fuelCost
+Double baseTransportCost
+Double totalEstimate
+LocalDateTime calculatedAt
}
Organization "1" --> "*" Profile : tiene
Organization "1" --> "*" Vehicle : posee
Organization "1" --> "*" Driver : emplea
Organization "1" --> "*" ShipmentRequest : crea
Organization "1" --> "*" CarrierTrip : publica
Organization "1" --> "1" ReputationScore : tiene
ShipmentRequest "1" --> "1" Auction : genera
ShipmentRequest "1" --> "1" CostCalculation : tiene
Auction "1" --> "*" Bid : recibe
Bid "1" --> "*" CounterOffer : genera
Bid "1" --> "0..1" Shipment : origina
Shipment "1" --> "*" ShipmentStop : tiene
Shipment "1" --> "*" Document : contiene
Shipment "1" --> "0..1" Incident : puede tener
Shipment "1" --> "0..2" Review : genera
ShipmentRequest "1" --> "*" Message : tiene hilo
Incident "1" --> "*" Message : tiene hilo
Review "*" --> "1" ReputationScore : actualiza
3. Diagramas de Secuencia¶
3.1 Flujo: PYME publica solicitud y se abre la subasta¶
sequenceDiagram
actor PYME
participant Web as Angular Web
participant API as Quarkus API
participant Pricing as pricing-module
participant ORS as OpenRouteService
participant DB as PostgreSQL
participant Notif as notification-module
participant Carrier as Transportista
PYME->>Web: Completa formulario de solicitud
Web->>API: POST /api/shipments/requests
API->>ORS: GET /directions?origin=Lima&dest=Juliaca
ORS-->>API: {distance_km: 1294, duration_min: 1245, geometry: [...]}
API->>Pricing: calculateSuggestedPrice(distance, weight, cargoType)
Pricing-->>API: {suggestedPrice: 1200, priceFloor: 900}
API->>DB: INSERT shipment_request (status='open', suggested_price=1200)
API->>DB: INSERT auction (closes_at=NOW()+20min, max_bids=5, status='open')
API->>DB: INSERT cost_calculation (distance=1294, fuel=180, tolls=120, total=1200)
API-->>Web: 201 {requestId, suggestedPrice: 1200, auctionId, closesAt}
Web-->>PYME: Muestra solicitud creada con precio referencial y subasta activa
API->>Notif: notifyCompatibleCarriers(requestId, route)
Notif->>DB: Query carriers con zonas compatibles
Notif-->>Carrier: Push/Email: "Nueva carga Lima→Juliaca · 2 ton · S/ 1,200 ref."
3.2 Flujo: Transportista oferta y PYME acepta¶
sequenceDiagram
actor Carrier as Transportista
actor PYME
participant App as Flutter Business
participant API as Quarkus API
participant Auction as auction-module
participant DB as PostgreSQL
participant Notif as notification-module
Carrier->>App: Ve marketplace, selecciona solicitud
App->>API: GET /api/marketplace (filtrado por perfil del carrier)
API-->>App: Lista rankeada de solicitudes abiertas con score de matching
Carrier->>App: Envía oferta: S/ 1,100, vehículo ABC-123
App->>API: POST /api/auctions/{auctionId}/bids
API->>Auction: validateBid(price=1100, vehicleId, requestWeight)
Auction->>DB: Query vehicle capacity vs request weight
Auction-->>API: valid (1100 >= priceFloor 900, capacity OK)
API->>DB: INSERT bid (amount=1100, status='pending')
API->>Notif: notifyPyme(requestId, "Nueva oferta: S/ 1,100")
Notif-->>PYME: Notificación in-app: "Carrier A ofertó S/ 1,100"
PYME->>App: Ve detalle de solicitud con ofertas
App->>API: GET /api/shipments/requests/{id}/bids
API-->>App: [{carrier, amount:1100, trustScore:94, punctuality:97%}, ...]
PYME->>App: Acepta oferta de Carrier A
App->>API: POST /api/bids/{bidId}/accept
API->>DB: UPDATE bid SET status='accepted' WHERE id=bidId
API->>DB: UPDATE bids SET status='rejected' WHERE auctionId=X AND id!=bidId
API->>DB: UPDATE auction SET status='closed'
API->>DB: UPDATE shipment_request SET status='matched'
API->>DB: INSERT shipment (tracking_code='LGX-48291', status='pending')
API->>Notif: notifyCarrier(carrierId, "Tu oferta fue aceptada · LGX-48291")
API-->>App: 200 {shipmentId, trackingCode: 'LGX-48291'}
App-->>PYME: Muestra tracking code y estado del envío
3.3 Flujo: Conductor actualiza estado de parada¶
sequenceDiagram
actor Driver as Conductor
participant DriverApp as Flutter Driver
participant API as Quarkus API
participant DB as PostgreSQL
participant Storage as MinIO/S3
participant Notif as notification-module
actor PYME
Driver->>DriverApp: Abre app, ve ruta asignada
DriverApp->>API: GET /api/driver/shipments/active
API->>DB: Query shipment con stops asignadas al driver
API-->>DriverApp: {shipment, stops: [{seq:1, location, eta, status}, ...]}
Driver->>DriverApp: Llega a parada 1 (Arequipa), marca "Entregado"
DriverApp->>DriverApp: Solicita foto de entrega
Driver->>DriverApp: Toma foto con cámara
Driver->>DriverApp: Captura firma del receptor
DriverApp->>API: POST /api/driver/stops/{stopId}/confirm
Note over DriverApp,API: {photo: base64, signature: base64, status: 'delivered'}
API->>Storage: Upload photo → "shipments/{id}/stop1_evidence.jpg"
Storage-->>API: {fileUrl: "https://..."}
API->>Storage: Upload signature → "shipments/{id}/stop1_signature.png"
Storage-->>API: {fileUrl: "https://..."}
API->>DB: UPDATE shipment_stop SET status='delivered', actual_arrival=NOW()
API->>DB: INSERT document (shipment_id, type='evidence', file_url=...)
API->>Notif: notifyPyme(requestId_parcela, "Tu carga fue entregada en Arequipa")
API-->>DriverApp: 200 OK
Notif-->>PYME: Notificación: "Carga entregada en Arequipa · 14:32"
3.4 Flujo: Negociación bidireccional con contra-oferta¶
sequenceDiagram
actor PYME
actor Carrier as Transportista
participant API as Quarkus API
participant Neg as negotiation-module
participant DB as PostgreSQL
participant WS as WebSocket / Redis Pub-Sub
Carrier->>API: POST /api/messages (requestId, body="Puedo hacerlo por S/ 1,150")
API->>Neg: moderateMessage(body)
Neg-->>API: {ok: true, noContactInfo: true}
API->>DB: INSERT message (sender=carrier, body="Puedo hacerlo por S/ 1,150")
API->>WS: PUBLISH "chat:{requestId}:{carrierId}" → PYME
WS-->>PYME: Mensaje recibido en tiempo real
PYME->>API: POST /api/messages (body="Te pago máximo S/ 1,000", proposed_price=1000)
API->>Neg: moderateMessage(body)
Neg-->>API: ok
API->>DB: INSERT message (sender=pyme, proposed_price=1000)
API->>WS: PUBLISH → Carrier
WS-->>Carrier: Counter-offer de PYME: S/ 1,000
Carrier->>API: POST /api/messages (body="Acepto en S/ 1,080", proposed_price=1080)
API->>DB: INSERT message (sender=carrier, proposed_price=1080)
API->>WS: PUBLISH → PYME
PYME->>API: POST /api/counter-offers/{bidId}/accept (accepted_price=1080)
API->>DB: UPDATE bid SET amount=1080
API->>DB: INSERT message (sender=system, body="Precio acordado: S/ 1,080")
API-->>PYME: 200 OK - Precio actualizado
3.5 Flujo: Smart Load Planner sugiere combinación de cargas¶
sequenceDiagram
participant Scheduler as Quarkus Scheduler
participant Routing as routing-module
participant DB as PostgreSQL
participant Notif as notification-module
actor Carrier as Transportista
Scheduler->>Routing: findCompatibleLoads() [cada 30 min]
Routing->>DB: SELECT open requests WHERE required_date BETWEEN NOW() AND NOW()+48h
DB-->>Routing: [RequestA: Lima→Puno 2t, RequestB: Lima→Juliaca 1t, RequestC: Lima→Puno 1.2t]
Routing->>Routing: clusterByRoute(requests, radius=50km origin, 80km dest)
Routing->>Routing: filterByCapacity(cluster, vehicle.maxWeight=8t)
Routing-->>Routing: Compatible: [A+C] Lima→Puno (3.2t), candidato vehículo XYZ-789
Routing->>DB: INSERT smart_load_suggestion (requests=[A,C], vehicle=XYZ, route, combinedIncome=2800)
Routing->>Notif: notifyCarrier(carrierId, "Smart Load: 2 cargas compatibles Lima→Puno · S/ 2,800")
Notif-->>Carrier: Notificación: "Puedes tomar 2 cargas · 3.2 ton · S/ 2,800 · 80% capacidad"
Carrier->>DB: GET /api/smart-load/suggestions
DB-->>Carrier: [{requests:[A,C], vehicle, route, income:2800, occupancy:80%}]
Carrier->>DB: POST /api/smart-load/{suggestionId}/accept
DB->>DB: INSERT shipment_multi (stops=[Lima, Puno x2], tracking='LGX-48292')
DB->>Notif: notify PYME_A + PYME_C "Tu carga fue incluida en ruta compartida"
4. Diagrama de Estados — Ciclo de Vida del Envío¶
stateDiagram-v2
[*] --> open : PYME publica solicitud
open --> auction_open : Sistema abre subasta
auction_open --> matched : PYME acepta oferta
auction_open --> cancelled : Subasta vence sin ofertas (operador interviene)
matched --> pending : Contrato generado y tracking code asignado
pending --> picked_up : Conductor confirma recogida en punto de origen
picked_up --> in_transit : Primer tramo iniciado
in_transit --> in_transit : Paradas intermedias completadas
in_transit --> delivered : Última parada confirmada con evidencia
in_transit --> issue : Se reporta incidencia
issue --> in_transit : Operador resuelve y reencauza
issue --> delivered : Operador confirma entrega pese a incidencia
issue --> cancelled : Operador cancela por incidencia grave
delivered --> [*]
cancelled --> [*]
5. Diagrama de Componentes — Aplicación Angular¶
graph TD
subgraph Angular Web App
CoreModule[Core Module\n- AuthGuard\n- RoleGuard\n- AuthInterceptor\n- AuthService]
SharedModule[Shared Module\n- Components UI\n- Pipes\n- Directives]
subgraph Features
AuthFeature[auth/\n- login\n- register\n- onboarding]
MarketplaceFeature[marketplace/\n- listado cargas\n- filtros\n- oferta inline]
ShipmentsFeature[shipments/\n- mis solicitudes\n- detalle\n- nueva solicitud\n- tracking]
NegotiationFeature[negotiation/\n- chat RT\n- contra-oferta]
FleetFeature[fleet/\n- vehículos\n- conductores]
ReputationFeature[reputation/\n- perfil score\n- historial reviews]
OperatorFeature[operator/\n- dashboard\n- asignación\n- incidencias]
ReturnsFeature[returns/\n- cargas retorno\n- oferta directa]
end
end
CoreModule --> AuthFeature
CoreModule --> MarketplaceFeature
CoreModule --> ShipmentsFeature
SharedModule --> MarketplaceFeature
SharedModule --> ShipmentsFeature
SharedModule --> OperatorFeature
LOGYX · E1 Modelado Técnico UML · Competencia CE0214 · Versión 1.0 · Junio 2026