Saltar al contenido
dumaloor.dev_
~/academia
R

De Excel a R: migrando análisis financieros reales

Sustituir un Excel financiero de 30 pestañas que tarda 8 minutos en abrir por un script de R que tarda 2 segundos. Pasos reales, problemas reales, y los argumentos para convencer al financiero de que merece la pena.

6 min de lecturarfinanzasexceltidyversemigración

El Excel del financiero pesa 180 MB. Tiene 30 pestañas con fórmulas que apuntan a otras pestañas que apuntan a otras pestañas. Tarda 8 minutos en abrir, otros 4 en recalcular, y si tocas una celda sin querer se rompe un cuadre que nadie sabe explicar. En una reunión, alguien pregunta "¿cuánto vendimos por canal en marzo del año pasado?". El financiero abre el Excel. Espera. Espera. Encuentra la pestaña. Calcula. 15 minutos después responde.

Esto es real. Y es donde R puede cambiar la vida del equipo. Cuento cómo migro este tipo de bestias.

#Por qué Excel deja de servir

Excel es perfecto para tres cosas: prototipar un cálculo, mostrar una tabla simple, y editar a mano. Falla en:

  • Reproducibilidad. Una celda cambiada sin querer y el cuadre se rompe sin trazabilidad.
  • Escala. Más de ~500.000 filas y el .xlsx empieza a sufrir. Más de 1 millón ya no abre.
  • Auditoría. ¿Por qué este número da 47.231,82€? Sigue las flechas durante 20 minutos.
  • Concurrencia. Si dos personas editan, una pierde sus cambios. SharePoint mejora algo, no resuelve.

Cuando llegamos a "para sacar el informe semanal hay que pasar 3 horas haciendo copia-pega entre pestañas", es hora.

#La migración en cinco pasos

Paso 1 — Extraer los datos, no las fórmulas

Excel es dos cosas: datos + fórmulas que operan sobre los datos. El truco está en separar. Las fórmulas las vas a reescribir en R. Los datos tienen que salir limpios a CSV.

r15 líneas
library(readxl)
library(tidyverse)
library(janitor)

# Lee solo la pestaña de movimientos en bruto, no las pestañas calculadas
mov <- read_excel(
  "tesoreria_2025.xlsx",
  sheet = "MOVIMIENTOS",
  range = "A3:M82531",                    # salta cabecera de adorno
  col_types = c("date", "text", "text", "numeric", rep("text", 9))
) |>
  clean_names()

dim(mov)
# [1] 82528    13
Cuidado con los rangos

Los Excel financieros suelen tener una cabecera de "información" con logo, fecha, autor, etc. Si pasas range = NULL, R lee esa basura. Identifica la fila donde empieza la cabecera real de la tabla y úsalo en range = "A3:...".

Paso 2 — Validar tipos y NULLs disfrazados

Excel es traicionero porque mete strings donde no debería. Una fecha mal formateada queda como número (45291), un importe como texto ("1.234,56€"), un NULL como "-" o "N/A". Antes de calcular nada:

r11 líneas
mov |>
  summarise(
    rows               = n(),
    fecha_min          = min(fecha, na.rm = TRUE),
    fecha_max          = max(fecha, na.rm = TRUE),
    importe_na         = sum(is.na(importe)),
    importe_min        = min(importe, na.rm = TRUE),
    importe_max        = max(importe, na.rm = TRUE),
    categorias_unicas  = n_distinct(categoria),
    cuentas_unicas     = n_distinct(cuenta)
  )

Si importe_na es > 0 cuando debería ser 0, ya sabes que hay celdas mal. Si fecha_max es del año 2099, hay un typo. Mejor saberlo ahora que tres meses después cuando alguien pregunte por qué un cuadre no encaja.

Paso 3 — Reescribir UNA fórmula del Excel y comparar contra Excel

Esto es lo crítico. No migres todo de golpe. Coge una sola métrica del Excel (la más importante o la más visible) y reprodúcela en R. Compara los dos números. Tienen que coincidir al céntimo.

r15 líneas
# La fórmula maestra del Excel es:
# = SUMA(MOVIMIENTOS, columna IMPORTE, filtros: 
#   AÑO=2025, TIPO=ENTRADA, CATEGORÍA<>"INTERCO")

gmv_2025 <- mov |>
  filter(
    year(fecha) == 2025,
    tipo == "ENTRADA",
    categoria != "INTERCO"
  ) |>
  summarise(gmv = sum(importe, na.rm = TRUE)) |>
  pull(gmv)

gmv_2025
# [1] 4823917.42

