Skip to main content
Get up and running with Tambo360 by creating your account, setting up your first establishment, and tracking your first production batch.
1

Create Your Account

Register for a Tambo360 account with your email and password.
apps/backend/src/controllers/authController.ts:11-39
export const crearCuenta = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const parsed = crearCuentaSchema.safeParse(req.body);
    
    if (!parsed.success) {
      const errores = parsed.error.issues.map(e => e.message);
      throw new AppError(errores.join(", "), 400);
    }

    const usuario = await UserService.crear(parsed.data);
    
    return res.status(201).json(
      ApiResponse.success(usuario, "Usuario creado correctamente. Verifica tu email.", 201)
    );
  } catch (error) {
    next(error);
  }
};
Password Requirements:
  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • At least one special character (@$!%*?&)
  • Maximum 50 characters
After registration, you’ll receive a verification email. Check your inbox and verify your account before logging in.
2

Verify Your Email

Click the verification link in your email or use the verification token.
apps/backend/src/services/userService.ts:89-138
async verificarEmail(idUsuario: string, token: string): Promise<void> {
  const tokenRecord = await prisma.verificarToken.findFirst({
    where: {
      idUsuario,
      tipo: 'verificacion',
      usadoEn: null,
      expiraEn: {
        gt: new Date()
      }
    }
  });

  if (!tokenRecord) {
    throw new AppError("Token de verificación inválido o expirado", 400);
  }

  const tokenValido = await bcrypt.compare(token, tokenRecord.tokenHash);
  
  if (!tokenValido) {
    throw new AppError("Token de verificación inválido", 400);
  }

  await prisma.$transaction([
    prisma.usuario.update({
      where: { idUsuario },
      data: { verificado: true }
    }),
    prisma.verificarToken.update({
      where: { tokenid: tokenRecord.tokenid },
      data: { usadoEn: new Date() }
    })
  ]);
}
The verification token expires in 24 hours. If it expires, you can request a new verification email.
3

Log In

Sign in with your verified email and password.
POST /api/auth/iniciar-sesion
Content-Type: application/json

{
  "correo": "[email protected]",
  "contrasena": "YourPassword123!"
}
Upon successful login, you’ll receive:
  • A JWT token stored in an HTTP-only cookie
  • User profile information
  • List of your establishments
The authentication token is automatically included in subsequent requests via cookies.
4

Create Your First Establishment

Set up your dairy farm establishment with location details.
apps/backend/src/schemas/establishmentSchema.ts
export const crearEstablecimientoSchema = z.object({
  nombre: z.string().min(1, "El nombre es requerido"),
  localidad: z.string().min(1, "La localidad es requerida"),
  provincia: z.string().min(1, "La provincia es requerida")
});
Example Request:
POST /api/establecimiento
Content-Type: application/json

{
  "nombre": "Lacteos La Pampa",
  "localidad": "General Pico",
  "provincia": "La Pampa"
}
Response:
{
  "success": true,
  "message": "Establecimiento creado correctamente",
  "data": {
    "idEstablecimiento": "550e8400-e29b-41d4-a716-446655440000",
    "nombre": "Lacteos La Pampa",
    "localidad": "General Pico",
    "provincia": "La Pampa",
    "fechaCreacion": "2024-03-08T10:30:00.000Z",
    "idUsuario": "123e4567-e89b-12d3-a456-426614174000"
  }
}
Currently, Tambo360 supports one establishment per user. This establishment will be used for all production tracking.
5

Create Your First Production Batch

Track a production batch for cheese or milk.
apps/backend/src/services/batchService.ts:8-55
static async crearLote(idUsuario: string, data: CrearLoteDTO) {
  const establecimiento = await prisma.establecimiento.findFirst({
    where: { idUsuario },
  });

  if (!establecimiento) {
    throw new AppError("El usuario no tiene un establecimiento registrado", 400);
  }

  const producto = await prisma.producto.findUnique({
    where: { idProducto: data.idProducto },
  });

  if (!producto) {
    throw new AppError("El producto seleccionado no existe", 400);
  }

  const ultimoLote = await prisma.loteProduccion.findFirst({
    where: { idEstablecimiento: establecimiento.idEstablecimiento },
    orderBy: { numeroLote: 'desc' }
  });

  const nuevoNumeroLote = ultimoLote ? ultimoLote.numeroLote + 1 : 1;

  const unidad: "kg" | "litros" = producto.categoria === "quesos" ? "kg" : "litros";

  const lote = await prisma.loteProduccion.create({
    data: {
      idProducto: data.idProducto,
      idEstablecimiento: establecimiento.idEstablecimiento,
      cantidad: data.cantidad,
      unidad,
      fechaProduccion: data.fechaProduccion ?? undefined,
      estado: data.estado ?? false,
      numeroLote: nuevoNumeroLote,
    },
    include: {
      producto: {
        select: {
          nombre: true,
          categoria: true
        }
      }
    }
  });

  return lote;
}
Example Request:
POST /api/lote/registrar
Content-Type: application/json

