1
Overview
CHAROS Live
Primary Tenant — Trucking Company
U1ST Logistics Live
Second Tenant — Freight Forwarder
Stack
Full-Stack TypeScript
Infrastructure
Docker + Traefik HTTPS
Source
C:\Users\chave\projects\handymanny\VPS Path
/docker/handymanny/ (CHAROS) · /docker/u1st-handymanny/ (U1ST)Adminchavez684@gmail.com (password in
.env)Auth Rolessuper_admin, admin, dispatcher, customer_admin, customer_viewer
2
Features
28 Built
| Feature | Details | Status |
|---|---|---|
| Multi-Tenant White-Label | brand.ts config, CSS custom properties, parameterized Docker/seed/deploy | Live |
| U1ST Logistics Instance | u1st.handymanny.cloud, blue branding, independent DB, port 3004 | Live |
| CHAROS AI Chatbot | Claude Haiku 4.5, 5 tools, image upload, rate-limited 20/min | Live |
| Public Chat API (Manny) | Public endpoint for handymanny.cloud, no auth, CORS, IP rate limit 10/min, Manny persona | Live |
| Deploy Scripts | ./deploy.sh charos|u1st, deploy-all.sh | Live |
| Authentication | NextAuth v5, 5 roles: super_admin, admin, dispatcher, customer_admin, customer_viewer | Live |
| Dashboard | KPI cards, recent activity feed, role-based views | Live |
| Shipments Map | Unified truck+ocean, Leaflet/OSM, 3-tier geocoding, type-aware popups | Live |
| Shipment Status | Fleet overview, status categories, position data | Live |
| Shipments CRUD | Create, list, detail pages with container editing | Live |
| Shipment Edit | Full edit form at /shipments/[id]/edit, type tabs, containers CSV | Live |
| Customer List + Filter | Server-side company query, CustomerFilter dropdown on /shipments | Live |
| Customer Detail | Contacts, invite buttons, notification prefs, shipments table, notification log | Live |
| Contact CRUD | Inline create/edit/delete on customer detail, role-based | Live |
| Public Tracking | Token-based /track/[code], no login required | Live |
| Position Tracking | 5 DB fields (lat/lng/city/state/timestamp), auto-extract on track | Live |
| Auto-Track (n8n) | Hourly 8am-7pm CST Mon-Sat, workflow XEYwLqrwEJwCz39g | Live |
| Archive Panel | Slide-in for delivered shipments >2 days old, auto-excluded from list | Live |
| Notification Prefs | Channel (email/whatsapp/both), frequency, quiet hours | Live |
| Settings > Users | Full CRUD, company dropdown, super_admin only with self-protection | Live |
| Settings > Notifications | Admin: templates + global log; customer_admin: own prefs + log | Live |
| Platform Invitations | nanoid(32) token, 7-day expiry, public /invite/[token] registration | Live |
| WhatsApp Client | Meta Cloud API: sendWhatsAppTemplate + sendWhatsAppText | Live |
| Email Client (Resend) | Verified domain handymanny.cloud, DKIM/SPF/DMARC, noreply@handymanny.cloud | Live |
| Notify Endpoint | /api/shipments/[id]/notify — WhatsApp + email, respects quiet hours | Live |
| Inbox | Internal message system with parsed data | Live |
| Webhook Receivers | WhatsApp + n8n webhook endpoints | Live |
| Docker Deploy | Traefik, PostgreSQL, healthcheck, fix-pw.js baked in | Live |
| GitHub Repo | Private, security-hardened: no creds, IPs, or deploy scripts committed | Live |
| Feature | Notes | Status |
|---|---|---|
| n8n-client | Direct n8n API integration from portal | Stub |
| Track Now UI | Live carrier query button in shipment detail (API works, UI stub) | Stub |
3
Multi-Tenant Architecture
2 Tenants
Single codebase, branding via env vars (
NEXT_PUBLIC_BRAND_*) + CSS custom properties (--brand, --brand-light, --brand-dark).
brand.ts→
NEXT_PUBLIC_* env→
CSS vars on <html>→
Themed UI
| Tenant | URL | VPS Path | Port | Color | Status |
|---|---|---|---|---|---|
| CHAROS | charos.handymanny.cloud | /docker/handymanny/ | 3002 | #d93324 | Live |
| U1ST Logistics | u1st.handymanny.cloud | /docker/u1st-handymanny/ | 3004 | #1e40af | Live |
Key Implementation Details
Brand config
src/lib/brand.ts — reads NEXT_PUBLIC_BRAND_* with CHAROS defaultsCSS injectionInline style on
<html> in src/app/layout.tsxBuild-timeNEXT_PUBLIC_* inlined at
npm run build via Docker ARG + ENVDockerSeparate containers per tenant (independent PostgreSQL, independent app)
SeedParameterized with
SEED_COMPANY_* env vars (CHAROS defaults)Deploy
./deploy.sh charos or ./deploy.sh u1st — sets VPS_PATH, CONTAINER, COMPOSE_CMD
Important: NEXT_PUBLIC_* are build-time only. Changing brand env vars requires Docker rebuild, not just restart. Values with spaces MUST be quoted in
.env.
4
CHAROS AI Chatbot
Live
ModelClaude Haiku 4.5 (
claude-haiku-4-5-20251001)API route
POST /api/chat — tool loop, rate limit 20/min, image supportWidget
src/components/chat/chat-widget.tsx — floating red button, 400x500 panelTools
src/lib/chat-tools.ts — 5 tools with Prisma executor, company-scopedScopeCustomers: read-only · Admin/dispatcher: write access
5 AI Tools
| Tool | Purpose | Access |
|---|---|---|
| list_shipments | List all shipments with filters (type, status, carrier) | All roles |
| get_shipment | Get detailed shipment info by ID or MBL | All roles |
| search_shipments | Full-text search across shipments and containers | All roles |
| create_shipment | Create new shipment with containers | Admin, Dispatcher |
| update_shipment | Update shipment fields and status | Admin, Dispatcher |
Features
Image Upload
Drag-and-drop document images for AI analysis (e.g., arrival notices, tracking screenshots)
Session Persistence
Chat history saved in sessionStorage, survives page navigation
Multi-Tenant
Deployed to both CHAROS + U1ST with separate Anthropic API keys
5
Shipments Map & Position Tracking
11/14 Mapped
Unified Shipments Map
3-Tier Geocoding Pipeline
Position Tracking
DB fields
latitude, longitude, positionCity, positionState, positionAtGeocoding
src/lib/geocoding.ts — 80+ hardcoded locations + state abbreviation map + Nominatim fallbackPosition extractor
src/lib/position-extractor.ts — extracts from truck (JSON) and ocean (events) dataAuto-trackn8n cron
0 14-23,0-1 * * 1-6 (8am-7pm CST Mon-Sat), Bearer token authAI Geocodingn8n workflow
J76GltqEdd731XGY — Claude Haiku, MX/US specializationMap Components
| File | Purpose |
|---|---|
| fleet-map.tsx | Map page shell — type filters, status filters, legend, dynamic import |
| fleet-map-inner.tsx | Leaflet map component (client-only, no SSR, type-aware popups) |
| map-marker-icon.ts | Color-coded DivIcon markers (truck=circle, ocean=rounded square) |
| mock-fleet-data.ts | Exports MapTruck interface with type/carrier/mbl/vessel |
6
API Routes
31 Endpoints
| Method | Route | Purpose | Auth |
|---|---|---|---|
| GET | /api/shipments | List shipments (filters: type, status, carrier, companyId, archived) | All roles |
| POST | /api/shipments | Create shipment with containers | Admin+ |
| GET | /api/shipments/[id] | Shipment detail with containers & company links | All roles |
| PATCH | /api/shipments/[id] | Update shipment fields, containers, company link | Admin+ |
| DELETE | /api/shipments/[id] | Delete shipment | super_admin, admin |
| POST | /api/shipments/[id]/track | Manual trigger tracking (ocean or truck) | Admin+ |
| POST | /api/shipments/[id]/notify | Send WhatsApp + email to contacts | Admin+ |
| POST | /api/shipments/auto-track | Batch auto-track (Bearer token auth) | API Key |
| Method | Route | Purpose | Auth |
|---|---|---|---|
| GET | /api/customers | List customers (paginated, searchable) | Admin+ |
| POST | /api/customers | Create customer | Admin+ |
| GET | /api/customers/[id] | Customer detail with contacts & shipments | All roles |
| PATCH | /api/customers/[id] | Update customer | Admin+ |
| DELETE | /api/customers/[id] | Soft delete (sets active=false) | Admin+ |
| GET | /api/customers/[id]/portal-summary | Dashboard stats (totals, by category/type, recent) | All roles |
| POST | /api/contacts | Create contact for company | Admin+ |
| PATCH/DEL | /api/contacts/[id] | Update or delete contact | Admin+ |
| Method | Route | Purpose | Auth |
|---|---|---|---|
| GET/POST | /api/auth/[...nextauth] | NextAuth handlers | Public |
| GET | /api/users | List users (filterable by role) | super_admin, admin |
| POST | /api/users | Create user | super_admin, admin |
| PATCH | /api/users/[id] | Update user (name, role, company, active, password) | super_admin, admin |
| DELETE | /api/users/[id] | Delete user (self-protection) | super_admin |
| POST | /api/invitations | Create platform invitation | Admin+ |
| GET/DEL | /api/invitations/[id] | List or revoke invitations | Admin+ |
| Method | Route | Purpose | Auth |
|---|---|---|---|
| GET | /api/track/[code] | Public tracking (sanitized shipment + containers) | Public |
| GET | /api/messages | List messages (pagination, processed filter) | Admin+ |
| GET/PUT | /api/notifications | List logs / Update preferences (upsert) | Varies |
| GET | /api/notifications/preferences | Get notification prefs for company | All roles |
| GET/POST | /api/webhooks/whatsapp | WhatsApp verification + incoming messages | Webhook |
| POST | /api/webhooks/n8n | n8n tracking update webhook | Webhook |
| POST | /api/chat | AI chatbot with 5 tools (rate limited 20/min) | All roles |
| POST | /api/chat/public | Public Manny chatbot (no tools, CORS, 10/min IP limit) | Public |
7
Database Schema
10 Models · 8 Enums
User
id, email, passwordHash, name, role (UserRole), companyId?, active, createdAt, updatedAt
Company
id, name, taxId?, address?, phone?, email?, active, createdAt, updatedAt → users[], contacts[], companyShipments[], notificationPreference?, notificationLogs[], invitations[]
Contact
id, companyId, name, email?, phone?, whatsapp?, roleTitle?, isPrimary, createdAt, updatedAt
Shipment
id, type (ShipmentType), mbl?, ssl?, vessel?, voyage?, pol?, pod?, truckerName?, unitNumber?, trackingUrl?, cuentaEspejoUrl?, platform?, eta?, status?, statusCategory, trackingCode (unique), trackingData (JSON), lastTrackedAt?, latitude?, longitude?, positionCity?, positionState?, positionAt?, createdAt, updatedAt
ShipmentContainer
id, shipmentId, containerNumber, size?, type?, seal?, createdAt
CompanyShipment
id, companyId, shipmentId, role (CompanyShipmentRole), createdAt — @@unique([companyId, shipmentId])
NotificationPreference
id, companyId (unique), channel (NotificationChannel), frequency (NotificationFrequency), quietHoursStart?, quietHoursEnd?, whatsappGroupId?, createdAt, updatedAt
NotificationLog
id, companyId, shipmentId?, channel, template?, status (NotificationStatus), sentAt, createdAt
Message
id, source (MessageSource), sender, subject?, rawContent, parsedData (JSON), shipmentId?, processed, createdAt, updatedAt
PlatformInvitation
id, companyId, contactId?, email, name, role (UserRole), token (unique), status (InvitationStatus), expiresAt, acceptedAt?, invitedBy, createdAt
Enums (8)
UserRole
super_admin, admin, dispatcher, customer_admin, customer_viewer
ShipmentType
ocean, truck
StatusCategory
on_time, delayed, stopped, delivered, pending, expired, not_found
NotificationChannel
whatsapp, email, both
NotificationFrequency
realtime, milestone, daily, exception_only
NotificationStatus
sent, delivered, failed, read
CompanyShipmentRole
consignee, shipper, notify_party
InvitationStatus
pending, accepted, expired, revoked
8
Connected n8n Workflows
Auto-Track Hourly
XEYwLqrwEJwCz39gGeocode AI
J76GltqEdd731XGYOcean Orchestrator
AzACxa8tKeisIYssTruck Tracker
2GF72l5GeWcUYohmShipment Notice Emailer
dH1nNvwPxR3eyGU8Batch Tracker
fCVbPtxhdMcroH4PEmail Report
ry7zDIJTBXzV1dez9
Integrations
Resend (Email) Active
Verified domain
handymanny.cloud. DKIM/SPF/DMARC. From: noreply@handymanny.cloud. Templates: tracking update + invitation.WhatsApp (Meta Cloud) Active
sendWhatsAppTemplate + sendWhatsAppText. Quiet hours respected (America/Mexico_City). Needs env vars configured per tenant.
Anthropic API Active
Claude Haiku 4.5 for CHAROS AI chatbot. Separate API keys per tenant. Rate limited 20 req/min.
Ocean Tracker Active
11 carriers via n8n orchestrator + headless browser. 6 verified (ONE, MSC, Yang Ming, Evergreen, HMM, COSCO). 3 blocked.
NextAuth v5 Active
Credentials provider, 5 roles, middleware route protection. Session via HttpOnly cookies.
Evolution API QR Needed
WhatsApp API v2.3.7 on srv1139900 port 8080. Instances need QR re-scan after migration.
10
Planned Modules
Planned
Comercializadora Planned
Mexican Customs 19-Stage Workflow
Certificado de Molino Planned
EN 10204 Steel Mill Certificates
11
Key Files Reference
| File | Purpose |
|---|---|
| src/lib/brand.ts | Brand config (reads NEXT_PUBLIC_BRAND_* env vars, CHAROS defaults) |
| prisma/schema.prisma | 10 models, 8 enums — core data layer |
| prisma/seed.ts | Parameterized with SEED_COMPANY_* env vars |
| src/middleware.ts | Route protection, role-based access control |
| src/components/layout/sidebar.tsx | Nav items, brand.* imports for name/logo/footer |
| src/lib/chat-tools.ts | CHAROS AI tool definitions (5 tools) + Prisma executor |
| src/app/api/chat/route.ts | Chat API — Claude Haiku, tool loop, rate limit, image |
| src/app/api/chat/public/route.ts | Public chat API — Manny persona, no auth, CORS, IP rate limit |
| src/components/chat/chat-widget.tsx | Floating chat widget (red button, 400x500 panel, drag-and-drop) |
| src/lib/geocoding.ts | 80+ hardcoded ports/cities, state abbrev map, Nominatim fallback, AI batch |
| src/lib/position-extractor.ts | Extracts position from truck (JSON) and ocean (events) data |
| src/lib/email-templates.ts | Uses brand.color directly (CSS vars don't work in emails) |
| src/lib/email-client.ts | Dynamic from using brand.name + brand.tagline |
| src/components/fleet/fleet-map.tsx | Map page shell — type filters, status filters, legend |
| src/components/fleet/fleet-map-inner.tsx | Leaflet map (client-only, no SSR, type-aware popups) |
| src/components/shipments/archive-panel.tsx | Slide-in archive for delivered shipments >2 days |
| fix-pw.js | Password fix script (admin roles only), runs after rebuild |
| deploy.sh | Multi-tenant VPS deploy (./deploy.sh charos|u1st) |
| deploy-all.sh | Deploys both tenants sequentially |
12
Roadmap
Phase 1 — Core Portal Done
✓
Auth (5 roles), Dashboard, Shipments CRUD✓
Customer list + detail + contacts CRUD✓
Public tracking, webhooks, inbox✓
Docker deploy + Traefik HTTPS✓
Position tracking + auto-track hourly workflow✓
Unified Shipments Map (truck + ocean)Phase 2 — Multi-Tenant & Comms Done
✓
Multi-tenant white-label architecture (brand.ts)✓
U1ST Logistics tenant deployment✓
CHAROS AI chatbot (Claude Haiku, 5 tools, image upload)✓
WhatsApp + Email client (Resend verified)✓
Platform invitations + Settings✓
Archive panel, customer filter, shipment editPhase 3 — Domain Modules Next
Comercializadora module (19-stage Mexican customs)
Certificado de Molino (EN 10204)
Track Now live carrier query UI
n8n-client direct integration
WhatsApp notifications via Evolution API
Phase 4 — Mobile & Scale Future
Flutter mobile app (customer tracking companion)
Push notifications
Offline mode
Additional tenants onboarding
13
Known Issues & Fixes
| Issue | Impact | Mitigation | Status |
|---|---|---|---|
| Password hash corruption on Docker rebuild | Login fails after deploy | fix-pw.js baked into Docker image, runs in deploy.sh | Fixed |
| NEXT_PUBLIC_* build-time | Brand changes need full rebuild | Docker ARG + ENV in builder stage | By Design |
| .env quoting | Values with spaces break bash source | Always quote: SEED_COMPANY_NAME="U1ST Logistics" | Documented |
| 3 ocean carriers blocked | OOCL/Wan Hai/ZIM unusable | API aggregator ($350-720/mo) or scraping specialist | Open |
| Seed trucks expired | 4 seed trucks show as expired | status_category=expired, auto-track skips them | Accepted |
| Batch Tracker inactive | No bulk truck tracking | Needs activation + SMTP for email report | Pending |
| Shipment Notice MSC bug | MSC items dropped in mixed batches | Added Merge node (append, 3 inputs) before Normalize | Fixed |
14
Decision Log
| Date | Decision | Rationale | Alternatives |
|---|---|---|---|
| 2026-02 | Next.js 16 + Prisma 6 | Latest stable, TypeScript-first, excellent DX | Express+Sequelize, Remix |
| 2026-02 | Multi-tenant via env vars | Single codebase, separate Docker containers per tenant | Shared DB with org_id, microservices |
| 2026-02 | Resend for transactional email | Better deliverability, verified domain (DKIM/SPF) | Gmail SMTP, SendGrid |
| 2026-02 | Meta Cloud API for WhatsApp | Direct API, no middleware dependency | Evolution API only |
| 2026-02 | Claude Haiku for chatbot | Low cost, fast, good enough for shipment queries | GPT-4o-mini, local LLM |
| 2026-02 | Leaflet/OSM for maps | Free, open-source, no API key required | Google Maps ($), Mapbox |
| 2026-02 | Hourly auto-track via n8n cron | Passive position updates, no manual tracking needed | Webhook-triggered only |
| 2026-02 | 3-tier geocoding | Hardcoded for speed, Nominatim fallback, AI for edge cases | Google Geocoding API ($), single source |
| 2026-02 | Decouple MC from Next.js | Static HTML = simpler, faster, independent deploy | Keep as Next.js route |