Construcción de base, tablas y exportación
Universidad Católica de Temuco
2026-05-04
Construir una base de remuneraciones municipales a partir de archivos descargados desde Transparencia Activa.
En este laboratorio vamos a:
bind_rows()left_join()La carpeta del proyecto debería tener una estructura parecida a esta:
Recordar primero instalar en caso de no contar en la biblioteca con los paquetes antes mencionados.
Los archivos vienen separados por punto y coma, por eso usaremos read_csv2().
planta <- read_csv2("datos_raw/planta_marzo_2026.csv", locale = locale(encoding = "Latin1")) |>
clean_names()
contrata <- read_csv2("datos_raw/contrata_marzo_26.csv", locale = locale(encoding = "Latin1")) |>
clean_names()
cdt <- read_csv2("datos_raw/cdt_marzo_26.csv", locale = locale(encoding = "Latin1")) |>
clean_names()
honorarios <- read_csv2("datos_raw/honorarios_26.csv", locale = locale(encoding = "Latin1")) |>
clean_names()Antes de limpiar, debemos mirar qué columnas trae cada archivo.
Antes de limpiar, debemos mirar qué columnas trae cada archivo.
[1] "ano"
[2] "mes"
[3] "estamento"
[4] "nombre_completo"
[5] "cargo_o_funcion"
[6] "grado_eus_o_jornada"
[7] "calificacion_profesional_o_formacion"
[8] "region"
[9] "asignaciones_especiales_del_mes_inc_en_rem_bruta"
[10] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[11] "remuneracion_liquida_del_mes"
[12] "rem_adicionales_del_mes_no_inc_en_rem_bruta"
[13] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"
[14] "derecho_a_horas_extraordinarias"
[15] "montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta"
[16] "montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta"
[17] "montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta"
[18] "fecha_de_inicio_dd_mm_aa"
[19] "fecha_de_termino_dd_mm_aa"
[20] "observaciones"
[21] "viaticos_del_mes_no_inc_en_rem_bruta"
[22] "x22"
Antes de limpiar, debemos mirar qué columnas trae cada archivo.
[1] "ano"
[2] "mes"
[3] "estamento"
[4] "nombre_completo"
[5] "cargo_o_funcion"
[6] "grado_eus_o_jornada"
[7] "calificacion_profesional_o_formacion"
[8] "region"
[9] "asignaciones_especiales_del_mes_inc_en_rem_bruta"
[10] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[11] "remuneracion_liquida_del_mes"
[12] "rem_adicionales_del_mes_no_inc_en_rem_bruta"
[13] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"
[14] "derecho_a_horas_extraordinarias"
[15] "montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta"
[16] "montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta"
[17] "montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta"
[18] "fecha_de_inicio_dd_mm_aa"
[19] "fecha_de_termino_dd_mm_aa"
[20] "viaticos_del_mes_no_inc_en_rem_bruta"
[21] "observaciones"
[22] "x22"
Antes de limpiar, debemos mirar qué columnas trae cada archivo.
[1] "ano"
[2] "mes"
[3] "nombre_completo"
[4] "cargo_o_funcion"
[5] "grado_eus_o_jornada"
[6] "calificacion_profesional_o_formacion"
[7] "region"
[8] "asignaciones_especiales_del_mes_inc_en_rem_bruta"
[9] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[10] "remuneracion_liquida_del_mes"
[11] "rem_adicionales_del_mes_no_inc_en_rem_bruta"
[12] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"
[13] "monto_de_desvinculacion_no_inc_en_rem_bruta"
[14] "derecho_a_horas_extraordinarias"
[15] "montos_y_horas_extraordinarias_del_mes_inc_en_rem_bruta"
[16] "viaticos_del_mes_no_inc_en_rem_bruta"
[17] "fecha_de_inicio_dd_mm_aaaa"
[18] "fecha_de_termino_dd_mm_aaaa"
[19] "observaciones"
[20] "x20"
Antes de limpiar, debemos mirar qué columnas trae cada archivo.
[1] "ano"
[2] "mes"
[3] "nombre_completo"
[4] "grado_eus_si_corresponde"
[5] "descripcion_de_la_funcion"
[6] "calificacion_profesional_o_formacion"
[7] "region"
[8] "honorario_total_bruto_del_mes"
[9] "honorario_total_liquido_del_mes"
[10] "tipo_de_pago"
[11] "descripcion_pago"
[12] "numero_de_cuotas"
[13] "fecha_de_inicio"
[14] "fecha_de_termino"
[15] "observaciones"
[16] "enlace_funciones_desarrolladas"
[17] "viaticos"
[18] "x18"
Rows: 72
Columns: 22
$ ano <dbl> …
$ mes <chr> …
$ estamento <chr> …
$ nombre_completo <chr> …
$ cargo_o_funcion <chr> …
$ grado_eus_o_jornada <dbl> …
$ calificacion_profesional_o_formacion <chr> …
$ region <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta <chr> …
$ derecho_a_horas_extraordinarias <chr> …
$ montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta <chr> …
$ montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta <chr> …
$ montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta <chr> …
$ fecha_de_inicio_dd_mm_aa <chr> …
$ fecha_de_termino_dd_mm_aa <chr> …
$ observaciones <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta <chr> …
$ x22 <lgl> …
Rows: 56
Columns: 22
$ ano <dbl> …
$ mes <chr> …
$ estamento <chr> …
$ nombre_completo <chr> …
$ cargo_o_funcion <chr> …
$ grado_eus_o_jornada <dbl> …
$ calificacion_profesional_o_formacion <chr> …
$ region <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta <chr> …
$ derecho_a_horas_extraordinarias <chr> …
$ montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta <chr> …
$ montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta <chr> …
$ montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta <chr> …
$ fecha_de_inicio_dd_mm_aa <chr> …
$ fecha_de_termino_dd_mm_aa <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta <chr> …
$ observaciones <chr> …
$ x22 <lgl> …
Rows: 5
Columns: 20
$ ano <dbl> …
$ mes <chr> …
$ nombre_completo <chr> …
$ cargo_o_funcion <chr> …
$ grado_eus_o_jornada <chr> …
$ calificacion_profesional_o_formacion <chr> …
$ region <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta <chr> …
$ monto_de_desvinculacion_no_inc_en_rem_bruta <chr> …
$ derecho_a_horas_extraordinarias <chr> …
$ montos_y_horas_extraordinarias_del_mes_inc_en_rem_bruta <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta <chr> …
$ fecha_de_inicio_dd_mm_aaaa <chr> …
$ fecha_de_termino_dd_mm_aaaa <chr> …
$ observaciones <chr> …
$ x20 <lgl> …
Rows: 204
Columns: 18
$ ano <dbl> 2026, 2026, 2026, 2026, 2026, 202…
$ mes <chr> "Marzo", "Marzo", "Marzo", "Marzo…
$ nombre_completo <chr> "Acuña Cerda, Jenifer Haydee", "A…
$ grado_eus_si_corresponde <chr> "No asimilado a grado", "No asimi…
$ descripcion_de_la_funcion <chr> "Prestacion de servicios Programa…
$ calificacion_profesional_o_formacion <chr> "Profesora Educación Fisica", "En…
$ region <chr> "Región de La Araucanía", "Región…
$ honorario_total_bruto_del_mes <chr> "$ 1.462.759", "$ 636.008", "$ 22…
$ honorario_total_liquido_del_mes <chr> "-", "-", "-", "-", "-", "-", "-"…
$ tipo_de_pago <chr> "Pago mensual", "Pago mensual", "…
$ descripcion_pago <chr> "-", "-", "-", "-", "-", "-", "-"…
$ numero_de_cuotas <chr> "-", "-", "-", "-", "-", "-", "-"…
$ fecha_de_inicio <chr> "02/01/2026", "01/01/2026", "05/0…
$ fecha_de_termino <chr> "31/12/2026", "31/12/2026", "30/0…
$ observaciones <chr> "decreto 415; decreto 874 17/02/2…
$ enlace_funciones_desarrolladas <chr> "No publica", "No publica", "No p…
$ viaticos <chr> "No informa", "No informa", "No i…
$ x18 <lgl> NA, NA, NA, NA, NA, NA, NA, NA, N…
Cada archivo debe conservar información sobre su origen.
Los archivos no tienen exactamente las mismas columnas. Por eso construiremos una versión simplificada de cada base.
planta_simple <- planta |>
transmute(
municipio,
unidad,
tipo_personal,
anio,
mes,
mes_num,
periodo,
nombre = nombre_completo,
estamento = estamento,
funcion = cargo_o_funcion,
grado_jornada = grado_eus_o_jornada,
formacion = calificacion_profesional_o_formacion,
region = region,
monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
monto_liquido = remuneracion_liquida_del_mes,
fecha_inicio = fecha_de_inicio_dd_mm_aa,
fecha_termino = fecha_de_termino_dd_mm_aa,
observaciones = observaciones
)contrata_simple <- contrata |>
transmute(
municipio,
unidad,
tipo_personal,
anio,
mes,
mes_num,
periodo,
nombre = nombre_completo,
estamento = estamento,
funcion = cargo_o_funcion,
grado_jornada = grado_eus_o_jornada,
formacion = calificacion_profesional_o_formacion,
region = region,
monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
monto_liquido = remuneracion_liquida_del_mes,
fecha_inicio = fecha_de_inicio_dd_mm_aa,
fecha_termino = fecha_de_termino_dd_mm_aa,
observaciones = observaciones
)cdt_simple <- cdt |>
transmute(
municipio,
unidad,
tipo_personal,
anio,
mes,
mes_num,
periodo,
nombre = nombre_completo,
estamento = NA_character_,
funcion = cargo_o_funcion,
grado_jornada = grado_eus_o_jornada,
formacion = calificacion_profesional_o_formacion,
region = region,
monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
monto_liquido = remuneracion_liquida_del_mes,
fecha_inicio = fecha_de_inicio_dd_mm_aaaa,
fecha_termino = fecha_de_termino_dd_mm_aaaa,
observaciones = observaciones
)honorarios_simple <- honorarios |>
transmute(
municipio,
unidad,
tipo_personal,
anio,
mes,
mes_num,
periodo,
nombre = nombre_completo,
estamento = NA_character_,
funcion = descripcion_de_la_funcion,
grado_jornada = grado_eus_si_corresponde,
formacion = calificacion_profesional_o_formacion,
region = region,
monto_bruto = honorario_total_bruto_del_mes,
monto_liquido = honorario_total_liquido_del_mes,
fecha_inicio = fecha_de_inicio,
fecha_termino = fecha_de_termino,
observaciones = observaciones
)Antes de apilar las bases, conviene asegurar que las columnas comunes tengan el mismo tipo. En estos archivos, por ejemplo, grado_jornada puede venir como número en una base y como texto en otra. Eso hace que bind_rows() se detenga.
Primero generaremos un conjunto, con los nombres de las variables que debiesen ser reconocidas como texto
Luego vamos a aplicar para cada elemento de esa lista de variables, la opción de ser conocida como texto (character)
planta_simple <- planta_simple |>
mutate(across(any_of(columnas_texto), as.character))
contrata_simple <- contrata_simple |>
mutate(across(any_of(columnas_texto), as.character))
cdt_simple <- cdt_simple |>
mutate(across(any_of(columnas_texto), as.character))
honorarios_simple <- honorarios_simple |>
mutate(across(any_of(columnas_texto), as.character))bind_rows()Como queremos apilar registros, usamos bind_rows().
Rows: 337
Columns: 18
$ municipio <chr> "Vilcún", "Vilcún", "Vilcún", "Vilcún", "Vilcún", "Vilcú…
$ unidad <chr> "Municipalidad", "Municipalidad", "Municipalidad", "Muni…
$ tipo_personal <chr> "planta", "planta", "planta", "planta", "planta", "plant…
$ anio <dbl> 2026, 2026, 2026, 2026, 2026, 2026, 2026, 2026, 2026, 20…
$ mes <chr> "marzo", "marzo", "marzo", "marzo", "marzo", "marzo", "m…
$ mes_num <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
$ periodo <chr> "2026-03", "2026-03", "2026-03", "2026-03", "2026-03", "…
$ nombre <chr> "ALFARO PULGAR, PABLO ESTEBAN", "ALVIAL RIVERA, VIVIANA…
$ estamento <chr> "Técnico", "Profesional", "Técnico", "Administrativo", "…
$ funcion <chr> "TÉCNICO ADMINISTRACIÓN MUNICIPAL", "PROFESIONAL OBRAS M…
$ grado_jornada <chr> "14", "11", "16", "17", "7", "9", "15", "17", "17", "7",…
$ formacion <chr> "Técnico en Amplificación", "Ingeniero Civil", "Técnico …
$ region <chr> "Región de La Araucanía", "Región de La Araucanía", "Reg…
$ monto_bruto <chr> "$ 1.737.854", "$ 2.426.876", "$ 1.314.239", "$ 892.019"…
$ monto_liquido <chr> "$", "$", "$", "$", "$", "$", "$", "$", "$", "$", "$", "…
$ fecha_inicio <chr> "01/06/2015", "03/11/2020", "25/03/2019", "17/11/2025", …
$ fecha_termino <chr> "Indefinido", "Indefinido", "Indefinido", "30/06/2026", …
$ observaciones <chr> "Sin Observaciones", "Sin Observaciones", "Sin Observaci…
Los montos vienen como texto. Necesitamos convertirlos a números.
Como este proceso lo tendremos que repetir, generaremos una función, así podemos replicar este proceso solo escribiendo el nombre de la función posteriormente.
Min. 1st Qu. Median Mean 3rd Qu. Max.
47334 500000 885765 1015447 1294552 7008009
remuneraciones |>
summarise(
casos = n(),
sin_monto_bruto = sum(is.na(monto_bruto)),
sin_monto_liquido = sum(is.na(monto_liquido))
)# A tibble: 1 × 3
casos sin_monto_bruto sin_monto_liquido
<int> <int> <int>
1 337 0 337
tabla_tipo <- remuneraciones |>
count(tipo_personal, name = "registros") |>
mutate(
porcentaje = round(registros / sum(registros) * 100, 1)
)
tabla_tipo# A tibble: 4 × 3
tipo_personal registros porcentaje
<chr> <int> <dbl>
1 codigo_trabajo 5 1.5
2 contrata 56 16.6
3 honorarios 204 60.5
4 planta 72 21.4
tabla_remuneraciones <- remuneraciones |>
group_by(tipo_personal) |>
summarise(
registros = n(),
promedio_bruto = round(mean(monto_bruto, na.rm = TRUE), 0),
mediana_bruto = round(median(monto_bruto, na.rm = TRUE), 0),
minimo_bruto = min(monto_bruto, na.rm = TRUE),
maximo_bruto = max(monto_bruto, na.rm = TRUE),
sin_monto = sum(is.na(monto_bruto)),
.groups = "drop"
)# A tibble: 4 × 7
tipo_personal registros promedio_bruto mediana_bruto minimo_bruto maximo_bruto
<chr> <int> <dbl> <dbl> <dbl> <dbl>
1 codigo_traba… 5 446342 120448 47334 976221
2 contrata 56 1109228 917666 525133 2540591
3 honorarios 204 662758 636008 53340 1890000
4 planta 72 1981312 1582105 86621 7008009
# ℹ 1 more variable: sin_monto <int>
# A tibble: 4 × 7
`Tipo de personal` Registros `Promedio bruto` `Mediana bruto` `Mínimo bruto`
<chr> <int> <dbl> <dbl> <dbl>
1 codigo_trabajo 5 446342 120448 47334
2 contrata 56 1109228 917666 525133
3 honorarios 204 662758 636008 53340
4 planta 72 1981312 1582105 86621
# ℹ 2 more variables: `Máximo bruto` <dbl>, `Sin monto informado` <int>
flextabletabla_flex <- tabla_presentacion |>
flextable() |>
set_caption("Tabla 1. Remuneraciones brutas por tipo de personal. Municipalidad de Vilcún, marzo 2026") |>
autofit()
tabla_flexTipo de personal | Registros | Promedio bruto | Mediana bruto | Mínimo bruto | Máximo bruto | Sin monto informado |
|---|---|---|---|---|---|---|
codigo_trabajo | 5 | 446,342 | 120,448 | 47,334 | 976,221 | 0 |
contrata | 56 | 1,109,228 | 917,666 | 525,133 | 2,540,591 | 0 |
honorarios | 204 | 662,758 | 636,008 | 53,340 | 1,890,000 | 0 |
planta | 72 | 1,981,312 | 1,582,105 | 86,621 | 7,008,009 | 0 |
dir.create("outputs", showWarnings = FALSE)
doc <- read_docx()
doc <- doc |>
body_add_par("Resultados descriptivos preliminares", style = "heading 1") |>
body_add_par("Tabla 1. Remuneraciones brutas por tipo de personal", style = "heading 2") |>
body_add_flextable(tabla_flex) |>
body_add_par(
"Fuente: elaboración propia a partir de Transparencia Activa, Municipalidad de Vilcún, marzo 2026.",
style = "Normal"
)
print(doc, target = "outputs/tabla_remuneraciones_vilcun.docx")gtleft_join()Ahora imaginemos que queremos agregar información externa del municipio.
Usamos left_join() porque queremos agregar columnas, no filas.
left_join()Revisamos:
remuneraciones_contexto |>
select(municipio, provincia, region_administrativa, tipo_comuna, tipo_personal) |>
head()# A tibble: 6 × 5
municipio provincia region_administrativa tipo_comuna tipo_personal
<chr> <chr> <chr> <chr> <chr>
1 Vilcún Cautín La Araucanía Municipio de referenc… planta
2 Vilcún Cautín La Araucanía Municipio de referenc… planta
3 Vilcún Cautín La Araucanía Municipio de referenc… planta
4 Vilcún Cautín La Araucanía Municipio de referenc… planta
5 Vilcún Cautín La Araucanía Municipio de referenc… planta
6 Vilcún Cautín La Araucanía Municipio de referenc… planta
El número de filas no cambia, porque left_join() agregó columnas de contexto.
Cada grupo debe adaptar el script a su municipio.
Debe entregar una carpeta con:
Escriba un párrafo de 5 a 7 líneas que responda:
El objetivo de este laboratorio no es solo producir una tabla.
El objetivo es construir una base trazable, ordenada y reproducible, que luego pueda alimentar el póster descriptivo.