Skip to content

Modulo bajas de inventario

Descripción del módulo

Este módulo permite registrar bajas de inventario de productos químicos. Cuando se da de baja un producto, se actualiza automáticamente:

  • La cantidad disponible del lote específico
  • El saldo total disponible del producto químico (suma de todos los lotes)
  • Se crea un registro en BalanceHistory para el historial de movimientos

Flujo completo para Frontend

Paso 1: Obtener productos químicos por categoría

Endpoint: [ POST ] /search-chemical-product
Body:

json
{
  "category": "desinfectante" // Valores válidos: 'regulador', 'desinfectante', 'floculante'
}

Response: Lista de productos químicos filtrados por categoría

Paso 2: Obtener lotes disponibles de un producto químico

Endpoint: [ GET ] /inventories/write-offs/lots/:chemicalProductId
Parámetros:

  • chemicalProductId: ID del producto químico (se puede usar el name del producto)

Response:

json
{
  "_id": "producto_id",
  "name": "Nombre del producto",
  "category": "desinfectante",
  "availableQuantity": 5000, // Saldo total disponible
  "quantitiesByLot": [
    {
      "quantity": 2000,
      "units": "litros",
      "lot": "L1",
      "expirationDate": "2025-12-31T00:00:00.000Z"
    },
    {
      "quantity": 3000,
      "units": "litros",
      "lot": "L2",
      "expirationDate": "2026-01-15T00:00:00.000Z"
    }
  ]
}

Nota importante: Los lotes con quantity: 0 deberían omitirse del response (TODO pendiente)

Paso 3: Crear baja de inventario

Endpoint: [ POST ] /inventories/write-offs
Body:

json
{
  "category": "desinfectante",
  "chemicalProduct": "nombre_del_producto", // Se usa el name, no el _id
  "lot": ["L1"], // Array con el lote seleccionado (solo se permite un lote a la vez)
  "quantity": 500, // Cantidad a dar de baja
  "units": "litros",
  "reason": "perdida", // Valores válidos: 'perdida', 'dano', 'vencimiento', 'otro'
  "customReason": null // Solo requerido si reason === 'otro'
}

Validaciones importantes:

  • Si reason === 'otro'customReason es obligatorio y no puede estar vacío
  • Si reason !== 'otro'customReason debe ser null o no enviarse
  • quantity debe ser mayor a 0
  • quantity no puede ser mayor que la cantidad disponible del lote seleccionado
  • El lote seleccionado debe existir en el producto químico

Response (201):

json
{
  "_id": "write_off_id",
  "category": "desinfectante",
  "chemicalProduct": "nombre_del_producto",
  "lot": ["L1"],
  "quantity": 500,
  "units": "litros",
  "reason": "perdida",
  "customReason": null,
  "modifiedBy": "user_id",
  "createdAt": "2025-12-04T10:30:00.000Z",
  "updatedAt": "2025-12-04T10:30:00.000Z"
}

Endpoints disponibles

1. Crear baja de inventario

  • Método: POST
  • Ruta: /inventories/write-offs
  • Autenticación: Requerida (JWT)
  • Permisos: INVENTORY_WRITE_OFF_CREATION
  • Validaciones: Ver sección de validaciones

2. Listar todas las bajas de inventario

  • Método: GET
  • Ruta: /inventories/write-offs
  • Autenticación: Requerida (JWT)
  • Permisos: INVENTORY_WRITE_OFF_CREATION
  • Response:
json
{
  "total": 10,
  "inventoryWriteOffs": [
    {
      "_id": "write_off_id",
      "category": "desinfectante",
      "chemicalProduct": "nombre_producto",
      "lot": ["L1"],
      "quantity": 500,
      "units": "litros",
      "reason": "perdida",
      "customReason": null,
      "modifiedBy": {
        "name": "Juan",
        "lastName": "Pérez"
      },
      "createdAt": "2025-12-04T10:30:00.000Z",
      "updatedAt": "2025-12-04T10:30:00.000Z"
    }
  ]
}

3. Obtener baja de inventario por ID

  • Método: GET
  • Ruta: /inventories/write-offs/:id
  • Autenticación: Requerida (JWT)
  • Permisos: INVENTORY_WRITE_OFF_CREATION
  • Response:
json
{
  "inventoryWriteOff": {
    "_id": "write_off_id",
    "category": "desinfectante",
    "chemicalProduct": "nombre_producto",
    "lot": ["L1"],
    "quantity": 500,
    "units": "litros",
    "reason": "perdida",
    "customReason": null,
    "modifiedBy": {
      "name": "Juan",
      "lastName": "Pérez"
    },
    "createdAt": "2025-12-04T10:30:00.000Z",
    "updatedAt": "2025-12-04T10:30:00.000Z"
  }
}