{
  "fechaProduccion": "08/03/2024",
  "idProducto": "prod-queso-001",
  "cantidad": 150.5
}
Response:
{
  "success": true,
  "message": "Lote creado correctamente",
  "data": {
    "idLote": "batch-123",
    "numeroLote": 1,
    "fechaProduccion": "2024-03-08T00:00:00.000Z",
    "producto": {
      "idProducto": "prod-queso-001",
      "nombre": "Queso Semi-duro",
      "categoria": "quesos"
    },
    "cantidad": "150.5",
    "unidad": "kg",
    "estado": false,
    "establecimiento": {
      "nombre": "Lacteos La Pampa"
    },
    "mermas": [],
    "costosDirectos": []
  }
}
  • Date format: Use DD/MM/YYYY (e.g., “08/03/2024”)
  • Units: kg for cheese, litros for milk
  • Auto-numbering: numeroLote increments automatically
  • Estado: Defaults to false (batch is open for tracking)
6

Track Waste (Optional)

Record any production waste associated with your batch.
POST /api/mermas
Content-Type: application/json

{
  "idLote": "batch-123",
  "tipo": "Natural",
  "cantidad": 5.2,
  "observacion": "Waste during cheese aging process"
}
Waste Types:
  • Natural - Expected waste from natural production processes
  • Tecnica - Technical failures or equipment issues
  • Administrativa - Administrative errors or miscounts
  • Danio - Product damage during handling
Waste quantity cannot exceed the batch production quantity.
7

Track Direct Costs (Optional)

Add costs associated with this production batch.
POST /api/costos
Content-Type: application/json

{
  "idLote": "batch-123",
  "concepto": "leche_cruda",
  "monto": 12500.50,
  "observaciones": "500 liters of raw milk at $25/liter"
}
Cost Concepts:
  • insumos_basicos - Basic production supplies
  • leche_cruda - Raw milk costs
  • cuajo_y_fermentos - Rennet and ferments
  • refrigeracion - Refrigeration and cooling
Amounts support up to 13 digits with 2 decimal places (e.g., 99999999999.99).
8

Complete the Batch

Lock the batch to include it in analytics and reporting.
PUT /api/lote/{idLote}/completar
This sets estado: true, marking the batch as complete. Once completed:
  • ✅ The batch is included in monthly dashboard analytics
  • ✅ You can’t add new waste or costs to this batch
  • ✅ The batch data is locked for historical reporting
You can still view the batch details, but modification is restricted to maintain data integrity.
9

View Your Dashboard

Navigate to the dashboard to see your production analytics.The dashboard shows:
  • Monthly KPIs: Cheese production, milk sales, waste percentage, total costs
  • Month-over-month trends: Percentage changes vs. previous month
  • 6-month historical charts: Track production, waste, and costs over time
  • Daily production log: Recent batches at a glance
  • Alerts: AI-powered insights from TamboEngine (requires 15+ batches)
apps/frontend/src/pages/Dashboard.tsx
const { data, isPending } = useCurrentMonth()

const totalProduccion =
  (data?.data.actual.quesos || 0) + (data?.data.actual.leches || 0)

const porcentajeMermas =
  totalProduccion > 0
    ? ((data?.data.actual.mermas || 0) / totalProduccion) * 100
    : 0

What’s Next?

Explore Features

Learn about authentication, multi-establishment support, and more

API Reference

Integrate with the Tambo360 REST API

Set Up Production

Deploy Tambo360 to production

View Architecture

Understand the system architecture and tech stack

Troubleshooting

  • Check your spam/junk folder
  • Verify you entered the correct email address
  • Request a new verification email via POST /api/auth/reenviar-verificacion
  • Verification tokens expire after 24 hours
Ensure you’re using the DD/MM/YYYY format:
  • ✅ Correct: "08/03/2024"
  • ❌ Incorrect: "2024-03-08", "03/08/2024", "8/3/24"
Common causes:
  • You haven’t created an establishment yet
  • Invalid product ID - verify the product exists
  • Date format is incorrect (use DD/MM/YYYY)
  • Unit doesn’t match product category (use kg for cheese, litros for milk)
The sum of all waste entries cannot exceed the batch production quantity. Check:
  • Total waste across all entries for this batch
  • Ensure quantities are in the same units
  • Review existing waste entries: GET /api/mermas?idLote={batchId}
The dashboard requires at least one completed batch:
  1. Create a production batch
  2. Optionally add waste and costs
  3. Mark the batch as complete: PUT /api/lote/{id}/completar
  4. Refresh the dashboard to see updated metrics

Need Help?

View Full Documentation

Explore the complete Tambo360 documentation

Build docs developers (and LLMs) love