Skip to main content
Tambo360’s cost tracking system enables precise financial management by associating direct costs with specific production batches. This granular approach provides accurate per-batch profitability and helps identify cost optimization opportunities.

Overview

The cost tracking module provides:
  • Direct cost assignment to production batches
  • Predefined cost categories for consistency
  • Optional observations for each expense
  • Batch-level and aggregate cost analysis
  • Integration with dashboard financial metrics

Cost Categories

The system defines four primary cost categories:
// schema.prisma:38-43
enum ConceptoCosto {
  insumos_basicos      // Basic supplies
  leche_cruda          // Raw milk
  cuajo_y_fermentos    // Rennet and ferments
  refrigeracion        // Refrigeration/cooling
}

Insumos Básicos

Basic Supplies - Essential production materials:
  • Packaging materials
  • Cleaning supplies
  • Labels and containers
  • General consumables

Leche Cruda

Raw Milk - Primary input for dairy production:
  • Fresh milk purchases
  • Milk quality premiums
  • Transportation costs
  • Supplier payments

Cuajo y Fermentos

Rennet & Ferments - Specialized ingredients:
  • Rennet enzymes
  • Bacterial cultures
  • Starter cultures
  • Specialty ingredients

Refrigeración

Refrigeration - Cooling and preservation:
  • Electricity for cooling
  • Refrigeration maintenance
  • Cold storage costs
  • Temperature control

Data Model

// schema.prisma:99-107
model CostosDirecto {
  idCostoDirecto String         @id @default(uuid())
  concepto       ConceptoCosto
  monto          Decimal        @db.Decimal(13, 2)
  observaciones  String?        @default("")
  fechaCreacion  DateTime       @default(now())
  idLote         String
  lote           LoteProduccion @relation(fields: [idLote], references: [idLote])
}

Key Fields

  • monto: Decimal with 2 decimal places for precise currency amounts
  • concepto: One of four predefined categories
  • observaciones: Optional notes about the expense
  • idLote: Links cost to specific production batch
All costs are stored as ARS (Argentine Pesos) by default. The system supports Decimal(13,2) which accommodates values up to 99,999,999,999.99.

Recording Costs

1

Identify Expense

During or after production, identify direct costs associated with the batch.
2

Create Cost Record

Submit cost information with the batch ID:
// Create cost (costController.ts:7-27)
POST /api/costos
Authorization: Bearer <jwt-token>

{
  "idLote": "lote-uuid",
  "concepto": "leche_cruda",
  "monto": 45000.00,
  "observaciones": "Compra de 500L de leche cruda - Proveedor Tambo San José"
}

// Response
{
  "success": true,
  "statusCode": 201,
  "message": "Costo registrado correctamente",
  "data": {
    "idCostoDirecto": "costo-uuid",
    "concepto": "leche_cruda",
    "monto": 45000.00,
    "observaciones": "Compra de 500L de leche cruda - Proveedor Tambo San José",
    "fechaCreacion": "2026-03-08T09:30:00.000Z",
    "idLote": "lote-uuid"
  }
}
3

Validation

The system validates:
// Validation checks (costController.ts:9-14)
- User is authenticated
- Schema validation passes
- Batch exists and belongs to user
- Amount is positive
- Concept is valid enum value

Cost Operations

Get Cost by ID

Retrieve a specific cost record:
// Get cost (costController.ts:29-51)
GET /api/costos/:id
Authorization: Bearer <jwt-token>

// Response
{
  "success": true,
  "data": {
    "idCostoDirecto": "costo-uuid",
    "concepto": "refrigeracion",
    "monto": 8500.00,
    "observaciones": "Consumo eléctrico cámara de frío - Marzo",
    "fechaCreacion": "2026-03-08T10:00:00.000Z",
    "idLote": "lote-uuid"
  }
}

Get Costs by Batch

Retrieve all costs for a specific production batch:
// Get batch costs (costController.ts:53-71)
GET /api/costos/lote/:loteId
Authorization: Bearer <jwt-token>

// Response
{
  "success": true,
  "data": [
    {
      "idCostoDirecto": "costo-uuid-1",
      "concepto": "leche_cruda",
      "monto": 45000.00,
      "observaciones": "Compra de leche"
    },
    {
      "idCostoDirecto": "costo-uuid-2",
      "concepto": "cuajo_y_fermentos",
      "monto": 3200.00,
      "observaciones": "Cultivos lácticos"
    },
    {
      "idCostoDirecto": "costo-uuid-3",
      "concepto": "refrigeracion",
      "monto": 8500.00,
      "observaciones": "Energía eléctrica"
    }
  ]
}
This endpoint is useful for:
  • Calculating total batch costs
  • Cost breakdown analysis
  • Profitability calculations

Update Cost

Modify existing cost records:
// Update cost (costController.ts:73-102)
PUT /api/costos/:id
Authorization: Bearer <jwt-token>

{
  "monto": 46000.00,
  "observaciones": "Compra de 500L de leche cruda - Proveedor Tambo San José - Ajustado por calidad premium"
}

// Response
{
  "success": true,
  "message": "Costo actualizado correctamente",
  "data": {
    "idCostoDirecto": "costo-uuid",
    "concepto": "leche_cruda",
    "monto": 46000.00,
    "observaciones": "Compra de 500L de leche cruda - Proveedor Tambo San José - Ajustado por calidad premium",
    ...
  }
}
Only costs belonging to the authenticated user’s batches can be updated.

Delete Cost

// Delete cost (costController.ts:104-122)
DELETE /api/costos/:id
Authorization: Bearer <jwt-token>

