Logistics & shipping management system built with Next.js 15 + Firebase
Enables end‑to‑end management of packages, boxes, and shipments in the Miami warehouse, with access for Admin/Staff, Clients, and Partners (multi‑client view).
- Full admin + client portal (Next.js App Router)
- New Partner area (
/partner/*) with multi-client visibility (trackings/boxes/shipments/clients) scoped to assigned clients. - Role-based security (Firestore Rules tested with Emulator)
- Vitest suite with integration, unit & rule tests
- 6×4 label generation (jsPDF) + dual weight handling (lb/kg)
- Mobile-first, accessible, bilingual-ready
- Next.js 15 (App Router, TypeScript, TailwindCSS)
- Firebase
- Authentication (Email/Password)
- Firestore Database
- Storage (package and document images)
- React Hook Form + Zod for forms
- ZXing for scanning tracking barcodes
- Next.js (App Router) as frontend + server (routes
/admin/*, client portal under/mi/*, and partner area under/partner/*). - Firebase Auth manages the session (email/password).
- Firestore stores entities (
users,clients,inboundPackages,boxes,shipments,trackingAlerts). - Storage stores photos (packages/documents), accessed via URL.
- jsPDF (CDN) generates 6×4 PDFs for labels.
- Tailwind defines color tokens and utility components.
Received → Consolidated (box) → Shipped → In transit → At destination.
- Admin/Staff: enters packages, builds boxes, creates shipments, and changes statuses.
- Partner: manages their assigned clients (create/edit/activate/deactivate) and sees trackings/boxes/shipments for all assigned clients.
- Client: sees their own trackings/boxes/shipments and edits their data.
Official palette:
- Primary green:
#005f40 - Secondary orange:
#eb6619 - Dark orange (shadow):
#cf6934 - White for contrast and backgrounds.
Official logo available in /public. Use green as primary and orange for CTAs.
src/
app/
admin/
ingreso/
preparado/
estado-envios/
historial-tracking/
clientes/
usuarios/
mi/
layout.tsx
page.tsx (redirects to /mi/historial)
historial/
page.tsx
cajas/
page.tsx
envios/
page.tsx
cuenta/
page.tsx
partner/
layout.tsx
page.tsx
historial/
page.tsx
cajas/
page.tsx
envios/
page.tsx
clientes/
page.tsx
[id]/
page.tsx
acceder/
registro/
components/
RequireAuth.tsx
AdminNav.tsx
PartnerNav.tsx
ConditionalNav.tsx
PartnerContext.tsx
clients/
ClientsManager.tsx
ClientProfile.tsx
boxes/
BoxDetailModal.tsx
useBoxDetailModal.ts
ui/
StatusBadge.tsx
BrandSelect.tsx
icons.tsx
lib/
firebase.ts
printBoxLabel.ts
weight.ts
utils.ts (chunk helper)
- Package intake: tracking (hardware scanner or manual), client selection, weight lb↔kg with automatic conversion, photo (camera or file) with compression; same‑day listing.
- Load preparation: search by client, build boxes (1 box = 1 client), CSV export; table with sticky header, zebra, accessible focus; dual weight
X lb / Y kg. - Shipments: create shipment (saves
clientIds), change status (Open → In transit → At destination → Closed), expand boxes, print 6×4 label. - Tracking history: filters; BOX: # modal with Type + Apply, Reference + Print label; items with dual weight and total weight.
- Clients: CRUD with 20‑column layout: Code (read‑only), Name, DocType/DocNumber, Country/State/City, Address/Postal code, Phone/Email/Extra email.
- Historial (multi-client): received trackings for all assigned clients (read-only).
- Cajas (multi-client): boxes for all assigned clients + detail modal.
- Envíos (multi-client): shipments derived from assigned clients’ boxes.
- Clientes: uses the same management UI as admin but scoped and with restricted actions.
- Can create/edit/activate/deactivate clients.
- Cannot delete clients.
- Cannot reset password or change managerUid.
- Navigation keeps Partner navbar across sections.
- History: their trackings (date, tracking, carrier, weight
lb/kg, status, photo). - Boxes: their boxes and detail (items with dual weight).
- Shipments: their shipments (visible if their
clientId∈shipment.clientIds). - Account: edit Name, Phone, Country/State/City, Address, Postal code, Extra email, DocType/DocNumber. Code and Email are read‑only.
- Report tracking: creates a document in
trackingAlertsfor admin to handle. - Account linking:
/mirequiresusers/{uid}.clientIdto be present. If the user is not linked yet, the portal shows a "not linked" message and blocks access until the account is linked by staff. - Bulk bootstrap (migration): legacy clients imported into Firestore can be linked to Firebase Auth using the superadmin tools (see Data maintenance below).
Internally, the client portal is split into nested routes: /mi/historial, /mi/cajas, /mi/envios, and /mi/cuenta, all sharing a common layout that handles authentication, header, and tabs.
- 6×4 PDF generated with jsPDF (CDN) in
src/lib/printBoxLabel.ts. - Layout: #REFERENCE at top (large auto‑fit text), two columns below #CLIENT and #BOX. No weight.
- RequireAuth with
requireAdminprotects all/admin/*routes. - Navigation:
AdminNav(admin/staff),PartnerNav(partner), and aConditionalNavwrapper at the root layout to ensure partners never see/admin/*links. - Firestore rules (effective summary):
users: self or staff.clients: client reads/updates basic fields of their own client; staff full.code/emailread‑only for client.inboundPackages/boxes: client only those with theirclientId.shipments: readable ifclientId∈shipment.clientIds.trackingAlerts: client create, staff read/manage.
- Post-login routing is role-based: partner_admin → /partner, client → /mi, staff → /admin/ingreso (with Firestore role reconciliation to handle stale claims).
- Partner scoping: data is filtered to the partner’s assigned clients using
users/{uid}.managedClientIdsand/orclients.managerUid == uid(fallback where needed).
Firestore rules (suggested)
rules_version = '2';
service cloud.firestore {
match /databases/{db}/documents {
function hasAuth() { return request.auth != null; }
function userDoc() { return hasAuth() ? get(/databases/$(db)/documents/users/$(request.auth.uid)) : null; }
function role() { return hasAuth() ? (userDoc().data.role != null ? userDoc().data.role : (request.auth.token.role != null ? request.auth.token.role : null)) : null; }
function clientId() { return hasAuth() ? (userDoc().data.clientId != null ? userDoc().data.clientId : (request.auth.token.clientId != null ? request.auth.token.clientId : null)) : null; }
function isSuperAdmin() { return role() == 'superadmin' || request.auth.token.superadmin == true; }
function isAdmin() { return role() == 'admin' || request.auth.token.admin == true; }
function isStaff() { return isSuperAdmin() || isAdmin(); }
function isOwner(cid) { return clientId() != null && clientId() == cid; }
match /users/{uid} {
allow read: if isStaff() || (hasAuth() && (uid == request.auth.uid || resource.data.uid == request.auth.uid));
allow create: if hasAuth() && (uid == request.auth.uid || request.resource.data.uid == request.auth.uid);
allow update: if isStaff() || (hasAuth() && (uid == request.auth.uid || resource.data.uid == request.auth.uid));
allow delete: if isSuperAdmin();
}
match /clients/{id} {
allow read: if isStaff() || isOwner(id) || (hasAuth() && resource.data.email == request.auth.token.email);
allow update: if isStaff() || ( isOwner(id) && resource.data.diff(request.resource.data).changedKeys().hasOnly(['name','phone','country','state','city','address','emailAlt','postalCode','docType','docNumber']) );
allow create, delete: if isStaff();
}
match /inboundPackages/{inbId} {
allow read: if isStaff() || isOwner(resource.data.clientId);
allow create, update, delete: if isStaff();
}
match /boxes/{boxId} {
allow read: if isStaff() || isOwner(resource.data.clientId);
allow create, update, delete: if isStaff();
}
match /shipments/{id} {
allow read: if isStaff() || (clientId() != null && clientId() in resource.data.clientIds);
allow write: if isStaff();
}
match /trackingAlerts/{id} {
allow create: if hasAuth() && request.resource.data.uid == request.auth.uid;
allow read, update, delete: if isStaff();
}
match /{document=**} { allow read, write: if false; }
}
}
LEM‑BOX V2 includes a complete automated testing suite to ensure functional accuracy, data integrity, and rule enforcement across the system.
- Vitest for unit, integration, and UI component tests.
- Firebase Emulator Suite for Firestore Rules validation.
- Playwright for end‑to‑end (E2E) browser automation.
- Unit & integration: services (
userService, utilities likeformatDate,weight). - UI: visual and DOM interaction tests (
ContactButton, smoke tests). - Firestore Rules: verified with Emulator (
users,clients,boxes,inboundPackages,shipments). - E2E: login, admin panel access, and client portal flow.
All automated tests currently pass successfully (pnpm test:all ✅).
pnpm test # Unit / integration / UI
pnpm test:rules # Firestore rules (Emulator)
pnpm test:all # Full suite (with Emulator)
pnpm e2e # Playwright E2E- CTAs: orange
#eb6619; secondary with border and green focus#005f40. - Status:
StatusBadge(Received/Consolidated; Open/In transit/At destination/Closed). - Tables: sticky header, subtle zebra,
tabular-nums, clear hover. - Weights: always
X lb / Y kg(utilfmtWeightPairFromLb). - Accessibility: visible focus,
role="tablist/tab",aria-currentin steppers. - Large lists: history pages use pagination (e.g., 25 per page) and token-based search to avoid loading all documents at once.
inboundPackages: compositeclientId ASC, receivedAt DESC(forwhere(clientId) + orderBy(receivedAt)).inboundPackages: (token search) composite indexes may be required for:managerUid ASC, trackingTokens ARRAY_CONTAINS_ANY, receivedAt DESCmanagerUid ASC, clientTokens ARRAY_CONTAINS, receivedAt DESC(create the exact composite suggested by Firestore when prompted).
boxes: single index byclientId.- (Optional)
shipments: bystatus/country/typeper admin listing needs.
- users/{uid}:
uid,email,displayName,clientId,managedClientIds:string[],termsAcceptedAt,lang:"es",role:"client"|"admin"|"superadmin"|"partner_admin". - clients/{id}:
code,name,email,phone,country,state,city,address,emailAlt?,postalCode?,docType?,docNumber?,activo,createdAt,managerUid?. - inboundPackages/{id}:
tracking,carrier('UPS'|'FedEx'|'USPS'|'DHL'|'Amazon'|'Other'),clientId,weightLb:number,photoUrl?,status('received'|'boxed'|'void'),receivedAt. - boxes/{id}:
code,clientId,type('COMERCIAL'|'FRANQUICIA'),country,itemIds:string[],weightLb:number,status('open'|'closed'),shipmentId?:string|null,createdAt?. - shipments/{id}:
code,country,type('COMERCIAL'|'FRANQUICIA'),status('open'|'shipped'|'arrived'|'closed'),boxIds:string[],clientIds:string[],openedAt?,arrivedAt?,closedAt?. - trackingAlerts/{id}:
uid,clientId,tracking,note?,createdAt.
- SuperAdmin: full access, user/partner management, can delete.
- Admin: full operational access.
- Operador: intake + box building (staff).
- Partner (partner_admin): multi-client view + client management for assigned clients; restricted from staff-only modules.
- Client: single-client portal under /mi.
Prerequisites
- pnpm is recommended (the repo includes
pnpm-lock.yaml). - Node.js 18.17+ (or Node 20+) to match Next.js 15 requirements and typical Vercel defaults.
-
Clone the repo and enter the folder:
cd /Users/lolo/PROYECTOS/lem-box-sistema-v2 -
Install dependencies:
pnpm install
-
Create
.env.localwith Firebase credentials:# Client SDK (required) NEXT_PUBLIC_FIREBASE_API_KEY=xxx NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=xxx NEXT_PUBLIC_FIREBASE_PROJECT_ID=lem-box-sistema-v2 NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=xxx NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=xxx NEXT_PUBLIC_FIREBASE_APP_ID=xxx # Firebase Admin SDK (required for /api/admin/*) FIREBASE_PROJECT_ID=lem-box-sistema-v2 FIREBASE_CLIENT_EMAIL=xxx@xxx.iam.gserviceaccount.com FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
Notes:
FIREBASE_PRIVATE_KEYmust preserve newlines (\n).- Without the Admin SDK vars,
/api/admin/*will fail in deploy.
-
Start the dev server:
pnpm dev
-
Open http://localhost:3000.
pnpm dev- development modepnpm build- production buildpnpm start- start local buildpnpm lint- linterpnpm format- code formatting
The project will be deployed on Vercel, connected to the main repository.
Backend services managed with Firebase (Firestore, Auth, Storage).
- Login with Firebase Auth.
- Package intake (tracking, weight, photo).
- Box building (Box Builder) + CSV export.
- 6×4 PDF labels (jsPDF, CDN).
- Client portal (MVP: History, Boxes, Shipments, Account, Report tracking).
- Role‑based security (RequireAuth + effective Firestore rules).
- Rates and reports.
- Hybrid scanner (BarcodeDetector + ZXing) with haptics/sounds.
- Sub‑clients (managedClientIds) with view selector.
- Usage telemetry/analytics.
- Offline‑first for intake.
- A) Admin panel + Client portal: consolidation, shipments, 6×4 labels, consistent UI/UX, dual weight.
- B) Data maintenance: backfill of
shipments.clientIds(legacy shipments) + indexes. - C) Future: rates/reports, hybrid scanner, sub‑clients, analytics.
- Intake: scan tracking, take/upload photo, lb↔kg conversion.
- Preparation: create box, add packages, CSV export, 6×4 label.
- Shipments: create, add boxes, change status, expand boxes.
- History: open box modal, edit reference, print label.
- Client portal: tabs History/Boxes/Shipments/Account, edit data, report tracking.
- Access: admin does not fall into
/mi; client cannot access/admin/*.
- Thermal printers: horizontal orientation, None margins, 100% scale.
- If the PDF opens blank: reload jsPDF (CDN) or disable blockers.
- Long references: text size auto‑adjusts.
- Visible focus on all controls.
aria-current="step"in steppers;role="tablist/tab"in tabs.- Touch targets ≥ 44px on buttons and interactive cells.
- TypeScript with a core-strict lint policy:
no-explicit-anyis error insrc/components/**andsrc/app/partner/**, and warn in legacy areas (admin/mi/api/tests/lib). - Pure components, no side‑effects on render.
- Commit style: Conventional Commits (
feat:,fix:,chore:…).
- If Next.js build/dev shows missing
.nextartifacts, clear cache:rm -rf .next node_modules/.cache. - Partner does not require label printing; label printing is for staff workflows.
- Bootstrap legacy clients:
/api/admin/bootstrap-all-clientslinks Firestoreclientsto Firebase Auth users and creates/updatesusers/{uid}docs. Intended as a one-time migration step. - Duplicate client codes:
- Detect:
/api/admin/detect-duplicate-codes - Fix (dry-run + apply):
/api/admin/fix-duplicate-codesAfter fixing, all new client creation goes through server endpoints that guarantee unique codes.
- Detect:
- Reindex search tokens: admin utilities exist to backfill
trackingTokens/clientTokensfor legacyinboundPackagesso global search works without loading all rows at once.
- Firestore rules published.
shipments.clientIdspopulated (legacy shipments).- Indexes created (see Firestore indexes section).
- Full smoke test of admin and client flows.
Project: portal.lem-box.com
Repository: github.com/devrodri-com/lem-box-sistema-v2
LEM-BOX V2 is a modern logistics platform built with performance, accessibility, and data security in mind.
- Source: Current system MySQL database (
tracking.users). - Status: Migration deferred until the end of the development sprint.
- Safe procedure:
- Create a snapshot of the Droplet in DigitalOcean.
- Connect to the database in read‑only mode.
- Export
userstable to CSV (/root/users.csv). - Download and then import into Firestore via script.
- Policy: No production changes until the new system is validated.