Skip to main content

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

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:
// 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
1

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;
2

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);
  }
};
3

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;
  }
}
4

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

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>
  )
}

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

Performance Optimizations

  • 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
  • Debounced search inputs (300ms delay)
  • TanStack Query caching (5-minute stale time)
  • Lazy loading with React Router code splitting
  • Optimized re-renders with React.memo and useMemo
  • Vite for fast HMR and optimized production builds
  • 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:
1

Horizontal Scaling

Multiple backend instances can run behind a load balancer. PostgreSQL connection pooling handles concurrent requests.
2

Database Scaling

PostgreSQL supports read replicas for analytics queries. Indexes on frequently queried columns ensure fast lookups.
3

Caching Layer

TanStack Query provides client-side caching. Redis can be added for server-side caching (planned).
4

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.

Build docs developers (and LLMs) love