Universidad Nacional de Cuyo Facultad de Ingeniería Ingeniería Industrial Mendoza - Argentina \(\dagger^{[1 y 3]}\) [ricardo.palma] [victoria.palma]@ingenieria.uncuyo.edu.ar
Universidad Católica Boliviana Departamento de Ingeniería Industrial La Paz - Bolivia \(\ddagger²\) flopez@ucb.edu.bo
Resumen El presente material aborda el uso de un método que aparece muy frecuentemente en la literatura denominado AHP. Fue creado por Tomas L. Saaty y dentro de sus múltiples posibilidades de uso, la más destacada es el ranking de alternativas o la selección de una de ellas. Se ha concebido como paso previo al desarrollo de un gemelo digital y posteriormente la implementación de una sombra digital de una empresa pequeña del centro oeste argentino. parap poder definir la tecnología de la sombra digital se han contemplado tres alternativas (A1-un sistema SAP, A2-Oddo CRM, A3-Un BSC con Power BI) Para realizar este proceso se parte del método de los factores ponderales y a partir de crear un vector de pesos relativos de los criterios de selección se confronta el desempeño de cada alternativa se obtiene un vector columna de ranking que muestra el desempeño de las alternativas A frente de los diferentes escenarios.
En el método de Saaty se agrega un interesante procedimiento basado en el autovector y autovalor de las matrices que intervienen para calcular el grado de inconsistencia del debate de los decisores. Esto elimina el sesgo que los decisores o metodólogos pudiesen inducir en el método de los factores ponderales.
La excelencia operacional es un enfoque que busca la mejora continua en todos los aspectos de un negocio para lograr una ejecución más eficiente y consistente que la competencia. Esto implica optimizar procesos, reducir costos, mejorar la calidad y fomentar una cultura de innovación. Los gemelos digitales, por su parte, son réplicas virtuales de un objeto o sistema físico que se actualizan en tiempo real con datos de sensores. Su función es simular, analizar y optimizar el rendimiento del sistema real.
La relación entre ambos conceptos, AHP y EO, es directa y simbiótica. Los gemelos digitales se convierten en una herramienta crucial para alcanzar la excelencia operacional. Permiten simular escenarios, identificar cuellos de botella, prever fallos y evaluar el impacto de diferentes decisiones antes de implementarlas en el mundo físico. Esta capacidad de análisis y predicción en tiempo real es clave para la mejora continua y la optimización de procesos, que son los pilares de la excelencia operacional.
En este contexto, el método de Tomás Saaty (Proceso de Jerarquía Analítica o AHP) podría utilizarse como una herramienta para la concepción de alternativas para el gemelo digital. El AHP es un método de toma de decisiones multicriterio que ayuda a estructurar un problema complejo en una jerarquía de objetivos, criterios y alternativas. Se basa en comparaciones por pares para asignar pesos y prioridades a cada elemento.
Este documento implementa la metodología AHP (Analytic Hierarchy Process) desarrollada por Thomas Saaty para la toma de decisiones multicriterio. Analizaremos 3 alternativas usando 5 criterios de selección. Estos han surgido de un estudio previo basado en el benchmarking de Excelencia Operacional de un sector empresarios que incluye una cadena de abastecimiento que abarca una producción primaria (cosecha), manufactura, logística y distribución y finalmente logística reversa de envases junto a disposición final de residuos (pasivado para relleno sanitario o fertilizante) o uso como insumo en otras cadenas de valor (economía circular)
Objetivo: Seleccionar la mejor alternativa de inversión tecnológica para la empresa mencionada que tiene como fortaleza el “preparedness” para la transformación digital
Alternativas:
Criterios de Evaluación:
# Cargar librerías necesarias
library(knitr)
library(ggplot2)
library(dplyr)
library(reshape2)
library(RColorBrewer)
# Función para calcular el vector propio principal
calcular_vector_propio <- function(matriz) {
eigenvalues <- eigen(matriz)
max_index <- which.max(eigenvalues$values)
vector_propio <- abs(eigenvalues$vectors[, max_index])
return(vector_propio / sum(vector_propio))
}
# Función para calcular el Índice de Consistencia (CI)
calcular_CI <- function(matriz) {
n <- nrow(matriz)
eigenvalues <- eigen(matriz)$values
lambda_max <- max(Re(eigenvalues))
CI <- (lambda_max - n) / (n - 1)
return(CI)
}
# Función para calcular el Ratio de Consistencia (CR)
calcular_CR <- function(CI, n) {
# Índices de consistencia aleatoria según Saaty
RI <- c(0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.49)
if (n <= 10) {
CR <- CI / RI[n]
} else {
CR <- NA
}
return(CR)
}
# Matriz de comparación por pares de criterios (5x5)
# Escala de Saaty: 1=igual, 3=moderadamente más importante, 5=fuertemente más importante,
# 7=muy fuertemente más importante, 9=extremadamente más importante
criterios_matriz <- matrix(c(
1, 1/3, 1/2, 2, 1/4, # C1: Costo
3, 1, 2, 3, 1/2, # C2: Facilidad de uso
2, 1/2, 1, 3, 1/3, # C3: Funcionalidad
1/2, 1/3, 1/3, 1, 1/5, # C4: Soporte técnico
4, 2, 3, 5, 1 # C5: Escalabilidad
), nrow = 5, byrow = TRUE)
rownames(criterios_matriz) <- c("Costo", "Facilidad_uso", "Funcionalidad", "Soporte", "Escalabilidad")
colnames(criterios_matriz) <- c("Costo", "Facilidad_uso", "Funcionalidad", "Soporte", "Escalabilidad")
print("Matriz de Comparación de Criterios:")
[1] "Matriz de Comparación de Criterios:"
| Costo | Facilidad_uso | Funcionalidad | Soporte | Escalabilidad | |
|---|---|---|---|---|---|
| Costo | 1.0 | 0.333 | 0.500 | 2 | 0.250 |
| Facilidad_uso | 3.0 | 1.000 | 2.000 | 3 | 0.500 |
| Funcionalidad | 2.0 | 0.500 | 1.000 | 3 | 0.333 |
| Soporte | 0.5 | 0.333 | 0.333 | 1 | 0.200 |
| Escalabilidad | 4.0 | 2.000 | 3.000 | 5 | 1.000 |
# Calcular vector propio (pesos de criterios)
pesos_criterios <- calcular_vector_propio(criterios_matriz)
names(pesos_criterios) <- rownames(criterios_matriz)
# Calcular consistencia
CI_criterios <- calcular_CI(criterios_matriz)
CR_criterios <- calcular_CR(CI_criterios, 5)
print("Pesos de los Criterios:")
[1] "Pesos de los Criterios:"
kable(data.frame(
Criterio = names(pesos_criterios),
Peso = round(pesos_criterios, 4),
Porcentaje = paste0(round(pesos_criterios * 100, 2), "%")
))
| Criterio | Peso | Porcentaje | |
|---|---|---|---|
| Costo | Costo | 0.0987 | 9.87% |
| Facilidad_uso | Facilidad_uso | 0.2521 | 25.21% |
| Funcionalidad | Funcionalidad | 0.1623 | 16.23% |
| Soporte | Soporte | 0.0665 | 6.65% |
| Escalabilidad | Escalabilidad | 0.4205 | 42.05% |
[1] "Índice de Consistencia (CI): 0.0215"
[1] "Ratio de Consistencia (CR): 0.0192"
if (CR_criterios < 0.1) {
print("✓ La matriz de criterios es consistente (CR < 0.10)")
} else {
print("la matriz de criterios presenta inconsistencias (CR ≥ 0.10)")
}
[1] "✓ La matriz de criterios es consistente (CR < 0.10)"
# Matriz de comparación de alternativas respecto al costo
# (menor costo es mejor)
costo_matriz <- matrix(c(
1, 1/2, 3, # ERP vs CRM, ERP vs BI
2, 1, 5, # CRM vs ERP, CRM vs BI
1/3, 1/5, 1 # BI vs ERP, BI vs CRM
), nrow = 3, byrow = TRUE)
rownames(costo_matriz) <- c("ERP", "CRM", "BI")
colnames(costo_matriz) <- c("ERP", "CRM", "BI")
pesos_costo <- calcular_vector_propio(costo_matriz)
names(pesos_costo) <- rownames(costo_matriz)
print("Matriz - Costo de Implementación:")
[1] "Matriz - Costo de Implementación:"
| ERP | CRM | BI | |
|---|---|---|---|
| ERP | 1.000 | 0.5 | 3 |
| CRM | 2.000 | 1.0 | 5 |
| BI | 0.333 | 0.2 | 1 |
print("Pesos para Costo:")
[1] "Pesos para Costo:"
kable(data.frame(Alternativa = names(pesos_costo), Peso = round(pesos_costo, 4)))
| Alternativa | Peso | |
|---|---|---|
| ERP | ERP | 0.3090 |
| CRM | CRM | 0.5816 |
| BI | BI | 0.1095 |
facilidad_matriz <- matrix(c(
1, 1/3, 1/2, # ERP vs CRM, ERP vs BI
3, 1, 2, # CRM vs ERP, CRM vs BI
2, 1/2, 1 # BI vs ERP, BI vs CRM
), nrow = 3, byrow = TRUE)
rownames(facilidad_matriz) <- c("ERP", "CRM", "BI")
colnames(facilidad_matriz) <- c("ERP", "CRM", "BI")
pesos_facilidad <- calcular_vector_propio(facilidad_matriz)
names(pesos_facilidad) <- rownames(facilidad_matriz)
print("Matriz - Facilidad de Uso:")
[1] "Matriz - Facilidad de Uso:"
| ERP | CRM | BI | |
|---|---|---|---|
| ERP | 1 | 0.333 | 0.5 |
| CRM | 3 | 1.000 | 2.0 |
| BI | 2 | 0.500 | 1.0 |
print("Pesos para Facilidad de Uso:")
[1] "Pesos para Facilidad de Uso:"
kable(data.frame(Alternativa = names(pesos_facilidad), Peso = round(pesos_facilidad, 4)))
| Alternativa | Peso | |
|---|---|---|
| ERP | ERP | 0.1634 |
| CRM | CRM | 0.5396 |
| BI | BI | 0.2970 |
funcionalidad_matriz <- matrix(c(
1, 2, 1/2, # ERP vs CRM, ERP vs BI
1/2, 1, 1/3, # CRM vs ERP, CRM vs BI
2, 3, 1 # BI vs ERP, BI vs CRM
), nrow = 3, byrow = TRUE)
rownames(funcionalidad_matriz) <- c("ERP", "CRM", "BI")
colnames(funcionalidad_matriz) <- c("ERP", "CRM", "BI")
pesos_funcionalidad <- calcular_vector_propio(funcionalidad_matriz)
names(pesos_funcionalidad) <- rownames(funcionalidad_matriz)
print("Matriz - Funcionalidad:")
[1] "Matriz - Funcionalidad:"
| ERP | CRM | BI | |
|---|---|---|---|
| ERP | 1.0 | 2 | 0.500 |
| CRM | 0.5 | 1 | 0.333 |
| BI | 2.0 | 3 | 1.000 |
print("Pesos para Funcionalidad:")
[1] "Pesos para Funcionalidad:"
kable(data.frame(Alternativa = names(pesos_funcionalidad), Peso = round(pesos_funcionalidad, 4)))
| Alternativa | Peso | |
|---|---|---|
| ERP | ERP | 0.2970 |
| CRM | CRM | 0.1634 |
| BI | BI | 0.5396 |
soporte_matriz <- matrix(c(
1, 3, 2, # ERP vs CRM, ERP vs BI
1/3, 1, 1/2, # CRM vs ERP, CRM vs BI
1/2, 2, 1 # BI vs ERP, BI vs CRM
), nrow = 3, byrow = TRUE)
rownames(soporte_matriz) <- c("ERP", "CRM", "BI")
colnames(soporte_matriz) <- c("ERP", "CRM", "BI")
pesos_soporte <- calcular_vector_propio(soporte_matriz)
names(pesos_soporte) <- rownames(soporte_matriz)
print("Matriz - Soporte Técnico:")
[1] "Matriz - Soporte Técnico:"
| ERP | CRM | BI | |
|---|---|---|---|
| ERP | 1.000 | 3 | 2.0 |
| CRM | 0.333 | 1 | 0.5 |
| BI | 0.500 | 2 | 1.0 |
print("Pesos para Soporte Técnico:")
[1] "Pesos para Soporte Técnico:"
kable(data.frame(Alternativa = names(pesos_soporte), Peso = round(pesos_soporte, 4)))
| Alternativa | Peso | |
|---|---|---|
| ERP | ERP | 0.5396 |
| CRM | CRM | 0.1634 |
| BI | BI | 0.2970 |
escalabilidad_matriz <- matrix(c(
1, 1/2, 2, # ERP vs CRM, ERP vs BI
2, 1, 4, # CRM vs ERP, CRM vs BI
1/2, 1/4, 1 # BI vs ERP, BI vs CRM
), nrow = 3, byrow = TRUE)
rownames(escalabilidad_matriz) <- c("ERP", "CRM", "BI")
colnames(escalabilidad_matriz) <- c("ERP", "CRM", "BI")
pesos_escalabilidad <- calcular_vector_propio(escalabilidad_matriz)
names(pesos_escalabilidad) <- rownames(escalabilidad_matriz)
print("Matriz - Escalabilidad:")
[1] "Matriz - Escalabilidad:"
| ERP | CRM | BI | |
|---|---|---|---|
| ERP | 1.0 | 0.50 | 2 |
| CRM | 2.0 | 1.00 | 4 |
| BI | 0.5 | 0.25 | 1 |
print("Pesos para Escalabilidad:")
[1] "Pesos para Escalabilidad:"
kable(data.frame(Alternativa = names(pesos_escalabilidad), Peso = round(pesos_escalabilidad, 4)))
| Alternativa | Peso | |
|---|---|---|
| ERP | ERP | 0.2857 |
| CRM | CRM | 0.5714 |
| BI | BI | 0.1429 |
# Crear matriz de decisión
matriz_decision <- cbind(pesos_costo, pesos_facilidad, pesos_funcionalidad, pesos_soporte, pesos_escalabilidad)
colnames(matriz_decision) <- c("Costo", "Facilidad_uso", "Funcionalidad", "Soporte", "Escalabilidad")
print("Matriz de Decisión (Pesos de alternativas por criterio):")
[1] "Matriz de Decisión (Pesos de alternativas por criterio):"
| Costo | Facilidad_uso | Funcionalidad | Soporte | Escalabilidad | |
|---|---|---|---|---|---|
| ERP | 0.3090 | 0.1634 | 0.2970 | 0.5396 | 0.2857 |
| CRM | 0.5816 | 0.5396 | 0.1634 | 0.1634 | 0.5714 |
| BI | 0.1095 | 0.2970 | 0.5396 | 0.2970 | 0.1429 |
# Calcular puntuaciones finales
puntuaciones_finales <- matriz_decision %*% pesos_criterios
puntuaciones_finales <- as.vector(puntuaciones_finales)
names(puntuaciones_finales) <- c("Sistema ERP", "Plataforma CRM", "Solución BI")
# Crear tabla de resultados
resultados <- data.frame(
Alternativa = names(puntuaciones_finales),
Puntuacion = round(puntuaciones_finales, 4),
Porcentaje = paste0(round(puntuaciones_finales * 100, 2), "%"),
Ranking = rank(-puntuaciones_finales)
)
print("RESULTADOS FINALES:")
[1] "RESULTADOS FINALES:"
| Alternativa | Puntuacion | Porcentaje | Ranking | |
|---|---|---|---|---|
| Plataforma CRM | Plataforma CRM | 0.4711 | 47.11% | 1 |
| Sistema ERP | Sistema ERP | 0.2759 | 27.59% | 2 |
| Solución BI | Solución BI | 0.2530 | 25.3% | 3 |
# Crear matriz detallada para análisis
contribuciones <- matriz_decision * matrix(rep(pesos_criterios, each = 3), nrow = 3)
rownames(contribuciones) <- c("Sistema ERP", "Plataforma CRM", "Solución BI")
colnames(contribuciones) <- c("Costo", "Facilidad de Uso", "Funcionalidad", "Soporte", "Escalabilidad")
print("Contribución de cada criterio a la puntuación final:")
[1] "Contribución de cada criterio a la puntuación final:"
| Costo | Facilidad de Uso | Funcionalidad | Soporte | Escalabilidad | |
|---|---|---|---|---|---|
| Sistema ERP | 0.0305 | 0.0412 | 0.0482 | 0.0359 | 0.1201 |
| Plataforma CRM | 0.0574 | 0.1361 | 0.0265 | 0.0109 | 0.2403 |
| Solución BI | 0.0108 | 0.0749 | 0.0876 | 0.0197 | 0.0601 |
# Preparar datos para visualización
datos_viz <- data.frame(
Alternativa = rep(names(puntuaciones_finales), 2),
Valor = c(puntuaciones_finales, puntuaciones_finales),
Tipo = rep(c("Puntuación Final", "Porcentaje"), each = 3),
Valor_Pct = c(puntuaciones_finales, puntuaciones_finales * 100)
)
# Gráfico de barras comparativo
p1 <- ggplot(datos_viz[datos_viz$Tipo == "Puntuación Final", ],
aes(x = reorder(Alternativa, -Valor), y = Valor, fill = Alternativa)) +
geom_bar(stat = "identity", alpha = 0.8, width = 0.6) +
geom_text(aes(label = paste0(round(Valor, 3), "\n(", round(Valor_Pct, 1), "%)")),
vjust = -0.5, size = 4, fontface = "bold") +
scale_fill_brewer(type = "qual", palette = "Set2") +
labs(title = "Comparación de Alternativas - Metodología AHP",
subtitle = paste("Ganador:", names(puntuaciones_finales)[which.max(puntuaciones_finales)]),
x = "Alternativas",
y = "Puntuación AHP",
caption = "Metodología: Analytic Hierarchy Process (Thomas Saaty)") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, size = 12, color = "darkgreen"),
axis.text.x = element_text(angle = 0, hjust = 0.5),
legend.position = "none") +
ylim(0, max(puntuaciones_finales) * 1.2)
print(p1)