// Response
{
  "success": true,
  "message": "Costo eliminado correctamente",
  "data": null
}
Deleting a cost is permanent and affects batch profitability calculations. Consider updating instead.

Dashboard Integration

Costs are aggregated in the monthly dashboard:
// dashboardService.ts:44-52
const buildSummary = (result: InfoMes) => {
  const summary = result.reduce<SummaryResult>((acc, lote) => {
    const category = lote.producto.categoria;
    if (!acc[category]) {
      acc[category] = { cantidad: 0, costos: 0, mermas: 0 };
    }
    acc[category].costos += lote.costosDirectos.reduce(
      (sum: number, costo: CostosDirecto) => sum + Number(costo.monto), 
      0
    );
    return acc;
  }, {});
};

Dashboard Display

The dashboard shows total costs with month-over-month comparison:
// Dashboard.tsx:86-100
<StatCard
  title="Costos totales"
  value={data?.data.actual.costos}
  unit="$ "
  trend={
    data?.data.variaciones.costos !== null
      ? {
          value: data?.data.variaciones.costos,
          isPositive: data?.data.variaciones.costos >= 0,
        }
      : undefined
  }
  description={'vs ' + data?.data.mesPrevio}
  isPending={isPending}
/>
Sum of all costs for batches produced in the current month.

Historical Cost Analysis

The system tracks costs over 6 months for trend analysis:
// dashboardService.ts:143-148
if (metrica === "costos") {
  valor = lote.costosDirectos.reduce(
    (sum, c) => sum + Number(c.monto),
    0
  );
}
This data powers the historical comparison chart in the dashboard.

AI Analysis Integration

Costs are included in AI analysis for alerts:
// tamboEngineService.ts:83-87
costosDirectos: l.costosDirectos.map(c => ({
  concepto: c.concepto,
  monto: Number(c.monto),
  moneda: "ARS",
}))
The AI engine can identify:
  • Unusual cost patterns
  • Cost efficiency opportunities
  • Budget anomalies
  • Supplier comparison insights

Use Cases by Category

Scenario: Daily milk procurement
// Morning delivery from supplier
POST /api/costos
{
  "idLote": "batch-morning-uuid",
  "concepto": "leche_cruda",
  "monto": 45000.00,
  "observaciones": "500L @ $90/L - Proveedor Tambo San José - Calidad A"
}
Track supplier pricing and quality over time.
Scenario: Cheese production supplies
// Rennet and cultures for 100kg batch
POST /api/costos
{
  "idLote": "cheese-batch-uuid",
  "concepto": "cuajo_y_fermentos",
  "monto": 3200.00,
  "observaciones": "Cuajo líquido 250ml + Cultivo mesófilo 50g - Lote #1247"
}
Essential for per-kg production cost calculations.
Scenario: Refrigeration expenses
// Allocated cooling costs
POST /api/costos
{
  "idLote": "batch-uuid",
  "concepto": "refrigeracion",
  "monto": 8500.00,
  "observaciones": "Consumo eléctrico cámara de frío - 15 días @ 4°C"
}
Allocate energy costs based on storage time and batch size.
Scenario: Basic production materials
// Packaging for batch
POST /api/costos
{
  "idLote": "batch-uuid",
  "concepto": "insumos_basicos",
  "monto": 5600.00,
  "observaciones": "Envases plásticos 1kg x 150 unidades + etiquetas"
}
Track packaging costs for profitability analysis.

Validation & Security

User Authorization

// costController.ts:16-17
const user = (req as any).user;
if (!user) throw new AppError("Usuario no autenticado", 401);

Ownership Verification

Costs can only be created/modified for batches owned by the authenticated user

Schema Validation

All inputs validated against Zod schema before processing

Decimal Precision

Amounts stored as Decimal(13,2) for financial accuracy

Best Practices

Record Promptly

Log costs as they occur to ensure accurate batch profitability

Detailed Notes

Include supplier info, quantities, and unit prices in observations

Consistent Categorization

Use the same category for similar expenses across batches

Regular Review

Analyze cost trends monthly to identify optimization opportunities

Profitability Analysis

Calculate per-batch profitability:
// Pseudo-code for profitability calculation

// 1. Get batch details
const batch = await getBatch(idLote);

// 2. Sum all costs for the batch
const costs = await getCostsByBatch(idLote);
const totalCosts = costs.reduce((sum, c) => sum + c.monto, 0);

// 3. Account for waste
const waste = batch.mermas.reduce((sum, m) => sum + m.cantidad, 0);
const usableQuantity = batch.cantidad - waste;

// 4. Calculate cost per unit
const costPerUnit = totalCosts / usableQuantity;

// 5. Compare with selling price
const profit = sellingPrice - costPerUnit;
const profitMargin = (profit / sellingPrice) * 100;

Common Issues

Error: “Costo no encontrado”Causes:
  • Incorrect cost ID
  • Cost belongs to another user
  • Cost was deleted
Solution: Verify the cost ID and ownership.
Error: Schema validation failureSolution: Use only the four valid concepts:
  • insumos_basicos
  • leche_cruda
  • cuajo_y_fermentos
  • refrigeracion
Error: Schema validation failureSolution: Amounts must be positive. For refunds or adjustments, delete the original cost and create a new one with the correct amount.

API Reference

EndpointMethodPurpose
/api/costosPOSTCreate cost record
/api/costos/:idGETGet specific cost
/api/costos/lote/:loteIdGETGet all costs for a batch
/api/costos/:idPUTUpdate cost record
/api/costos/:idDELETEDelete cost record

Build docs developers (and LLMs) love