Auditoría del beneficio de alimentación en campo
Caso ficticio. Fiorena Redes Elétricas S.A., sus bases, personas y registros no existen. Los datos se generaron desde cero con un script en R, con reglas de beneficio inventadas y defectos de tamaño conocido. El método es real.
Descargar el informe (PDF) · Descargar los casos a revisar (CSV)
Los comentarios del código R están en portugués, el idioma original del caso.
La pregunta
La contraloría de Fiorena vio que el valor pagado en alimentación por día de cuadrilla en campo subió de R$ 219 en el primer semestre de 2025 a R$ 239 en el segundo, un 9,5% más, sin reajuste de valores ni cambios en el tipo de servicio. La pregunta era simple: ¿cuánto de eso es gasto legítimo y cuánto se pagó fuera de la regla?
La caída de julio tiene una causa en los datos: julio y enero son los meses con más personas de vacaciones (en julio, 81 electricistas por día hábil, frente a 37 en la mediana del año), y cada cuadrilla salió a campo con 3,5 personas, frente a 3,8 en la mediana. El valor por persona en campo no bajó: se mantuvo entre R$ 58 y R$ 60 de enero a julio.
Cada electricista recibe en la tarjeta de alimentación una recarga por día en campo. La recarga la registra la base, la aprueba el supervisor y se paga sin cruce automático con el diario de campo ni con el registro de vacaciones y ausencias. Nadie había mirado juntos los 12 meses de registros.
La auditoría respondió tres preguntas, en este orden:
- ¿Cuánto se pagó fuera de una regla objetiva (duplicidad, por encima del tope, persona que no estaba trabajando)?
- ¿Cuánto se pagó sin respaldo: sin registro de la cuadrilla en campo ese día, o con una justificación que no explica nada?
- ¿Dónde se concentra, y por qué?
La regla y los datos
La regla del beneficio:
- Almuerzo de R$ 52 por día en campo.
- Cena de R$ 44, solo cuando el diario de campo registra pernocte de la cuadrilla.
- Extra de hasta R$ 24, solo con una de las justificaciones de la lista: “desplazamiento de más de 300 km” o “jornada extendida en más de 2 horas”.
- Tope de R$ 120 por persona por día, sumando todo, y como máximo una recarga de cada tipo por día.
- Solo para quien está activo, fuera de vacaciones y ausencias, y en una cuadrilla que estuvo en campo ese día.
Se auditaron 155.909 recargas de 2025, por un total de R$ 7,63 millones, pagadas a 649 personas de 8 bases, cruzadas con 33.254 registros de cuadrillas en campo en el diario, con el registro de personal y con las vacaciones y ausencias de RR. HH.
Método
Cada recarga pasa por las mismas verificaciones y termina en una de cuatro conclusiones. La auditoría separa lo que es un error objetivo, que se puede recuperar, de lo que solo merece una segunda mirada de la base; no saca conclusiones sobre la intención de nadie.
- Preparar. Usar el día trabajado, y no la fecha en que se registró la recarga, y vincular cada recarga con el registro de personal, las ausencias y la cuadrilla de la persona ese día.
- Revisar las reglas objetivas. Duplicidad, persona fuera de servicio, cena sin pernocte, extra sin justificación y suma del día por encima del tope. En el tope, solo el excedente cuenta como indebido.
- Revisar el respaldo. ¿La cuadrilla estaba en campo ese día? ¿La justificación del extra es válida, vaga o falta? Un diccionario de términos acepta errores de tipeo, para que “deslocamneto acima de 300km” (con error, en portugués) cuente como válida.
- Comparar con lo esperado. Para cada base y mes, el valor por día en campo se compara con el de las otras bases en el mismo mes. Queda fuera del rango esperado el grupo que se aleja mucho más de lo que las bases suelen alejarse entre sí (el criterio de Tukey, en el código).
- Puntuar el riesgo. Un punto por cada señal: regla violada, sin respaldo de campo, justificación vaga o ausente, base y mes por encima del rango.
- Concluir. Cada recarga recibe una de las cuatro conclusiones de abajo.
| Conclusión | Cuándo | Qué hacer |
|---|---|---|
| Indebido | Violó una regla objetiva | Revertir o recuperar, según la política de la empresa y con apoyo de RR. HH. y del área legal |
| Revisar | Ninguna regla violada, pero sin respaldo de campo o con dos señales o más | La base confirma o corrige en un plazo de 15 días |
| Atención | Una sola señal, sin regla violada | Entra en la muestra del mes siguiente |
| Conforme | Ninguna señal | Nada que hacer |
Indebido
Cuándo: Violó una regla objetiva
Qué hacer: Revertir o recuperar, según la política de la empresa y con apoyo de RR. HH. y del área legal
Revisar
Cuándo: Ninguna regla violada, pero sin respaldo de campo o con dos señales o más
Qué hacer: La base confirma o corrige en un plazo de 15 días
Atención
Cuándo: Una sola señal, sin regla violada
Qué hacer: Entra en la muestra del mes siguiente
Conforme
Cuándo: Ninguna señal
Qué hacer: Nada que hacer
Ver el código R de la etapa 1: preparar
# O que vale é o dia de referência (o dia trabalhado), e não a data em que a
# recarga foi lançada. Cada recarga é ligada ao cadastro, às ausências e à
# equipe da pessoa naquele dia no diário de campo.
recargas <- read_csv("dados/recargas.csv.gz", show_col_types = FALSE, na = "",
col_types = cols(justificativa = "c"))
diario <- read_csv("dados/diario_campo.csv", show_col_types = FALSE)
cadastro <- read_csv("dados/cadastro.csv", show_col_types = FALSE, na = "")
ausencias <- read_csv("dados/ausencias.csv", show_col_types = FALSE)
base_auditoria <- recargas |>
mutate(dia = dia_referencia) |>
left_join(cadastro |> select(matricula, equipe, admissao, desligamento), "matricula") |>
left_join(ausencias |> select(matricula, tipo_ausencia = tipo, inicio, fim),
join_by(matricula, between(dia, inicio, fim))) |>
left_join(diario |> transmute(equipe, dia = data, em_campo = TRUE, pernoite = pernoite == "sim"),
c("equipe", "dia")) |>
mutate(em_campo = coalesce(em_campo, FALSE))Ver el código R de la etapa 2: reglas objetivas
# Regras objetivas: cada violação pode ser cobrada. No teto, só o excedente
# conta como indevido; ele é calculado depois de tirar o que já é indevido por
# outra regra, para não contar o mesmo real duas vezes.
normalizar <- function(x) {
x <- tolower(iconv(coalesce(x, ""), to = "ASCII//TRANSLIT"))
x <- gsub("([0-9])\\s*km", "\\1 km", x)
x <- gsub("([0-9])\\s*h\\b", "\\1 horas", x)
trimws(gsub("[^a-z0-9 ]", " ", gsub("\\s+", " ", x)))
}
# Dicionário com tolerância a erro de digitação: a justificativa vale se
# estiver a até 15% de distância (em letras) de uma das frases da regra.
classificar_justificativa <- function(x) {
n <- normalizar(x)
validas <- normalizar(JUSTIFICATIVAS_VALIDAS)
dist <- adist(n, validas, partial = FALSE) / outer(nchar(n), nchar(validas), pmax)
case_when(n == "" ~ "ausente",
apply(dist, 1, min) <= 0.15 ~ "válida",
TRUE ~ "vaga")
}
# Cada texto diferente é classificado uma vez só.
textos <- unique(coalesce(base_auditoria$justificativa[base_auditoria$tipo == "Extra"], ""))
classes <- classificar_justificativa(textos)
auditada <- base_auditoria |>
mutate(justificativa_classe = if_else(tipo == "Extra", classes[match(coalesce(justificativa, ""), textos)], NA)) |>
arrange(matricula, dia, tipo, data_recarga, id) |>
group_by(matricula, dia, tipo) |>
mutate(duplicidade = row_number() > 1) |>
ungroup() |>
mutate(
fora_do_trabalho = dia < admissao | (!is.na(desligamento) & dia > desligamento) | !is.na(tipo_ausencia),
jantar_sem_pernoite = tipo == "Jantar" & !coalesce(pernoite, FALSE),
extra_sem_justificativa = tipo == "Extra" & justificativa_classe == "ausente",
motivo_regra = case_when(duplicidade ~ "Duplicidade",
fora_do_trabalho ~ "Pessoa fora do trabalho",
jantar_sem_pernoite ~ "Jantar sem pernoite",
extra_sem_justificativa ~ "Extra sem justificativa"),
valor_indevido = if_else(is.na(motivo_regra), 0, valor)
)
# Teto diário: soma do que não é indevido por outra regra; o excedente sai
# primeiro do extra, depois do jantar.
ordem_corte <- c("Extra" = 1, "Jantar" = 2, "Almoço" = 3)
auditada <- auditada |>
mutate(restante = valor - valor_indevido, corte = ordem_corte[tipo]) |>
group_by(matricula, dia) |>
mutate(excedente_dia = pmax(sum(restante) - TETO, 0)) |>
arrange(corte, .by_group = TRUE) |>
mutate(antes = cumsum(restante) - restante,
acima_do_teto = pmin(restante, pmax(excedente_dia - antes, 0))) |>
ungroup() |>
mutate(valor_indevido = valor_indevido + acima_do_teto,
motivo_regra = if_else(is.na(motivo_regra) & acima_do_teto > 0, "Acima do teto", motivo_regra),
regra = !is.na(motivo_regra)) |>
select(-restante, -corte, -excedente_dia, -antes)Ver el código R de la etapa 3: respaldo
# A equipe estava em campo naquele dia? A justificativa do extra é válida,
# vaga ou ausente?
auditada <- auditada |>
mutate(sem_lastro = !em_campo,
justificativa_fraca = tipo == "Extra" & justificativa_classe %in% c("vaga", "ausente"))Ver el código R de la etapa 4: rango esperado
# Valor pago por dia de equipe em campo, por base e mês. Como o nível geral
# muda de um mês para outro (mais pernoites no segundo semestre), cada base é
# comparada com a mediana das bases no mesmo mês; a faixa esperada desse
# índice vem do critério de Tukey sobre todos os grupos com pelo menos 15 dias
# de equipe em campo. Como a auditoria procura pagamento a mais, só ficar
# acima da faixa conta como sinal de risco; abaixo dela, o grupo só é anotado.
dias_campo <- diario |>
mutate(mes = format(data, "%Y-%m")) |>
count(base, mes, name = "dias_equipe")
base_mes <- auditada |>
mutate(mes = format(dia, "%Y-%m")) |>
group_by(base, mes) |>
summarise(valor = sum(valor), .groups = "drop") |>
left_join(dias_campo, c("base", "mes")) |>
mutate(valor_dia = valor / dias_equipe) |>
group_by(mes) |>
mutate(mediana_mes = median(valor_dia), indice = valor_dia / mediana_mes) |>
ungroup()
validos <- base_mes |> filter(dias_equipe >= 15)
q <- quantile(validos$indice, c(0.25, 0.75))
faixa <- c(q[[1]] - 1.5 * diff(q), q[[2]] + 1.5 * diff(q))
base_mes <- base_mes |>
mutate(faixa_min = faixa[1] * mediana_mes, faixa_max = faixa[2] * mediana_mes,
abaixo_da_faixa = dias_equipe >= 15 & indice < faixa[1],
fora_da_faixa = dias_equipe >= 15 & indice > faixa[2])
auditada <- auditada |>
mutate(mes = format(dia, "%Y-%m")) |>
left_join(base_mes |> select(base, mes, fora_da_faixa), c("base", "mes")) |>
mutate(fora_da_faixa = coalesce(fora_da_faixa, FALSE))Ver el código R de la etapa 5: puntaje de riesgo
# Um ponto para cada sinal, de 0 a 4.
auditada <- auditada |>
mutate(score = regra + sem_lastro + justificativa_fraca + fora_da_faixa,
score_sem_regra = score - regra)Ver el código R de la etapa 6: conclusión
# Indevido: violou uma regra objetiva. Revisar: sem regra violada, mas sem
# lastro de campo ou com score de 2 ou mais. Atenção: score 1. Regular: 0.
# Numa recarga que passou do teto só em parte, o excedente é Indevido e o
# resto recebe a conclusão que teria sem essa regra.
concluir <- function(sem_lastro, score) {
case_when(sem_lastro | score >= 2 ~ "Revisar", score == 1 ~ "Atenção", TRUE ~ "Regular")
}
auditada <- auditada |>
mutate(conclusao = if_else(regra, "Indevido", concluir(sem_lastro, score)),
conclusao_resto = concluir(sem_lastro, score_sem_regra),
valor_resto = valor - valor_indevido)
NIVEIS <- c("Indevido", "Revisar", "Atenção", "Regular")Resultado
Del valor pagado en el año, R$ 305 mil (4,0%) fueron indebidos, y otros R$ 235 mil (3,1%) necesitan confirmación de la base.
- Indebido es lo que violó una regla objetiva y se puede recuperar.
- Revisar está formado casi todo por recargas sin respaldo de campo: R$ 234 mil en 4.500 recargas registradas en días en que la cuadrilla no aparece en el diario. Puede ser una falla del diario, y no de la recarga; por eso la base confirma antes de cualquier recuperación.
- Atención no es una acusación. De las 8.686 recargas en Atención, 7.925 están ahí solo porque la base y el mes quedaron por encima del rango esperado; la recarga en sí cumple la regla. Entre ellas hay 1.072 registros que solo parecían error (registrados uno o dos días después, pernocte anotado con atraso, justificación con error de tipeo). Las otras 872 tienen una justificación vaga, como “ajuste” o “conforme combinado” (según lo acordado).
Lo indebido por tipo de defecto
Qué explica el salto del segundo semestre
El valor por día en campo subió R$ 20,86. De ese aumento, R$ 10,21 (49%) vinieron de pagos indebidos. Otros R$ 8,51 (41%) vinieron de más cenas legítimas, porque los pernoctes pasaron del 11,5% al 16,8% de los días en campo. El resto, R$ 2,14, acompaña a los pernoctes, en los extras.
Dónde y por qué
- El cambio de sistema en agosto creó duplicidades. Hasta julio suman R$ 6 mil; de agosto a diciembre, R$ 112 mil. El sistema nuevo acepta dos recargas del mismo tipo para la misma persona el mismo día.
- El registro de personal llega con atraso. El 100% de las recargas pagadas a personas de vacaciones cayeron en los cinco primeros días del período: la base se entera de las vacaciones después de que empezaron.
- Dos bases pagan cena sin pernocte. La Base 3 y la Base 6 concentran el 100% de las cenas sin pernocte, y son las únicas con meses por encima del rango esperado: 3 meses en la primera y 3 en la segunda.
Casos a revisar
La lista completa tiene 12.033 recargas clasificadas como Indebido o Revisar, ordenadas por conclusión, riesgo y valor, y se puede descargar al inicio de la página. Abajo, primero las 5 personas con mayor valor indebido (solo la parte indebida de cada recarga) y después las 5 con mayor valor a revisar.
| Conclusión | Matrícula | Base | Período | Recargas | Valor | Motivo |
|---|---|---|---|---|---|---|
| Indebido | F6561 | Base 3 | 06/01/2025 a 17/12/2025 | 40 | R$ 1.692 | Cena sin pernocte |
| Indebido | F5109 | Base 3 | 24/02/2025 a 11/12/2025 | 40 | R$ 1.684 | Cena sin pernocte |
| Indebido | F5730 | Base 6 | 27/03/2025 a 31/12/2025 | 37 | R$ 1.616 | Cena sin pernocte |
| Indebido | F4119 | Base 6 | 22/04/2025 a 26/12/2025 | 37 | R$ 1.580 | Cena sin pernocte |
| Indebido | F5573 | Base 3 | 23/01/2025 a 29/12/2025 | 37 | R$ 1.568 | Cena sin pernocte |
| Revisar | F3012 | Base 2 | 13/01/2025 a 29/12/2025 | 18 | R$ 936 | Sin respaldo de campo |
| Revisar | F8211 | Base 6 | 23/01/2025 a 31/12/2025 | 18 | R$ 936 | Sin respaldo de campo |
| Revisar | F1355 | Base 6 | 08/01/2025 a 23/12/2025 | 18 | R$ 872 | Sin respaldo de campo |
| Revisar | F6862 | Base 6 | 15/01/2025 a 16/12/2025 | 17 | R$ 852 | Sin respaldo de campo |
| Revisar | F1035 | Base 2 | 27/01/2025 a 29/12/2025 | 16 | R$ 832 | Sin respaldo de campo |
Indebido
Matrícula: F6561
Base: Base 3
Período: 06/01/2025 a 17/12/2025
Recargas: 40
Valor: R$ 1.692
Motivo: Cena sin pernocte
Indebido
Matrícula: F5109
Base: Base 3
Período: 24/02/2025 a 11/12/2025
Recargas: 40
Valor: R$ 1.684
Motivo: Cena sin pernocte
Indebido
Matrícula: F5730
Base: Base 6
Período: 27/03/2025 a 31/12/2025
Recargas: 37
Valor: R$ 1.616
Motivo: Cena sin pernocte
Indebido
Matrícula: F4119
Base: Base 6
Período: 22/04/2025 a 26/12/2025
Recargas: 37
Valor: R$ 1.580
Motivo: Cena sin pernocte
Indebido
Matrícula: F5573
Base: Base 3
Período: 23/01/2025 a 29/12/2025
Recargas: 37
Valor: R$ 1.568
Motivo: Cena sin pernocte
Revisar
Matrícula: F3012
Base: Base 2
Período: 13/01/2025 a 29/12/2025
Recargas: 18
Valor: R$ 936
Motivo: Sin respaldo de campo
Revisar
Matrícula: F8211
Base: Base 6
Período: 23/01/2025 a 31/12/2025
Recargas: 18
Valor: R$ 936
Motivo: Sin respaldo de campo
Revisar
Matrícula: F1355
Base: Base 6
Período: 08/01/2025 a 23/12/2025
Recargas: 18
Valor: R$ 872
Motivo: Sin respaldo de campo
Revisar
Matrícula: F6862
Base: Base 6
Período: 15/01/2025 a 16/12/2025
Recargas: 17
Valor: R$ 852
Motivo: Sin respaldo de campo
Revisar
Matrícula: F1035
Base: Base 2
Período: 27/01/2025 a 29/12/2025
Recargas: 16
Valor: R$ 832
Motivo: Sin respaldo de campo
Cómo saber que la auditoría acertó
Como los datos de este caso se generaron con un script, fue posible comparar la auditoría con lo que el generador plantó: encontró los 12.905 defectos plantados, sin excepción, y ninguno de los 19.885 registros que solo parecían error terminó como Indebido o Revisar. La auditoría no lee la clave de respuestas; un script aparte compara las dos cosas al final. Con datos reales no existe una clave de respuestas, y esa verificación se hace con la base: una muestra de casos de cada conclusión se revisa con quienes conocen el proceso antes de cerrar el informe.
Recomendaciones
Cada hallazgo se convierte en una corrección del proceso, para que el problema no vuelva al año siguiente.
| Hallazgo | Corrección recomendada | Responsable |
|---|---|---|
| Duplicidad después del cambio de sistema | Bloqueo en el sistema de una recarga por tipo, persona y día | TI y proveedor de la tarjeta |
| Registro de personal con atraso | Lista diaria de vacaciones, ausencias y desvinculaciones enviada al sistema de recargas | RR. HH. |
| Cena sin pernocte | Cena habilitada solo cuando el diario registra pernocte | Operaciones y supervisores de las bases |
| Por encima del tope | Tope diario aplicado por el propio sistema | TI |
| Justificación vaga | Justificación elegida de una lista cerrada, no escrita a mano | Contraloría |
| Valores ya pagados como indebidos | Reversión o recuperación según la política de la empresa | RR. HH., con apoyo del área legal |
Duplicidad después del cambio de sistema
Corrección recomendada: Bloqueo en el sistema de una recarga por tipo, persona y día
Responsable: TI y proveedor de la tarjeta
Registro de personal con atraso
Corrección recomendada: Lista diaria de vacaciones, ausencias y desvinculaciones enviada al sistema de recargas
Responsable: RR. HH.
Cena sin pernocte
Corrección recomendada: Cena habilitada solo cuando el diario registra pernocte
Responsable: Operaciones y supervisores de las bases
Por encima del tope
Corrección recomendada: Tope diario aplicado por el propio sistema
Responsable: TI
Justificación vaga
Corrección recomendada: Justificación elegida de una lista cerrada, no escrita a mano
Responsable: Contraloría
Valores ya pagados como indebidos
Corrección recomendada: Reversión o recuperación según la política de la empresa
Responsable: RR. HH., con apoyo del área legal
La auditoría también deja tres indicadores para seguir todos los meses:
- Tasa de recargas indebidas: valor indebido ÷ valor pagado, con meta por debajo del 0,5%.
- Cobertura de respaldo: recargas con cuadrilla registrada en campo ÷ total de recargas, con meta por encima del 99%.
- Valor por día en campo: por base, frente al rango esperado.
Lo que demuestra el caso
- Cruzar bases que nadie cruzaba. El error no estaba en ninguna base por sí sola, sino en la falta de verificación entre la tarjeta, el diario de campo y RR. HH.
- Separar el error objetivo de la sospecha. Solo lo que viola una regla va a recuperación; el resto va a la base para que lo confirme, sin acusar a nadie.
- Un número que explica el problema. El salto del segundo semestre se dividió entre lo indebido y lo legítimo, en lugar de convertirse en una orden genérica de recorte de gastos.
- Un informe que se vuelve a ejecutar. El código queda en la empresa, y el mismo informe se ejecuta el año siguiente con los datos nuevos.
¿Tu empresa paga algún beneficio, reembolso o adicional sin cruzarlo con otra base?
Cuéntame cómo funciona hoy y te digo qué se puede verificar.