# Preparar datos para gráfico de contribuciones
contrib_long <- melt(contribuciones)
colnames(contrib_long) <- c("Alternativa", "Criterio", "Contribucion")
# Gráfico de barras apiladas
p2 <- ggplot(contrib_long, aes(x = Alternativa, y = Contribucion, fill = Criterio)) +
geom_bar(stat = "identity", position = "stack") +
scale_fill_brewer(type = "qual", palette = "Set3") +
labs(title = "Contribución de cada Criterio por Alternativa",
subtitle = "Análisis de Sensibilidad - Metodología AHP",
x = "Alternativas",
y = "Contribución a la Puntuación Final",
fill = "Criterios") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, size = 12),
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "bottom") +
guides(fill = guide_legend(nrow = 1))
print(p2)

# Preparar datos para gráfico radar
radar_data <- as.data.frame(matriz_decision)
radar_data$Alternativa <- rownames(radar_data)
# Convertir a formato largo
radar_long <- melt(radar_data, id.vars = "Alternativa")
colnames(radar_long) <- c("Alternativa", "Criterio", "Valor")
# Gráfico de coordenadas polares (pseudo-radar)
p3 <- ggplot(radar_long, aes(x = Criterio, y = Valor, color = Alternativa, group = Alternativa)) +
geom_line(size = 1.2, alpha = 0.8) +
geom_point(size = 3, alpha = 0.9) +
coord_polar() +
scale_color_brewer(type = "qual", palette = "Dark2") +
labs(title = "Perfil de Alternativas por Criterio",
subtitle = "Gráfico Radar - Análisis Multicriterio AHP",
y = "Puntuación por Criterio") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
plot.subtitle = element_text(hjust = 0.5, size = 12),
axis.text.x = element_text(size = 10),
legend.position = "bottom")
print(p3)

