En este ejercicio se realiza la simulación de un Centro de Distribución Central (CDC) con el fin de evaluar su desempeño operativo bajo un horizonte de trabajo de 8 horas. A partir de parámetros definidos como la capacidad de almacenamiento, tiempos de procesamiento en recepción, almacenamiento, alistamiento y despacho, así como la frecuencia de llegada de pedidos, se construye un modelo en R con la librería simmer que permite representar el flujo de pedidos en el sistema.
El objetivo principal es calcular e interpretar una serie de Indicadores Clave de Desempeño (KPIs) que reflejan la eficiencia y nivel de servicio del CDC, entre los cuales se encuentran:
Order Cycle Time: tiempo promedio de ciclo por pedido.
Dock Utilization: nivel de utilización de los muelles de recepción y despacho.
Order Fill Rate: porcentaje de pedidos atendidos en su totalidad.
Inventory Turnover: índice de rotación del inventario.
Backorders/Stockouts: número de pedidos pendientes o no atendidos por falta de inventario.
Con estos resultados se busca analizar la capacidad de respuesta del CDC, identificar posibles cuellos de botella y evaluar si los recursos disponibles permiten satisfacer la demanda de manera eficiente.
Cargar librerías necesarias
# ---------- LIBRERÍAS ----------
library(simmer) #librería de simulación de eventos discretos
library(simmer.plot) #permite visualizar fácilmente resultados de simulación
library(ggplot2) #libreria para graficar
library(dplyr) # libreria para el manejo, transformación y análisis de datos.
Aquí se definen las condiciones del sistema (turno de trabajo, frecuencia de pedidos, tiempos deterministas de procesos y capacidad del almacén).
# ---------- PARÁMETROS ----------
sim_time <- 8 * 60 # 8 horas en minutos
order_interval <- 5 # 1 pedido cada 5 minutos
warehouse_capacity <- 100 # capacidad máxima
receiving_time <- 2 # tiempo de recepción
storage_time <- 1 # tiempo de almacenamiento
picking_time <- 3 # tiempo de picking
shipping_time <- 2 # tiempo de envío
initial_inventory <- 50 # inventario inicial (supuesto)
# Variables globales (inventario, backorders, etc.)
inventory <- initial_inventory # nivel de inventario disponible en cada momento.
backorders <- 0 # pedidos pendientes por falta de stock
fulfilled_orders <- 0 # pedidos atendidos con éxito
total_orders <- 0 # pedidos generados
inventory_movements <- 0 # para turnover
Se crea el entorno de simulación con 3 recursos:
ReceivingDock: muelle de recepción (capacidad 1).
ShippingDock: muelle de envío (capacidad 1).
WarehouseSpace: espacio de almacenamiento (capacidad máxima 100 unidades).
# ---------- AMBIENTE ----------
env <- simmer("CDC#1")
# Recursos
env %>%
add_resource("ReceivingDock", capacity = 1) %>%
add_resource("ShippingDock", capacity = 1) %>%
add_resource("WarehouseSpace", capacity = warehouse_capacity)
## simmer environment: CDC#1 | now: 0 | next:
## { Monitor: in memory }
## { Resource: ReceivingDock | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
## { Resource: ShippingDock | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
## { Resource: WarehouseSpace | monitored: TRUE | server status: 0(100) | queue status: 0(Inf) }
Aquí se modela el camino que sigue un pedido dentro del CDC:
Recepción → el pedido llega y ocupa el muelle de recepción (2 min).
Almacenamiento → se incrementa el inventario en 1 unidad y se registra movimiento (1 min).
Picking:
- Si hay inventario disponible → se descuenta 1 unidad, se atiende el pedido, pasa al muelle de envío (3 + 2 min).
- Si **NO** hay inventario → se registra como backorder (pedido insatisfecho).
# ---------- TRAJECTORY ----------
order_traj <- trajectory("OrderFlow") %>%
# Llega pedido (demanda 1 unidad)
set_attribute("start_time", function() {now(env)}) %>%
seize("ReceivingDock", 1) %>%
timeout(receiving_time) %>%
release("ReceivingDock", 1) %>%
# Almacenamiento incrementa inventario (entrada de stock)
set_attribute("stock_in", function() {
inventory <<- min(inventory + 1, warehouse_capacity)
inventory_movements <<- inventory_movements + 1
return(inventory)
}) %>%
timeout(storage_time) %>%
# Picking (se necesita inventario)
branch(
option = function() {
total_orders <<- total_orders + 1
if (inventory > 0) {
inventory <<- inventory - 1
fulfilled_orders <<- fulfilled_orders + 1
return(1) # va al flujo normal
} else {
backorders <<- backorders + 1
return(2) # flujo alterno (no hay stock)
}
},
continue = c(TRUE, TRUE),
trajectory("Fulfilled") %>%
timeout(picking_time) %>%
seize("ShippingDock", 1) %>%
timeout(shipping_time) %>%
release("ShippingDock", 1) %>%
set_attribute("end_time", function() {now(env)}),
trajectory("Backorder") %>%
set_attribute("end_time", function() {now(env)})
)
Para la sección de Generador tenemos:
# ---------- GENERADOR ----------
env %>%
add_generator("Order", order_traj, at(seq(0, sim_time, by = order_interval))) %>%
run(until = sim_time)
## simmer environment: CDC#1 | now: 480 | next: 480
## { Monitor: in memory }
## { Resource: ReceivingDock | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
## { Resource: ShippingDock | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
## { Resource: WarehouseSpace | monitored: TRUE | server status: 0(100) | queue status: 0(Inf) }
## { Source: Order | monitored: 1 | n_generated: 97 }
Se calculan los indicadores clave:
Order Cycle Time: Tiempo promedio desde que llega un pedido hasta que se despacha.
Dock Utilization: Proporción de tiempo que los muelles estuvieron ocupados.
Order Fill Rate: Porcentaje de pedidos atendidos correctamente.
Inventory Turnover: Veces que el inventario se renovó durante la simulación.
Backorders: Número de pedidos no atendidos por falta de inventario.
# ---------- KPIs ----------
arrivals <- get_mon_arrivals(env)
resources <- get_mon_resources(env)
# 1. Order Cycle Time
arrivals$cycle_time <- arrivals$end_time - arrivals$start_time
avg_cycle_time <- mean(arrivals$cycle_time, na.rm = TRUE)
# 2. Dock Utilization
dock_util <- resources %>%
dplyr::filter(resource %in% c("ReceivingDock", "ShippingDock")) %>%
dplyr::group_by(resource) %>%
dplyr::summarise(utilization = sum((lead(time, default = sim_time) - time) *
server) / sim_time)
# 3. Order Fill Rate
order_fill_rate <- ifelse(total_orders > 0, fulfilled_orders / total_orders, 0)
# 4. Inventory Turnover
inventory_turnover <- ifelse(mean(c(inventory, initial_inventory)) > 0,
inventory_movements / ((initial_inventory + inventory) / 2),
NA)
# 5. Backorders
backorders_total <- backorders
Se imprimen los valores calculados para cada KPI en la consola y se genera un histograma con la distribución de los tiempos de ciclo de los pedidos.
# ---------- RESULTADOS ----------
cat("----- KPIs -----\n")
## ----- KPIs -----
cat("1. Avg Order Cycle Time (min):", round(avg_cycle_time, 2), "\n")
## 1. Avg Order Cycle Time (min): 8
cat("2. Dock Utilization:\n")
## 2. Dock Utilization:
print(dock_util)
## # A tibble: 2 × 2
## resource utilization
## <chr> <dbl>
## 1 ReceivingDock 0.4
## 2 ShippingDock 0.396
cat("3. Order Fill Rate:", round(order_fill_rate * 100, 2), "%\n")
## 3. Order Fill Rate: 100 %
cat("4. Inventory Turnover:", round(inventory_turnover, 2), "\n")
## 4. Inventory Turnover: 1.92
cat("5. Backorders:", backorders_total, "\n")
## 5. Backorders: 0
# ---------- GRÁFICO DE TIEMPO DE CICLO ----------
ggplot(arrivals, aes(x = cycle_time)) +
geom_histogram(binwidth = 1, fill = "steelblue", color = "black") +
labs(title = "Distribución del tiempo de ciclo", x = "Tiempo de ciclo (min)",
y = "Frecuencia")
1. Avg Order Cycle Time (8 min)
El tiempo promedio de ciclo por pedido fue de 8 minutos.
Esto significa que, desde que un pedido entra al sistema hasta que sale despachado, el flujo tarda aproximadamente 8 minutos en completarse.
Es un tiempo bastante corto, lo que indica que la capacidad de procesamiento de tu CDC es adecuada para la carga simulada.
2. Dock Utilization (Receiving: 40%, Shipping: 39.6%)
El muelle de recepción se utilizó un 40% del tiempo total simulado.
El muelle de despacho estuvo ocupado un 39.6% del tiempo.
Ambos valores están en torno al 40%, lo que refleja un buen balance de carga y que no hay cuellos de botella severos en estos recursos.
En la práctica, esto significa que los muelles no están saturados y existe margen para manejar más pedidos sin generar congestión.
3. Order Fill Rate (100%)
El nivel de servicio es perfecto (100%): todos los pedidos generados fueron atendidos.
Esto indica que el inventario inicial y la capacidad de almacenamiento fueron suficientes para responder a la demanda durante toda la jornada simulada.
En un CDC real, mantener siempre 100% es difícil; aquí se logra porque el inventario nunca se agotó.
4. Inventory Turnover (1.92)
El índice de rotación de inventario fue de 1.92 veces en las 8 horas simuladas.
Esto quiere decir que, en promedio, el inventario “se renovó” casi 2 veces durante el periodo.
Una rotación relativamente alta en tan poco tiempo muestra que el flujo de entrada y salida fue dinámico, lo cual es positivo para evitar acumulación y costos de almacenamiento.
5. Backorders (0)
No hubo pedidos pendientes ni faltantes de inventario.
El CDC respondió de manera eficiente a toda la demanda sin necesidad de retrasar pedidos.