Comparas con la celda equivalente del Excel. Si dan 4.823.917,42 ambos, vas bien. Si difieren en 12,80€, busca el origen ANTES de continuar. Suele ser:

  • Un NULL en el importe (na.rm cambia el comportamiento).
  • Una categoría con espacios al final ("INTERCO " vs "INTERCO").
  • Una fecha mal parseada (Excel a veces lee "01/03/2025" como "03/01/2025").

Ningún financiero va a confiar en tu migración si el primer número que le enseñas no cuadra.

Paso 4 — Generar TODO el informe en código

Una vez la primera métrica cuadra, el resto es mecánico. Cada pestaña del Excel original = una función en R que produce el mismo dataframe. Empaquetas en un script único:

r21 líneas
source("R/funciones.R")

mov <- read_movimientos("data/tesoreria_2025.xlsx")

informe <- list(
  gmv_mensual    = calcular_gmv_mensual(mov),
  por_canal      = calcular_por_canal(mov),
  pyl_resumen    = calcular_pyl(mov),
  conciliacion   = calcular_conciliacion(mov),
  alertas        = detectar_anomalias(mov),
  forecast       = forecast_resto_ano(mov)
)

# Exporta a un único Excel pulido, una pestaña por concepto
library(openxlsx)
wb <- createWorkbook()
for (nombre in names(informe)) {
  addWorksheet(wb, nombre)
  writeData(wb, nombre, informe[[nombre]])
}
saveWorkbook(wb, "out/informe_semanal.xlsx", overwrite = TRUE)

El financiero sigue recibiendo un Excel (porque eso es lo que sabe abrir), pero ese Excel se genera en 2 segundos desde R, no a mano en 3 horas.

Paso 5 — Programar y monitorizar

Si el informe es semanal, programa el script con cron en un servidor o GitHub Actions. Que se ejecute solo cada lunes a las 7 AM y deje el Excel en una carpeta compartida o lo envíe por email.

bash2 líneas
# crontab del servidor
0 7 * * 1 cd /opt/finanzas && Rscript scripts/informe_semanal.R >> logs/informe.log 2>&1

A partir de ese momento el equipo no abre el Excel maestro nunca más. El Excel maestro se mantiene como fuente única de datos (movimientos crudos), todo el cálculo vive en código.

#Los argumentos para convencer al financiero

El financiero no quiere "migrar a R". Quiere que el informe esté listo el lunes a las 8 AM sin tener que entrar él a tocar nada. Vende eso, no el lenguaje:

Antes (Excel)Después (R)
8 min en abrir el Excel2 segundos en generar el informe
3 horas humanas/semana para preparar el informe0 horas (cron)
Imposible auditar un número raroCada cálculo en una función con tests
Una celda mal y el cuadre se rompeValidaciones automáticas paran el job
No se puede comparar entre meses fácilmenteComparativas YoY/MoM en una línea
Solo el financiero sabe abrirloCualquiera del equipo puede leer el script

Cuando lo enseñas funcionando en su propia mesa, deja de haber discusión.

#Errores típicos al migrar

Trampas que he pisado

Fechas españolas vs americanas. Si read_excel lee "03/04/2025" como 2025-04-03 cuando era 2025-03-04, vas a tener un mes mal en todo el informe. Comprueba con head(mov$fecha).

Importes con miles y decimales europeos. Excel a veces los guarda como texto "1.234,56". as.numeric los convierte a NA silenciosamente. Usa readr::parse_number(x, locale = locale(decimal_mark = ",", grouping_mark = ".")).

Pestañas ocultas con cálculos clave. El financiero olvida mencionarlas. Abre el Excel con excel_sheets("...") y mira TODO lo que hay, no solo lo visible.

#Lo que NO migras

Algunas cosas siguen siendo mejor en Excel:

  • Edición manual puntual (un ajuste de tesorería al cierre de mes).
  • Tablas dinámicas exploratorias para gente no técnica.
  • Output final visto por externos (gestoría, banco, auditor).

R produce el dato calculado. Excel sigue siendo la capa de presentación y edición manual cuando hace falta. Es complementario, no sustituto.

Receta

Extrae datos crudos del Excel a R. Reescribe UNA métrica clave y cuadra al céntimo contra el Excel. Cuando cuadra, replica el resto en funciones. Genera el output en Excel desde R y prográmalo. El financiero gana 3 horas a la semana y duerme mejor.

Si quieres que te ayude a destripar un Excel financiero concreto, escríbeme: hola@dumaloor.es. Lo más probable es que el dolor que tienes se resuelva en una semana de trabajo.