mejor_alternativa <- names(puntuaciones_finales)[which.max(puntuaciones_finales)]
puntuacion_mejor <- max(puntuaciones_finales)
cat("========== RESUMEN EJECUTIVO ==========\n")
========== RESUMEN EJECUTIVO ==========
cat("Metodología: Analytic Hierarchy Process (AHP) - Thomas Saaty\n")
Metodología: Analytic Hierarchy Process (AHP) - Thomas Saaty
Criterios evaluados: 5
Alternativas analizadas: 3
cat("Consistencia de juicios: CR =", round(CR_criterios, 3),
ifelse(CR_criterios < 0.1, "(Aceptable)", "(Revisar)"), "\n\n")
Consistencia de juicios: CR = 0.019 (Aceptable)
cat("RECOMENDACIÓN:\n")
RECOMENDACIÓN:
cat("La mejor alternativa es:", mejor_alternativa, "\n")
La mejor alternativa es: Plataforma CRM
cat("Puntuación AHP:", round(puntuacion_mejor, 4),
paste0("(", round(puntuacion_mejor * 100, 1), "%)"), "\n\n")
Puntuación AHP: 0.4711 (47.1%)
cat("RANKING COMPLETO:\n")
RANKING COMPLETO:
for(i in order(-puntuaciones_finales)) {
cat(paste0(which(order(-puntuaciones_finales) == i), "° ",
names(puntuaciones_finales)[i], ": ",
round(puntuaciones_finales[i], 4),
" (", round(puntuaciones_finales[i] * 100, 1), "%)\n"))
}
1° Plataforma CRM: 0.4711 (47.1%)
2° Sistema ERP: 0.2759 (27.6%)
3° Solución BI: 0.253 (25.3%)
cat("\nCRITERIO MÁS IMPORTANTE:", names(pesos_criterios)[which.max(pesos_criterios)],
paste0("(", round(max(pesos_criterios) * 100, 1), "%)"))
CRITERIO MÁS IMPORTANTE: Escalabilidad (42%)
Nota metodológica: Este análisis implementa la metodología AHP desarrollada por Thomas Saaty, utilizando comparaciones por pares con la escala fundamental de 1-9 y verificando la consistencia mediante el Ratio de Consistencia (CR). Se recomienda que CR < 0.10 para considerar los juicios como consistentes.
** link to https://rpubs.com/ricardorpama/1338567**
cite: Palma, R. R. (2025). Excelencia Operacional Bases para un framework de estrategia de manufactura. Zenodo. https://doi.org/10.5281/zenodo.16945306