ECARRGO
A logistics reverse-auction marketplace with a controlled shipment state machine.

A two-role mobile app that turns shipping orders into a rate auction: senders post an order, logistics vendors bid, and the system locks the winner, payment, and tracking status into one deterministic flow.
Orders were simple on paper.
Fulfillment was fragmented.
Shipping used to run through intermediaries: senders contacted several vendors separately and negotiated prices with no structured record. That pattern breaks at volume because prices cannot be compared, one order risks being held by more than one vendor, and shipment status has no single authority. When the vendor, sender, and admin see different states, an order stalls with no clear owner. Without an explicit state machine and a single data source, every status change can produce two contradicting truths.
Shipping rates were negotiated manually over chat and phone, so prices went unrecorded, could not be compared, and orders were at risk of being placed with more than one vendor.
With no single source, shipment_id, auction_id, and waybill number were easily swapped between the fill-in, payment, and tracking screens, breaking the integrity chain of the shipment data.
Transfer proofs were received without a record tying them to a transaction, so payment verification depended on manual matching by an admin and could not be audited.
One order. Multiple fulfillment paths.
Explore how the system splits each line item into independent operational branches while enforcing strict transactional convergence before dispatch.
Order Creation & Auction
Runs when a sender creates a new shipment and the system must produce one shipment with one linked auction.
Select Route & Schedule
The app captures pickup and destination points via map and geolocation, then stores the schedule in FillDataProvider.
Enter Cargo Details
Per-item validation rejects weight over 100 tons, or empty volume or value, before the data is provisionally locked.
Set Auction Parameters
Fetches a rate reference from the generate-rates endpoint and ensures the auction duration does not exceed the shipping time.
Submit Order
POST /auctions creates the shipment and auction together, returning auction_id, shipment_id, and resi_number that are shared to the providers.
SERVER STATUS AS A STATE MACHINE, NOT A CLIENT CACHE
UI steps are recomputed from the shipment status on the server, and local progress cache is reset when it is not backed by a confirmation.
If the client decides its own steps, an order can stall on a stale step after the server status changed. Mapping status to steps and validating the cache keeps navigation consistent with backend authority even when the app is closed mid-flow.
ONE ACCUMULATOR PROVIDER FOR THE MULTI-STEP WIZARD
All five steps of input are stored in a single FillDataProvider with per-step validation before the payload is sent.
Sending data per step invites partial orders and orphan shipment_ids. A single accumulation ensures the auction is created only when every field is valid, at the cost of holding wizard state longer on the client.
REVERSE AUCTION WITH SERVER-SIDE WINNER LOCKING
Winner selection happens through the sender's select-winner endpoint, not free negotiation on the client.
A reverse auction lowers prices naturally, but without locking, one order can be held by many vendors. A dedicated endpoint ensures only the order owner sets the winner and prevents double assignment.
SECURE TOKENS AND AUTH-BOUND DEVICE SYNC
The access token is stored in secure storage with centralized Bearer injection, and the FCM token is registered following the login cycle.
Storing the token in plain storage or spreading headers across every call increases leak risk and inconsistency. Centralized injection simplifies authorization, while post-login device-token registration keeps notifications reaching the right user.
Engineered for relational integrity.
The Flutter client separates data sources, domain, and presentation; Dio handles transport with an automatically injected Bearer token. A single FillDataProvider accumulates all wizard input into one object before it is sent as one auction payload, preventing half-sent orders. On the server, shipment status is the source of truth; the select-winner, payments, and tracking endpoints enforce per-owner authorization. The status transition history (status_history) becomes an audit trail that is rendered back to the user timeline.
CLIENT LAYER
- Flutter (Dart) multi-platform
- Provider & Riverpod for state
- flutter_map + geolocator + latlong2
- flutter_secure_storage & shared_preferences
BUSINESS & DOMAIN
- Per-feature data-domain-presentation architecture
- Data source layer + Dio HTTP client
- Shipment status to flow-step mapping
- Role-based navigation (customer/vendor) + l10n en/id
DATA & STORAGE
- REST API with Bearer authentication
- Secure storage for the access token
- Local progress store as cache
- Object storage for transfer proofs + status_history as audit trail
ECARRGO turns shipping that was negotiated manually into a structured auction flow with a single status authority on the server. Every order now has a linked shipment and auction, a selected winner that cannot be duplicated, and payments that can be verified through a traceable transfer proof. Mapping status to flow steps makes the process deterministic and resumable across sessions, while the status audit trail gives operations a clear basis and room to grow shipping volume.
More systems worth exploring.
Explore another project where software, business processes, and practical engineering meet.