Behind the Build: How We Built Tugvan's Fleet Platform in 9 Weeks
Every project has a moment where the stated requirement and the actual need diverge. On the Tugvan fleet platform build, that moment came at the end of day one of our discovery embedding.
The stated requirement was: "We need a mobile app for drivers and a web dashboard for dispatchers." The actual need, two hours into watching the dispatch team work: dispatchers needed to stop asking drivers where they were. That's it. Everything else was feature decoration on top of that core problem.
Week 1: Discovery and architecture
We spent two full days at the dispatch center before writing a line of code. This is not a rule we apply to every project — it's a rule we apply to any project where the complexity is operational rather than technical.
What we learned: the average dispatcher sent 12–15 WhatsApp messages per vehicle, per shift. 80% of those messages were "where are you?" or "when will you be there?" The information existed somewhere — in the driver's head, in their estimated departure time — but there was no system to surface it.
The architectural decision that followed from this: the system's primary output isn't a map. It's a status board. The map is how you understand the status board, not the other way around.
This reframing changed everything. A status board optimizes for scannability. A map optimizes for geographic context. We needed both, but the status board came first.
Technology decisions:
- React Native for the driver app — the client had no iOS/Android preference, and React Native let us ship both simultaneously
- Mapbox over Google Maps — offline tile support was non-negotiable for drivers in rural areas
- WebSockets for real-time location updates — polling would have introduced latency that made the status board feel stale
- Node.js + PostgreSQL — standard choice for this data model; nothing exotic needed
Week 2–3: Core architecture
The hardest part of real-time location systems isn't displaying the location — it's deciding how often to update it, what to store, and how to calculate ETAs from raw GPS data.
We settled on a 30-second location ping interval for active trips, dropping to 5-minute intervals when a vehicle was stationary for more than 10 minutes. This reduced database write volume by 60% while keeping the status board accurate enough to be useful.
GPS coordinates alone don't give you ETAs. We integrated with the Mapbox Directions API to calculate route-aware ETAs at each ping — not straight-line estimates. This added latency (average 180ms per ping) but made the ETA numbers trustworthy, which was the whole point.
Week 4–6: Driver app and offline sync
The driver app had one requirement that dictated most of the architecture: it had to work in low or no connectivity. Drivers delivering to industrial sites, rural areas, and underground facilities have unreliable signal.
We chose an offline-first approach: job details, customer information, and route data are synced to the device at job assignment. The driver can navigate, complete the job, and collect the signature without any network connection. The data syncs when connectivity returns.
The tricky part: handling conflicts. If a job was reassigned while the driver was offline, the app needed to resolve the conflict gracefully when it reconnected. We built a last-write-wins system with a server-side timestamp as the authority, with a notification prompt to the driver if their job state changed significantly while offline.
Week 7–8: Dispatcher dashboard
The dispatcher dashboard is a React web application — not React Native — because it runs on a desktop browser at the dispatch center. We shared the data models and API client code between the web and native apps using a shared utilities package.
The dashboard's primary view is the status board: 40–60 active vehicles, their current status (in transit / at destination / delayed / offline), current ETA, and driver contact. Each row is clickable, expanding to show the full trip detail and map view.
We ran two rounds of testing with actual dispatchers — not UAT sessions, but sitting with them while they ran a live shift with the prototype. Both sessions produced significant UI changes that desk-based testing would never have caught.
Week 9: Integration, testing, and handoff
The final week was ERP integration, end-to-end QA, and deployment.
The ERP integration was the only part of the project that ran over — not because of complexity we hadn't anticipated, but because the ERP's API documentation was three years out of date and the actual API behavior differed in 6 significant ways. We added two days to the schedule to handle this. The client was notified the moment we confirmed the discrepancy — not at delivery.
Testing covered: device matrix (8 real iOS and Android devices across different OS versions), offline/online transition testing, signal degradation simulation, and a full-day live pilot with 12 volunteer drivers.
We handed over: full source code, architecture documentation, deployment runbook, database schema with comments, and a 2-hour recorded walkthrough of every component.
What we'd do differently
The ERP integration timeline assumption. We quoted it at 3 days based on the documentation. The actual API made that impossible. In hindsight: integrations with legacy enterprise systems should default to 2× the estimate until proven otherwise.
The offline sync conflict resolution was more complex than we initially designed for. If we were to do this again, we'd build the conflict resolution system first (week 1, not week 5) because it has architectural implications that ripple upward through the data model.
The result
9 weeks from kickoff to production deployment. 65% reduction in dispatchers' manual communication workload in the first month. 4.8 stars on the App Store. Two legacy systems retired.
The dispatcher team's feedback at the 30-day check-in: "We only call drivers now when something's actually wrong."
That's the win.
