Overview
Tambo360 is built as a modern full-stack SaaS application with a clear separation of concerns between frontend, backend, and database layers. The architecture follows industry best practices for scalability, security, and maintainability.
Technology Stack
Tambo360 leverages modern, production-ready technologies across the stack:
React 19 + TypeScript The frontend is built with React 19 and TypeScript for type safety and modern React features. Key Dependencies:
React 19.2.4 : Latest React with concurrent features
TypeScript 5.8 : Full type safety across the application
Vite 6.2 : Lightning-fast build tool and dev server
React Router DOM 7.13 : Declarative routing with data loading
Styling & UI:
Tailwind CSS 4.1 : Utility-first CSS framework
Radix UI : Accessible, unstyled component primitives
Lucide React : Beautiful icon library
Framer Motion (motion) : Smooth animations
State & Data:
Zustand 5.0 : Lightweight state management
React Context : Authentication and global state
TanStack Query 5.90 : Server state management and caching
Axios 1.13 : HTTP client for API requests
Forms & Validation:
React Hook Form 7.71 : Performant form handling
Zod 4.3 : Schema validation
@hookform/resolvers : Zod integration for forms
Node.js + Express + TypeScript The backend is a RESTful API built with Express and TypeScript. Core Framework:
Node.js : JavaScript runtime
Express 4.22 : Web application framework
TypeScript 5.9 : Type-safe backend code
ts-node-dev : Development server with auto-reload
Database & ORM:
Prisma 5.22 : Modern TypeScript ORM
@prisma/client : Type-safe database client
PostgreSQL : Relational database
Authentication & Security:
jsonwebtoken 9.0 : JWT token generation and verification
bcrypt 6.0 : Password hashing
cookie-parser 1.4 : Cookie parsing middleware
cors 2.8 : Cross-origin resource sharing
Validation & Email:
Zod 4.3 : Runtime type validation
Nodemailer 7.0 : Email sending
Resend 6.9 : Modern email API
Mailtrap 4.4 : Email testing
API Documentation:
swagger-jsdoc 6.2 : Generate OpenAPI specs
swagger-ui-express 5.0 : Interactive API docs
Docker + PostgreSQL Containerized deployment with Docker Compose for consistency across environments. Container Stack:
Docker : Application containerization
Docker Compose : Multi-container orchestration
PostgreSQL 15 : Official PostgreSQL image
Configuration:
Multi-stage Dockerfile for frontend and backend
Development and production build targets
Health checks for service monitoring
Volume persistence for database
Custom DNS configuration (8.8.8.8, 8.8.4.4)
Application Structure
Project Organization
Tambo360 uses a monorepo structure with separate frontend and backend applications:
tambo360/
├── apps/
│ ├── backend/ # Node.js + Express + TypeScript API
│ │ ├── prisma/
│ │ │ ├── migrations/ # Database migrations
│ │ │ └── schema.prisma # Prisma schema definition
│ │ ├── src/
│ │ │ ├── config/ # Configuration files
│ │ │ ├── controllers/ # Route controllers
│ │ │ ├── middleware/ # Express middleware
│ │ │ ├── models/ # Data models
│ │ │ ├── routes/ # API route definitions
│ │ │ ├── schemas/ # Zod validation schemas
│ │ │ ├── services/ # Business logic layer
│ │ │ ├── types/ # TypeScript type definitions
│ │ │ ├── utils/ # Utility functions
│ │ │ ├── lib/ # Shared libraries (Prisma client)
│ │ │ ├── swagger.ts # Swagger configuration
│ │ │ └── server.ts # Application entry point
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ └── frontend/ # React 19 + Vite + TypeScript SPA
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── common/ # Reusable UI components
│ │ │ ├── layout/ # Layout components
│ │ │ └── shared/ # Feature-specific components
│ │ ├── context/ # React Context providers
│ │ ├── hooks/ # Custom React hooks
│ │ ├── pages/ # Page components
│ │ ├── routes/ # Route configuration
│ │ ├── services/ # API service layer
│ │ ├── types/ # TypeScript types
│ │ ├── utils/ # Utility functions
│ │ └── constants/ # Application constants
│ ├── Dockerfile
│ ├── package.json
│ ├── vite.config.ts
│ └── tsconfig.json
│
└── docker-compose.yml # Multi-service orchestration
Database Architecture
Entity Relationship Diagram
Tambo360’s database schema is designed for relational integrity and efficient querying:
Prisma Schema
The complete database schema is defined in Prisma:
User Model
Establishment Model
Production Batch Model
Enums
// From apps/backend/prisma/schema.prisma
model Usuario {
idUsuario String @id @default ( uuid ())
correo String @unique
contrasena String
nombre String @db.VarChar ( 50 )
verificado Boolean @default ( false )
fechaCreacion DateTime @default ( now ())
establecimientos Establecimiento []
tokens VerificarToken []
}
Backend Architecture
Layered Architecture Pattern
The backend follows a clean layered architecture:
Request → Router → Controller → Service → Prisma → Database
↓
Middleware
↓
Error Handler
Routes Layer
Define API endpoints and HTTP methods. Map routes to controller functions. // From apps/backend/src/routes/batch.ts
import express from "express" ;
import { authenticate } from "../middleware/authMiddleware" ;
import * as batchController from "../controllers/batchController" ;
const router = express . Router ();
router . post ( "/" , authenticate , batchController . crearLote );
router . put ( "/:idLote" , authenticate , batchController . editarLote );
router . delete ( "/:idLote" , authenticate , batchController . eliminarLote );
router . get ( "/" , authenticate , batchController . listarLotes );
router . get ( "/:idLote" , authenticate , batchController . obtenerLote );
router . patch ( "/:idLote/completar" , authenticate , batchController . completarLote );
export default router ;
Controllers Layer
Handle HTTP requests, validate input, and call service functions. // From apps/backend/src/controllers/batchController.ts
export const crearLote = async ( req : Request , res : Response , next : NextFunction ) => {
try {
// Validate request body with Zod
const parsed = crearLoteSchema . safeParse ( req . body );
if ( ! parsed . success ) {
const errores = parsed . error . issues . map ( e => e . message );
throw new AppError ( errores . join ( ", " ), 400 );
}
// Get authenticated user
const user = ( req as any ). user ;
if ( ! user ) throw new AppError ( "Usuario no autenticado" , 401 );
// Call service layer
const lote = await LoteService . crearLote ( user . id , parsed . data );
// Return success response
return res . status ( 201 ). json (
ApiResponse . success ( lote , "Lote creado correctamente" , 201 )
);
} catch ( error ) {
next ( error );
}
};
Services Layer
Contain business logic and interact with the database via Prisma. // From apps/backend/src/services/batchService.ts
export class LoteService {
static async crearLote ( idUsuario : string , data : CrearLoteDTO ) {
// Get user's establishment
const establecimiento = await prisma . establecimiento . findFirst ({
where: { idUsuario },
});
if ( ! establecimiento ) {
throw new AppError ( "El usuario no tiene un establecimiento registrado" , 400 );
}
// Validate product exists
const producto = await prisma . producto . findUnique ({
where: { idProducto: data . idProducto },
});
if ( ! producto ) {
throw new AppError ( "El producto seleccionado no existe" , 400 );
}
// Generate next batch number
const ultimoLote = await prisma . loteProduccion . findFirst ({
where: { idEstablecimiento: establecimiento . idEstablecimiento },
orderBy: { numeroLote: 'desc' }
});
const nuevoNumeroLote = ultimoLote ? ultimoLote . numeroLote + 1 : 1 ;
// Determine unit based on product category
const unidad : "kg" | "litros" =
producto . categoria === "quesos" ? "kg" : "litros" ;
// Create batch in database
const lote = await prisma . loteProduccion . create ({
data: {
idProducto: data . idProducto ,
idEstablecimiento: establecimiento . idEstablecimiento ,
cantidad: data . cantidad ,
unidad ,
fechaProduccion: data . fechaProduccion ?? undefined ,
estado: false ,
numeroLote: nuevoNumeroLote ,
},
include: {
producto: {
select: { nombre: true , categoria: true }
}
}
});
return lote ;
}
}
Middleware Layer
Handle cross-cutting concerns like authentication, error handling, and logging. // From apps/backend/src/middleware/authMiddleware.ts
export const authenticate = (
req : Request ,
res : Response ,
next : NextFunction
) : void => {
try {
// Extract JWT from HTTP-only cookie
const token = req . cookies ?. token ;
if ( ! token ) {
throw new AppError ( "No autenticado" , 401 );
}
// Verify JWT signature
const decoded = jwt . verify (
token ,
process . env . JWT_SECRET !
) as JwtPayload ;
// Attach user ID to request object
req . user = { id: decoded . user . idUsuario };
next ();
} catch ( error ) {
next ( new AppError ( "Token inválido o expirado" , 401 ));
}
};
API Routes Structure
// From apps/backend/src/routes/index.ts
import express from "express" ;
import RutasAutenticacion from "./auth" ;
import HealthRoutes from "./health" ;
import RutasEstablecimientos from "./establishment" ;
import RutasLotes from "./batch" ;
import RutasMermas from "./mermas" ;
import RutasCostos from "./cost" ;
import RutasProductos from "./product" ;
import RutasDashboard from "./dashboard" ;
import RutasAlertas from "./alertRoutes" ;
const router = express . Router ();
// API route registration
router . use ( '/auth' , RutasAutenticacion ); // Authentication endpoints
router . use ( '/health' , HealthRoutes ); // Health check
router . use ( '/establecimiento' , RutasEstablecimientos ); // Establishments
router . use ( '/lote' , RutasLotes ); // Production batches
router . use ( '/mermas' , RutasMermas ); // Waste management
router . use ( '/costos' , RutasCostos ); // Cost tracking
router . use ( '/productos' , RutasProductos ); // Products
router . use ( '/alertas' , RutasAlertas ); // Alerts
router . use ( '/dashboard' , RutasDashboard ); // Dashboard data
export default router ;
Frontend Architecture
Component Hierarchy
The React frontend follows a feature-based component organization:
App
├── AuthProvider (Context)
│ ├── PublicRoute
│ │ ├── Login
│ │ ├── Register
│ │ └── ResetPassword
│ │
│ ├── ProtectedRoute
│ │ └── CheckEstablishment
│ │ └── Layout
│ │ ├── Header
│ │ ├── Sidebar
│ │ └── Outlet
│ │ ├── Dashboard
│ │ ├── Produccion
│ │ ├── BatchDetails
│ │ ├── TamboEngine
│ │ └── Perfil
│ │
│ └── VerifyUser (standalone)
State Management Strategy
Authentication State
Server State
Form State
React Context for global authentication state: // From apps/frontend/src/context/AuthContext.tsx
interface AuthContextType extends AuthState {
setToken : ( token : string | null ) => void
login : ({ user , token } : { user : User ; token : string }) => void
setUser : ( user : User | null ) => void
logout : () => void
setLoading : ( loading : boolean ) => void
setError : ( error : string | null ) => void
}
export const AuthProvider : React . FC <{ children : React . ReactNode }> = ({
children ,
}) => {
const [ user , setUser ] = useState < User | null >( null )
const [ token , setToken ] = useState < string | null >( null )
const [ loading , setLoading ] = useState ( true )
const [ error , setError ] = useState < string | null >( null )
const isAuthenticated = !! user
// Fetch session on mount
useEffect (() => {
fetchSession ()
}, [])
return (
< AuthContext . Provider value = {{
user , setUser , token , setToken ,
isAuthenticated , loading , error ,
login , logout , setLoading , setError
}} >
{ children }
</ AuthContext . Provider >
)
}
TanStack Query for server data caching and synchronization: // From apps/frontend/src/hooks/batch/useBatches.ts
export const useBatches = ({ filters } : { filters : any }) => {
return useQuery ({
queryKey: [ 'batches' , filters ],
queryFn : async () => {
const response = await api . get ( '/lote' , { params: filters })
return response . data
},
staleTime: 1000 * 60 * 5 , // 5 minutes
})
}
// Usage in component
const { data , isPending , error } = useBatches ({
filters: {
nombre: searchTerm ,
orden: 'desc' ,
pagina: '1'
}
})
React Hook Form with Zod validation: // Form with validation
const form = useForm ({
resolver: zodResolver ( loginSchema ),
defaultValues: {
correo: '' ,
contraseña: ''
}
})
// Zod schema
const loginSchema = z . object ({
correo: z . string (). email ( 'Email inválido' ),
contraseña: z . string (). min ( 8 , 'Mínimo 8 caracteres' )
})
Routing Architecture
// From apps/frontend/src/routes/AppRoutes.tsx
export const AppRoutes = () => {
const { loading } = useAuth ()
if ( loading ) return < LoadingSpinner />
return (
< Routes >
{ /* Public routes */ }
< Route element = {<PublicRoute><Outlet /></PublicRoute>} >
< Route path = {ROUTES. LOGIN } element = {<Login />} />
< Route path = {ROUTES. REGISTER } element = {<Register />} />
< Route path = "/auth/reset-password" element = {<ResetPassword />} />
</ Route >
{ /* Email verification (standalone) */ }
< Route path = "/auth/verify" element = {<VerifyUser />} />
{ /* Establishment setup */ }
< Route path = "/establecimiento" element = {<Establishment />} />
{ /* Protected routes with layout */ }
< Route element = {
<ProtectedRoute>
<CheckEstablishment>
<Layout />
</CheckEstablishment>
</ProtectedRoute>
} >
< Route index element = {<Navigate to = {ROUTES. DASHBOARD } replace /> } />
< Route path = {ROUTES. DASHBOARD } element = {<Dashboard />} />
< Route path = "/produccion" element = {<Produccion />} />
< Route path = "/produccion/lote/:loteId" element = {<BatchDetails />} />
< Route path = "/tambo-engine" element = {<TamboEngine />} />
< Route path = "/perfil" element = {<Perfil />} />
</ Route >
{ /* Catch-all redirect */ }
< Route path = "*" element = {<Navigate to = "/" replace /> } />
</ Routes >
)
}
Authentication Flow
Complete Auth Lifecycle
JWT Token Structure
// Token payload from apps/backend/src/controllers/authController.ts
const userData = {
user: {
nombre: user . nombre ,
correo: user . correo ,
idUsuario: user . idUsuario ,
verificado: user . verificado ,
fechaCreacion: user . fechaCreacion ,
establecimientos: user . establecimientos
}
}
const token = jwt . sign ( userData , process . env . JWT_SECRET ! , {
expiresIn: "1d"
});
// Set as HTTP-only cookie
res . cookie ( "token" , token , {
httpOnly: true , // Prevent XSS
secure: isProduction , // HTTPS only in production
sameSite: isProduction ? "none" : "lax" , // CSRF protection
maxAge: 24 * 60 * 60 * 1000 // 24 hours
});
Docker Deployment
Multi-Container Setup
# From docker-compose.yml
version : '3.8'
services :
db :
image : postgres:15
container_name : tambo-db
restart : always
environment :
POSTGRES_USER : postgres
POSTGRES_PASSWORD : tambo360
POSTGRES_DB : tambo
ports :
- "5433:5432"
volumes :
- postgres_data:/var/lib/postgresql/data
backend :
build :
context : ./apps/backend
target : development
ports :
- "3000:3000"
environment :
- NODE_ENV=${NODE_ENV}
- PORT=${PORT}
- DATABASE_URL=${DATABASE_URL}
- JWT_SECRET=${JWT_SECRET}
- EMAIL_USER=${EMAIL_USER}
- EMAIL_PASS=${EMAIL_PASS}
- FRONTEND_URL=${FRONTEND_URL}
volumes :
- ./apps/backend:/app
- /app/node_modules
restart : unless-stopped
healthcheck :
test : [ "CMD" , "node" , "-e" , "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" ]
interval : 30s
timeout : 10s
retries : 3
start_period : 40s
frontend :
build :
context : ./apps/frontend
target : development
ports :
- "5173:5173"
environment :
- VITE_API_URL=http://localhost:3000/api
volumes :
- ./apps/frontend:/app
- /app/node_modules
restart : unless-stopped
depends_on :
backend :
condition : service_healthy
volumes :
postgres_data :
networks :
default :
name : example-auth-network
Health Checks
The backend includes a health check endpoint:
// From apps/backend/src/controllers/healthController.ts
export const checkHealth = ( req : Request , res : Response ) => {
res . status ( 200 ). json ({
status: 'healthy' ,
timestamp: new Date (). toISOString (),
uptime: process . uptime ()
});
};
API Documentation (Swagger)
Tambo360 includes interactive API documentation via Swagger UI:
// From apps/backend/src/swagger.ts
import swaggerJsdoc from 'swagger-jsdoc' ;
import swaggerUi from 'swagger-ui-express' ;
const options = {
definition: {
openapi: '3.0.0' ,
info: {
title: 'Tambo360 API' ,
version: '1.0.0' ,
description: 'Dairy farm production management API' ,
},
servers: [
{
url: 'http://localhost:3000/api' ,
description: 'Development server' ,
},
],
},
apis: [ './src/routes/*.ts' , './src/controllers/*.ts' ],
};
const specs = swaggerJsdoc ( options );
export const setupSwagger = ( app : Express ) => {
app . use ( '/api-docs' , swaggerUi . serve , swaggerUi . setup ( specs ));
};
Access at: http://localhost:3000/api-docs
Database Query Optimization
Indexed columns for faster lookups (user email, token hashes)
Selective field inclusion with Prisma select and include
Pagination for large datasets (20 records per page)
Database transactions for atomic operations
HTTP-only cookies reduce request size
Gzip compression in production
CDN for static assets (planned)
Connection pooling in Prisma
Security Measures
Tambo360 implements multiple layers of security to protect user data and prevent common web vulnerabilities.
Security Checklist
✅ Password Hashing : bcrypt with salt rounds
✅ JWT Tokens : Signed with secret, 24-hour expiration
✅ HTTP-Only Cookies : Prevent XSS token theft
✅ CORS Configuration : Whitelist trusted origins
✅ Input Validation : Zod schemas on both client and server
✅ SQL Injection Prevention : Prisma parameterized queries
✅ Email Verification : Required before account activation
✅ Token Expiration : Short-lived verification tokens
✅ HTTPS in Production : Secure cookie flag enabled
✅ SameSite Cookies : CSRF protection
Scalability Considerations
Tambo360 is designed to scale:
Horizontal Scaling
Multiple backend instances can run behind a load balancer. PostgreSQL connection pooling handles concurrent requests.
Database Scaling
PostgreSQL supports read replicas for analytics queries. Indexes on frequently queried columns ensure fast lookups.
Caching Layer
TanStack Query provides client-side caching. Redis can be added for server-side caching (planned).
CDN & Static Assets
Frontend can be deployed to a CDN for global distribution. Static assets are separated from API server.
Next Steps
API Reference Explore complete API endpoint documentation.
Installation Guide Set up Tambo360 locally or in production.
Database Setup Configure PostgreSQL and run migrations.
Frontend Guide Understand React components and state management.