4. Obtener lotes de un producto químico

  • Método: GET
  • Ruta: /inventories/write-offs/lots/:chemicalProductId
  • Autenticación: Requerida (JWT)
  • Permisos: INVENTORY_WRITE_OFF_CREATION
  • Parámetros: chemicalProductId (puede ser el _id o el name del producto)

Lógica de negocio

Cuando se crea una baja de inventario, el sistema realiza automáticamente:

  1. Validaciones:

    • Verifica que la cantidad sea mayor a 0
    • Verifica que el producto químico exista
    • Verifica que el lote seleccionado exista
    • Verifica que la cantidad a dar de baja no exceda la cantidad disponible del lote
  2. Actualización del inventario:

    • Actualiza la cantidad del lote específico: nuevaCantidad = cantidadActual - cantidadBaja
    • Recalcula el saldo total del producto: suma de todas las cantidades de todos los lotes
    • Actualiza availableQuantity del producto químico con el nuevo saldo total
  3. Registro en historial:

    • Crea un registro en BalanceHistory con:
      • tipo: 'Baja de inventario'
      • movimiento: '****'
      • cantidadDosificada: cantidad dada de baja
      • totalBalance: nuevo saldo total del producto
      • lote: lote afectado
      • fechaExpiracion: fecha de expiración del lote

Estructura de datos

IInventoryWriteOff (Request Body)

typescript
{
  category: ChemicalCategoryType; // 'regulador' | 'desinfectante' | 'floculante'
  chemicalProduct: string; // Nombre del producto (name, no _id)
  lot: string[]; // Array con el lote seleccionado (solo un elemento)
  quantity: number; // Cantidad a dar de baja
  units: ValidUnitsType; // Unidad de medida
  reason: string; // 'perdida' | 'dano' | 'vencimiento' | 'otro'
  customReason?: string | null; // Solo si reason === 'otro'
}

Motivos válidos (enum)

  • 'perdida': Producto perdido
  • 'dano': Producto dañado
  • 'vencimiento': Producto vencido
  • 'otro': Otro motivo (requiere customReason)

Validaciones implementadas

En el Validador (express-validator):

  • category: Requerido
  • chemicalProduct: Requerido
  • lot: Requerido
  • quantity: Requerido
  • units: Requerido
  • reason: Requerido
  • customReason:
    • Si reason === 'otro' → Requerido y no puede estar vacío
    • Si reason !== 'otro' → No debe enviarse o debe ser null

En el Controlador:

  • quantity debe ser mayor a 0
  • El producto químico debe existir
  • El lote seleccionado debe existir
  • La cantidad a dar de baja no puede exceder la cantidad disponible del lote

Códigos de respuesta HTTP

  • 201: Baja de inventario creada exitosamente
  • 200: Consulta exitosa (GET)
  • 400: Error de validación (cantidad inválida, lote no existe, etc.)
  • 404: Producto químico o baja de inventario no encontrado
  • 422: Error de validación de Mongoose (enum inválido, etc.)
  • 500: Error interno del servidor

Notas importantes para Frontend

  1. Solo un lote a la vez: El sistema solo permite dar de baja de un lote por operación. El campo lot es un array pero solo debe contener un elemento.

  2. Campo customReason:

    • Solo se habilita cuando reason === 'otro'
    • Debe validarse en frontend que no esté vacío si se selecciona "otro"
    • No debe enviarse si reason !== 'otro'
  3. Validación de cantidad: El frontend debe validar que la cantidad no exceda la cantidad disponible del lote antes de enviar la petición.

  4. Actualización de UI: Después de crear una baja exitosa, se debe:

    • Actualizar la lista de lotes del producto
    • Actualizar el saldo total disponible
    • Mostrar mensaje de éxito
  5. Manejo de errores: Los errores de validación retornan un objeto con msg que contiene el mensaje de error específico.

Archivos relacionados

  • Controller: src/controllers/inventoryWriteOff/inventoryWriteOffController.ts
  • Routes: src/routes/v1/inventoryWriteOff/inventoryWriteOff.ts
  • Validator: src/validators/inventoryWriteOffValidator.ts
  • Model: src/models/inventoryWriteOff/inventoryWriteOff.ts
  • Interface: src/interfaces/IInventoryWriteOff/IInventoryWriteOff.ts
  • Service: src/services/inventoryService.ts
  • Enum: src/enums/InventoryWriteOff/enumsInventoryWriteOff.ts