En los proyectos de implementación y personalización de Odoo ERP, el equipo de Mesa de Ayuda de una empresa colombiana Gold Partner de Odoo gestiona diariamente tickets que abarcan desde pequeñas configuraciones hasta desarrollos técnicos complejos en Python y OWL/JavaScript.
Cada ticket recibe una estimación de esfuerzo en puntos de complejidad siguiendo la escala Fibonacci {1, 2, 3, 5, 8, 13}, metodología adoptada en los procesos de planning poker del equipo. La pregunta estratégica que motiva este análisis es:
¿La complejidad estimada de un ticket —medida en puntos de esfuerzo Fibonacci— tiene una relación significativa con su costo real de resolución? ¿Es posible construir un modelo que prediga ese costo?
Responder esta pregunta tiene implicaciones directas en la estimación de presupuestos, la fijación de precios de servicios y la asignación eficiente de recursos en los proyectos de personalización Odoo.
H₀ (Hipótesis nula): No existe correlación significativa entre la complejidad del ticket y su costo de resolución (\(\rho = 0\)).
H₁ (Hipótesis alternativa): Existe una correlación positiva significativa entre la complejidad del ticket y su costo de resolución (\(\rho > 0\)).
Nivel de significancia: \(\alpha = 0.05\)
| Rol | Variable | Descripción | Escala |
|---|---|---|---|
| Independiente (\(X\)) | complejidad |
Puntos de esfuerzo asignados en planning poker | Fibonacci {1,2,3,5,8,13} |
| Dependiente (\(Y\)) | costo_ticket |
Costo total de resolución del ticket | USD (continua) |
El dataset odoo_tickets_1000.csv
contiene información de 1.000 tickets del área de Mesa
de Ayuda. Las 10 variables registradas son:
df = read.csv("odoo_tickets_1000.csv", stringsAsFactors = FALSE)
cat("Dimensiones:", nrow(df), "filas x", ncol(df), "columnas\n")## Dimensiones: 1000 filas x 10 columnas
## Sin valores faltantes en el dataset.
vars_num = c("complejidad","horas_reales","bugs_reportados","retrabajos",
"satisfaccion","experiencia_dev","horas_qa",
"n_revisiones","costo_ticket","dias_resolucion")
resumen = data.frame(
Variable = vars_num,
n = sapply(df[vars_num], function(x) sum(!is.na(x))),
Media = sapply(df[vars_num], function(x) round(mean(x, na.rm=TRUE), 2)),
Mediana = sapply(df[vars_num], function(x) round(median(x, na.rm=TRUE), 2)),
DE = sapply(df[vars_num], function(x) round(sd(x, na.rm=TRUE), 2)),
Min = sapply(df[vars_num], function(x) round(min(x, na.rm=TRUE), 2)),
Max = sapply(df[vars_num], function(x) round(max(x, na.rm=TRUE), 2))
)
resumencomplejidad (Puntos de Esfuerzo Fibonacci)freq_comp = as.data.frame(table(df$complejidad))
names(freq_comp) = c("Puntos", "Frecuencia")
freq_comp$Porcentaje = round(freq_comp$Frecuencia / nrow(df) * 100, 1)
freq_compggplot(df, aes(x = factor(complejidad))) +
geom_bar(aes(y = after_stat(count) / sum(after_stat(count))),
fill = "#3B82F6", color = "white", alpha = 0.85, width = 0.6) +
geom_text(aes(y = after_stat(count) / sum(after_stat(count)),
label = paste0(after_stat(count), "\n(",
round(after_stat(count)/nrow(df)*100,1), "%)")),
stat = "count", vjust = -0.3, size = 3.5) +
labs(title = "Distribucion de Complejidad - Escala Fibonacci",
subtitle = "Frecuencia relativa por nivel de esfuerzo",
x = "Puntos de Esfuerzo (Fibonacci)", y = "Proporcion") +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Distribución de complejidad — escala Fibonacci
Análisis: La distribución es discreta y asimétrica. Los niveles 2, 3 y 5 concentran el 73.5% de los tickets, coherente con un equipo donde predominan desarrollos de mediana dificultad. Los tickets de 13 puntos (4.6%) corresponden a integraciones complejas o migraciones técnicas de alta envergadura.
par(mfrow = c(1, 2))
hist(df$complejidad,
main = "Histograma - Complejidad",
xlab = "Puntos de Esfuerzo",
col = "#BFDBFE", border = "white", freq = FALSE)
curve(dnorm(x, mean = mean(df$complejidad), sd = sd(df$complejidad)),
col = "#1D4ED8", lwd = 2, add = TRUE)
qqnorm(df$complejidad, main = "Q-Q Plot - Complejidad",
col = "#3B82F6", pch = 19, cex = 0.5)
qqline(df$complejidad, col = "#DC2626", lwd = 2)Histograma y Q-Q Plot — complejidad
Análisis: El histograma evidencia una distribución con múltiples picos discretos, completamente alejada de la campana normal teórica. El Q-Q Plot confirma desviaciones marcadas de la línea de referencia en todos los puntos, por los saltos discontinuos propios de la escala Fibonacci.
sw_comp = shapiro.test(df$complejidad)
cat(sprintf(
"Estadistico W: %.6f\n p-valor: %.2e\n Decision: %s\n",
sw_comp$statistic,
sw_comp$p.value,
ifelse(sw_comp$p.value < 0.05,
"RECHAZA H0 - NO sigue distribucion normal",
"No rechaza H0 - Compatible con normalidad")
))## Estadistico W: 0.819201
## p-valor: 4.50e-32
## Decision: RECHAZA H0 - NO sigue distribucion normal
Análisis: Con \(p = 4.5
\times 10^{-32} \ll \alpha = 0.05\), se rechaza la normalidad de
forma contundente. complejidad no sigue
distribución normal. Este resultado es determinante para la
elección del método de correlación: descarta Pearson y exige un enfoque
no paramétrico.
costo_ticket (Costo del Ticket en USD)ggplot(df, aes(x = costo_ticket)) +
geom_histogram(aes(y = after_stat(density)), bins = 30,
fill = "#10B981", color = "white", alpha = 0.80) +
geom_density(color = "#065F46", linewidth = 1.2) +
stat_function(fun = dnorm,
args = list(mean = mean(df$costo_ticket),
sd = sd(df$costo_ticket)),
color = "#DC2626", linetype = "dashed", linewidth = 1) +
labs(title = "Distribucion de Costo del Ticket (USD)",
subtitle = "Verde: densidad empirica | Rojo discontinuo: normal teorica",
x = "Costo del Ticket (USD)", y = "Densidad") +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Distribución del costo del ticket
Análisis: La distribución del costo muestra una forma asimétrica con sesgo positivo (hacia la derecha), producto de la mezcla de tickets de baja complejidad (costos bajos) y alta complejidad (costos elevados). La curva normal teórica (línea roja discontinua) no se ajusta a la distribución empírica en ninguna región, evidenciando visualmente la no normalidad de la variable.
sw_costo = shapiro.test(df$costo_ticket)
cat(
sprintf(" Estadistico W: %.6f\n", sw_costo$statistic),
sprintf(" p-valor: %.2e\n", sw_costo$p.value),
sprintf(" Decision: %s\n",
ifelse(sw_costo$p.value < 0.05,
"RECHAZA H0 - NO sigue distribucion normal",
"No rechaza H0 - Compatible con normalidad"))
)## Estadistico W: 0.939153
## p-valor: 8.39e-20
## Decision: RECHAZA H0 - NO sigue distribucion normal
Análisis: \(p = 8.39
\times 10^{-20} < 0.05\). costo_ticket tampoco
sigue distribución normal. La distribución refleja la estructura del
negocio: los costos están condicionados por el nivel de complejidad,
generando una forma multimodal con picos correspondientes a cada nivel
Fibonacci. El incumplimiento de normalidad en ambas variables
confirma que los métodos no paramétricos son los
apropiados.
complejidad y costo_ticketggplot(df, aes(x = complejidad, y = costo_ticket)) +
geom_jitter(color = "#6366F1", alpha = 0.25, size = 1,
width = 0.15, height = 0) +
geom_smooth(method = "lm", color = "#DC2626", fill = "#FCA5A5",
linewidth = 1.2, se = TRUE) +
labs(title = "Dispersion: Complejidad vs. Costo del Ticket",
subtitle = "Cada punto es un ticket | Banda roja: IC 95%",
x = "Complejidad (Puntos Fibonacci)",
y = "Costo del Ticket (USD)") +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Dispersión: complejidad vs costo_ticket con recta de tendencia OLS
Análisis: La nube de puntos muestra una tendencia positiva clara y consistente: a mayor complejidad, mayor costo del ticket. La dispersión crece con el nivel de complejidad, comportamiento típico en proyectos de software donde los tickets de mayor esfuerzo tienen mayor incertidumbre en el costo final. La tendencia lineal captura bien el patrón central.
Dado que ambas variables incumplen el supuesto de normalidad
(Shapiro-Wilk rechazó \(H_0\) en ambos
casos) y complejidad es de naturaleza ordinal discreta en
escala Fibonacci, el método estadísticamente apropiado es el coeficiente
de Spearman (\(\rho_s\)). Mide la asociación monótona
entre los rangos de los datos, sin exigir normalidad ni relación lineal
estricta.
| Método | Aplica | Razon |
|---|---|---|
| Pearson (\(r\)) | No optimo | Requiere normalidad bivariada — no cumplida |
| Spearman (\(\rho_s\)) | Recomendado | No parametrico, robusto ante no normalidad |
\[H_0: \rho_s = 0 \qquad H_1: \rho_s \neq 0\]
##
## Spearman's rank correlation rho
##
## data: df$complejidad and df$costo_ticket
## S = 32256825, p-value < 2.2e-16
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
## rho
## 0.8064589
cat(
sprintf("\n rho: %.4f\n", cor_s$estimate),
sprintf(" S: %.2f\n", cor_s$statistic),
sprintf(" p-valor: %.2e\n", cor_s$p.value),
sprintf(" Decision (a=0.05): %s\n",
ifelse(cor_s$p.value < 0.05,
"RECHAZA H0 - Existe correlacion positiva significativa",
"No rechaza H0"))
)##
## rho: 0.8065
## S: 32256825.21
## p-valor: 5.66e-230
## Decision (a=0.05): RECHAZA H0 - Existe correlacion positiva significativa
Análisis: \(\rho_s = 0.8065\) con \(p\text{-valor} = 5.66 \times 10^{-230}\) se rechaza \(H_0\). Existe una correlación positiva fuerte y estadísticamente significativa entre la complejidad del ticket y su costo de resolución. Los puntos de esfuerzo asignados en el planning no son solo una estimación subjetiva: tienen poder predictivo real sobre el costo que genera cada ticket. Esto valida el proceso de estimación del equipo y justifica la construcción de un modelo predictivo de costos.
Para comprender la estructura completa de relaciones antes de definir el modelo, se calcula la matriz de correlación de Spearman sobre todas las variables numéricas.
mat_cor = cor(df[, vars_num], method = "spearman", use = "complete.obs")
corrplot(mat_cor,
method = "color",
type = "upper",
order = "hclust",
addCoef.col = "black",
number.cex = 0.65,
tl.col = "black",
tl.srt = 45,
tl.cex = 0.85,
col = colorRampPalette(c("#DC2626","white","#1D4ED8"))(200),
title = "Matriz de Correlacion de Spearman",
mar = c(0, 0, 2, 0))Matriz de correlacion de Spearman — todas las variables
Análisis de la matriz: Tres hallazgos clave guían la construcción del modelo:
complejidad correlaciona fuertemente con
costo_ticket (\(\rho_s =
0.81\)) — confirma la hipótesis — pero también con
horas_reales (\(\rho_s =
0.82\)) y horas_qa (\(\rho_s = 0.69\)). Esto indica que
complejidad actúa como un predictor upstream: su
efecto sobre el costo viaja a través de las horas que genera.
horas_reales (\(\rho_s =
0.97\)) y horas_qa (\(\rho_s = 0.90\)) presentan las
correlaciones más fuertes con costo_ticket, siendo las
variables más directamente vinculadas al costo real.
experiencia_dev muestra correlación prácticamente
nula con complejidad (\(\rho_s =
-0.05\)), lo que indica que es una dimensión
independiente y complementaria que puede aportar
información única al modelo sin redundancia.
La variable dependiente costo_ticket es
continua (medida en USD), por lo que la
regresión lineal es el marco apropiado. La matriz de
correlación reveló que el costo del ticket es un fenómeno
multifactorial: la complejidad genera horas de desarrollo y horas de QA,
e involucra la experiencia del desarrollador. Estas tres dimensiones
medibles y directamente registradas en el sistema explican el costo de
forma precisa y completa.
Los predictores seleccionados son horas_reales,
experiencia_dev y horas_qa:
horas_reales (\(\rho_s = 0.97\) con
costo_ticket): el predictor más fuerte. Las horas reales de
desarrollo son el insumo más directo del costo.horas_qa (\(\rho_s = 0.90\) con
costo_ticket): las horas de pruebas tienen un peso
equivalente en la estructura de costos del proyecto.experiencia_dev: aporta información
genuinamente independiente sin redundancia, capturando el efecto de la
tarifa del desarrollador sobre el costo.Nota sobre complejidad: La hipótesis
quedó respondida en la sección de correlación: \(\rho_s = 0.81\), \(H_0\) rechazada. En el modelo predictivo,
el efecto de complejidad queda contenido en horas_reales y
horas_qa: la complejidad determina cuántas horas
genera un ticket, y las horas determinan el costo. El modelo
captura esta cadena causal de forma más directa y precisa.
##
## Call:
## lm(formula = costo_ticket ~ horas_reales + experiencia_dev +
## horas_qa, data = df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -100.379 -24.140 0.374 21.821 138.252
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -63.0498 3.4788 -18.12 <2e-16 ***
## horas_reales 47.6042 0.2347 202.82 <2e-16 ***
## experiencia_dev 10.5133 0.3383 31.08 <2e-16 ***
## horas_qa 47.1697 0.5047 93.46 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 35.31 on 996 degrees of freedom
## Multiple R-squared: 0.9965, Adjusted R-squared: 0.9965
## F-statistic: 9.506e+04 on 3 and 996 DF, p-value: < 2.2e-16
## (Intercept) horas_reales experiencia_dev horas_qa
## -63.0498 47.6042 10.5133 47.1697
cat(sprintf(
"\n costo_ticket = %.4f + %.4f*horas_reales + %.4f*experiencia_dev + %.4f*horas_qa\n",
coefs[1], coefs[2], coefs[3], coefs[4]
))##
## costo_ticket = -63.0498 + 47.6042*horas_reales + 10.5133*experiencia_dev + 47.1697*horas_qa
La ecuación del modelo ajustado es:
\[\widehat{\text{costo_ticket}} = -63.05 + 47.60 \cdot \text{horas_reales} + 10.51 \cdot \text{experiencia_dev} + 47.17 \cdot \text{horas_qa}\]
Interpretación de los coeficientes:
Intercepto (\(\beta_0 = -63.05\)): Constante de ajuste matemático. Sin interpretación práctica en este contexto.
horas_reales (\(\beta_1 = 47.60\)): Manteniendo
las demás variables constantes, cada hora adicional de desarrollo
incrementa el costo del ticket en promedio USD
47.60.
experiencia_dev (\(\beta_2 = 10.51\)): A igual carga
de horas, cada año adicional de experiencia del desarrollador incrementa
el costo en promedio USD 10.51. Los desarrolladores más
senior tienen tarifas más altas, lo que se refleja directamente en el
costo del ticket.
horas_qa (\(\beta_3 = 47.17\)): Cada hora
adicional de pruebas QA incrementa el costo en promedio USD
47.17, prácticamente equivalente al costo por hora de
desarrollo, lo que refleja que el proceso de QA tiene un peso
proporcional en la estructura de costos del equipo.
r2 = round(summary(modelo)$r.squared, 4)
r2_adj = round(summary(modelo)$adj.r.squared, 4)
f_stat = round(summary(modelo)$fstatistic[1], 2)
f_pval = pf(summary(modelo)$fstatistic[1],
summary(modelo)$fstatistic[2],
summary(modelo)$fstatistic[3],
lower.tail = FALSE)
cat(sprintf(" R2: %.4f\n", r2))## R2: 0.9965
## R2 ajustado: 0.9965
## F-estadistico: 95058.02
## p-valor (F): 0.0000e+00
Análisis:
\(R^2 = 0.9965\): El modelo explica el 99.65% de la variabilidad del costo. Prácticamente todo el costo de un ticket queda determinado por las tres variables del modelo. Solo el 0.35% restante obedece a factores no capturados.
\(F = 95058.02\), \(p < 0.001\): El modelo es globalmente significativo. Los tres predictores en conjunto explican el costo de forma real, sistemática y no atribuible al azar.
Todos los predictores son significativos (\(p < 2 \times 10^{-16}\)), confirmando que cada variable aporta información estadísticamente valiosa e independiente.
tabla_coefs = as.data.frame(summary(modelo)$coefficients)
tabla_coefs$Variable = rownames(tabla_coefs)
tabla_coefs$Significativo = ifelse(tabla_coefs[, 4] < 0.05, "Si", "No")
tabla_coefs <- tabla_coefs[, c("Variable","Estimate","Std. Error",
"t value","Pr(>|t|)","Significativo")]
tabla_coefs[, 2:5] = round(tabla_coefs[, 2:5], 4)
tabla_coefsLos tres predictores son estadísticamente significativos con \(p < 2 \times 10^{-16}\), confirmando que cada uno aporta información única e independiente para predecir el costo del ticket.
Análisis: La tabla ANOVA confirma que los tres predictores contribuyen de forma individual y significativa (\(p \approx 0\) en los tres casos):
horas_reales es el predictor dominante
(mayor \(F = 275.578\)), explicando la
mayor proporción de la varianza del costo.horas_qa aporta la segunda mayor
contribución (\(F = 8.734\)),
reflejando el peso del proceso de pruebas en la estructura de
costos.experiencia_dev contribuye de forma
independiente (\(F = 860\)), capturando
el efecto de la tarifa del desarrollador sobre el costo total.Para que el estimador sea el Mejor Estimador Lineal Insesgado y para que la inferencia paramétrica sea válida, los residuos del modelo deben cumplir los mismos cinco supuestos evaluados en el modelo simple.
El modelo postula que la relación entre horas_reales, experiencia_dev y horas_qa con costo_ticket es estrictamente lineal.
Se evalúa visualmente: si los residuos se distribuyen de forma aleatoria alrededor de la línea horizontal en cero, sin ningún patrón sistemático, el supuesto se cumple. La presencia de curvaturas o patrones en forma de embudo indicaría una mala especificación del modelo.
plot(modelo, which = 1, col = "#6366F1", pch = 19, cex = 0.5,
main = "Residuos vs. Ajustados - Linealidad")Residuos vs. Ajustados — Supuesto de Linealidad
Análisis: La línea roja de tendencia es aproximadamente horizontal y los puntos se distribuyen de forma aleatoria alrededor de cero sin ningún patrón sistemático. No hay curvas ni formas de embudo. Se confirma el supuesto de linealidad: la especificación lineal es adecuada para este modelo.
El valor esperado de los residuos debe ser exactamente cero: E(εᵢ) = 0. Esto garantiza que el modelo no tiene sesgo.
residuos = residuals(modelo)
media_res = mean(residuos)
cat(sprintf(" Media aritmetica de los residuos: %.2e\n", media_res))## Media aritmetica de los residuos: -1.18e-15
## [1] TRUE
Análisis: La media de los residuos es \(-1.18 \times 10^{-15}\), un número
infinitesimal equivalente a cero (error de precisión computacional
estándar). La prueba all.equal confirma la equivalencia
algebraica. Se confirma \(E(\varepsilon_i) = 0\). El modelo
no presenta sesgo sistemático en sus predicciones.
La varianza de los residuos debe ser constante a lo largo de todo el rango de valores ajustados: Var(εᵢ) = σ².
bp = bptest(modelo)
cat(
sprintf(" BP estadistico: %.4f\n", bp$statistic),
sprintf(" p-valor: %.4e\n", bp$p.value),
sprintf(" Decision: %s\n",
ifelse(bp$p.value < 0.05,
"RECHAZA H0 - Heterocedasticidad detectada",
"No rechaza H0 - Homocedasticidad confirmada"))
)## BP estadistico: 19.4810
## p-valor: 2.1741e-04
## Decision: RECHAZA H0 - Heterocedasticidad detectada
Análisis: Con \(BP = 19.48\) y \(p = 2.17 \times 10^{-4} < 0.05\), se detecta heterocedasticidad: la varianza de los errores no es constante. Esto no invalida los coeficientes estimados (siguen siendo insesgados), pero sesga los errores estándar. Se aplica corrección con errores robustos de White (HC3):
##
## t test of coefficients:
##
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -63.04981 3.85251 -16.366 < 2.2e-16 ***
## horas_reales 47.60416 0.23714 200.746 < 2.2e-16 ***
## experiencia_dev 10.51331 0.37545 28.002 < 2.2e-16 ***
## horas_qa 47.16969 0.46795 100.802 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Análisis post-corrección: Tras aplicar los errores estándar robustos de White, los tres predictores del modelo mantienen su significancia estadística con p-valor < 2.2e-16 en los tres casos. Los coeficientes β̂ permanecen idénticos a los del modelo original (horas_reales = 47.60, experiencia_dev = 10.51, horas_qa = 47.17), confirmando que la heterocedasticidad no introdujo sesgo en la estimación. Lo que cambia son los errores estándar, que ahora son robustos y confiables. Se concluye que el modelo costo_ticket ~ horas_reales + experiencia_dev + horas_qa es estadísticamente válido, los p-valores son correctos y la inferencia sobre los coeficientes es confiable.
Los residuos no deben estar correlacionados entre sí: Cov(εᵢ, εⱼ) = 0 para todo i ≠ j. Se evalúa con la prueba de Durbin-Watson, cuyo estadístico toma valores entre 0 y 4: valores cercanos a 2 indican ausencia de autocorrelación, valores < 1.5 sugieren autocorrelación positiva y valores > 2.5 autocorrelación negativa.
## DW estadistico: 1.9604
## p-valor: 0.2663
cat(sprintf(" Decision: %s\n",
ifelse(dw$p.value < 0.05,
"RECHAZA H0 - Autocorrelacion detectada",
"No rechaza H0 - Errores independientes")))## Decision: No rechaza H0 - Errores independientes
Análisis: \(DW = 1.9604\), cercano al valor teórico ideal de 2, y \(p = 0.2663 > 0.05\). Se confirma la independencia de los residuos: el error de un ticket no contiene información sobre el error del siguiente. Cada observación es estadísticamente autónoma.
par(mfrow = c(1, 2))
qqnorm(residuos, main = "Q-Q Plot - Residuos",
col = "#6366F1", pch = 19, cex = 0.5)
qqline(residuos, col = "#DC2626", lwd = 2)
hist(residuos, main = "Histograma - Residuos",
xlab = "Residuos", col = "#C7D2FE",
border = "white", freq = FALSE)
curve(dnorm(x, mean = 0, sd = sd(residuos)),
col = "#4338CA", lwd = 2, add = TRUE)Q-Q Plot e Histograma de Residuos — Normalidad
par(mfrow = c(1, 1))
sw_res = shapiro.test(residuos)
cat(
sprintf(" W: %.6f\n", sw_res$statistic),
sprintf(" p-valor: %.4f\n", sw_res$p.value),
sprintf(" Decision: %s\n",
ifelse(sw_res$p.value < 0.05,
"RECHAZA H0 - Residuos NO normales",
"No rechaza H0 - Residuos normales confirmado"))
)## W: 0.998105
## p-valor: 0.3295
## Decision: No rechaza H0 - Residuos normales confirmado
Análisis: El Q-Q Plot muestra que los puntos se alinean con precisión sobre la diagonal en todo el rango central. El histograma exhibe una campana simétrica centrada en cero. Shapiro-Wilk confirma formalmente: \(W = 0.9981\), \(p = 0.3295 > 0.05\). Se confirma \(\varepsilon_i \sim N(0, \sigma^2)\), validando que los \(p\)-valores e intervalos de confianza del modelo son matemáticamente correctos y confiables.
El modelo cumple 4 de los 5 supuestos de Gauss-Markov. La heterocedasticidad fue corregida con errores estándar robustos (HC3), preservando la validez de la inferencia. Los coeficientes son insesgados, consistentes y la inferencia es estadísticamente válida.
La prueba de correlación de Spearman arrojó \(\rho_s = 0.8065\) con \(p = 5.66 \times 10^{-230}\). Se rechaza \(H_0\): existe una correlación positiva fuerte y estadísticamente significativa entre la complejidad del ticket y su costo de resolución. La escala Fibonacci utilizada en el planning tiene poder predictivo real sobre el costo que genera cada ticket, validando el proceso de estimación del equipo de Mesa de Ayuda.
\[\widehat{\text{costo_ticket}} = -63.05 + 47.60 \cdot \text{horas_reales} + 10.51 \cdot \text{experiencia_dev} + 47.17 \cdot \text{horas_qa}\]
Con \(R^2 = 0.9965\), el modelo explica el 99.65% de la variabilidad del costo. Los tres predictores son significativos, los supuestos se cumplen (con corrección por heterocedasticidad) y la inferencia es estadísticamente válida.
nuevo_ticket = data.frame(
Perfil = c("Ticket sencillo (2-3 pts)",
"Ticket mediano (5 pts)",
"Ticket complejo (8 pts)"),
horas_reales = c(8, 20, 38),
experiencia_dev = c(3, 6, 10),
horas_qa = c(4, 10, 16)
)
nuevo_ticket$costo_estimado_USD = round(
predict(modelo, nuevo_ticket[, c("horas_reales","experiencia_dev","horas_qa")]), 2)
nuevo_ticketEl modelo permite estimar el costo de un ticket en tiempo real a partir de tres variables registradas en el sistema. Esto abre la posibilidad de construir una función de estimación automática integrada directamente en el módulo Proyecto de Odoo, usando los datos de cada ticket como input del modelo, facilitando la toma de decisiones en planeación de sprints y fijación de precios de servicios.