Este documento sistematiza, en formato de línea de tiempo interactiva, los principales hitos en materia de política educativa identificados a lo largo del semestre en los dos ámbitos de análisis del Observatorio: Poder Legislativo y Poder Ejecutivo. El ámbito de prensa, presente en versiones anteriores de este gráfico, fue eliminado por decisión del equipo al resultar redundante con los hitos ya registrados en los otros dos ámbitos.
La tabla de datos que alimenta el gráfico está pensada para
actualizarse de manera continua, sesión a sesión, de modo que este
.Rmd pueda reutilizarse en cada informe sin rehacer el
trabajo de sistematización.
Cada fila representa un hito, con los siguientes campos:
evento: título corto del hito (aparece en el
gráfico)ambito: Legislativo / Ejecutivofecha_inicio / fecha_fin: si son iguales,
el hito se grafica como punto; si son distintas, como rango (útil para
tramitación de proyectos de ley)detalle: descripción extendida, visible al pasar el
mouse (hover)Nota metodológica: en la medida en que esta tabla crezca, conviene externalizarla a un
.csv(ej.hitos_educacion.csv) y leerla conread_csv()en vez de mantenerla comotribble()dentro del.Rmd. Esto facilita que distintos integrantes del equipo la actualicen sin tocar el documento.
El criterio para asignar el ambito de un hito no es
“quién lo origina”, sino dónde produce su efecto
institucional:
| Acto | Ámbito | Por qué |
|---|---|---|
| Anuncio o conferencia de prensa | Depende de quién anuncia (Ejecutivo si es el presidente/ministro; Legislativo si es una bancada o comisión) | Es un acto político real, se le asigna el ámbito de su emisor |
| Ingreso de un proyecto de ley (mensaje o moción) | Legislativo | Activa la tramitación parlamentaria, independiente de quién lo origine |
| Indicación (del Ejecutivo o de parlamentarios) a un proyecto en trámite | Legislativo | Solo produce efecto si es aprobada dentro del proceso legislativo |
| Sesión de comisión / votación | Legislativo | Ocurre dentro del Congreso |
| Decreto, resolución exenta, oficio circular | Ejecutivo | Tiene efecto inmediato, sin necesidad de aprobación parlamentaria |
| Promulgación | Ejecutivo | Acto final del Presidente, una vez completado el trámite en el Congreso |
Por esto, cuando un mismo proceso combina un anuncio del Ejecutivo con su posterior tramitación legislativa (como la reforma al SAE), se registran como dos hitos separados: un punto Ejecutivo para el anuncio, y una estela Legislativa para el ingreso, las indicaciones y la votación.
# tipo -> define el símbolo del marcador (ver sección de diseño más abajo)
# relevancia -> ya no afecta el tamaño del marcador (ver nota en {r paleta}); se conserva
# en el csv solo como referencia histórica, sin uso visual actual
hitos <- read_csv("hitos_educacion.csv", show_col_types = FALSE) %>%
mutate(
fecha_inicio = as.Date(fecha_inicio),
fecha_fin = as.Date(fecha_fin),
es_rango = fecha_inicio != fecha_fin,
ambito = factor(ambito, levels = c("Ejecutivo", "Legislativo"))
)
hitos
## # A tibble: 15 × 9
## evento ambito tipo relevancia fecha_inicio fecha_fin detalle url es_rango
## <chr> <fct> <chr> <dbl> <date> <date> <chr> <chr> <lgl>
## 1 Ingre… Legis… Proy… 3 2026-04-07 2026-06-12 Proyec… <NA> TRUE
## 2 Aprob… Legis… Proy… 2 2026-05-05 2026-05-05 Aproba… <NA> FALSE
## 3 Sesió… Legis… Sesi… 2 2026-05-12 2026-05-12 Votaci… <NA> FALSE
## 4 Sesió… Legis… Sesi… 3 2026-06-02 2026-06-02 Aproba… <NA> FALSE
## 5 Sesió… Legis… Sesi… 3 2026-06-09 2026-06-09 Denunc… <NA> FALSE
## 6 Ofici… Ejecu… Medi… 3 2026-04-21 2026-04-21 Hacien… <NA> FALSE
## 7 Promu… Legis… Proy… 3 2026-03-25 2026-03-25 Marco … http… FALSE
## 8 Resol… Ejecu… Medi… 3 2026-04-27 2026-04-27 Gobier… <NA> FALSE
## 9 TGR i… Ejecu… Medi… 1 2026-04-23 2026-04-23 Primer… <NA> FALSE
## 10 TGR a… Ejecu… Medi… 3 2026-06-04 2026-06-04 Segund… <NA> FALSE
## 11 Comis… Legis… Sesi… 1 2026-04-21 2026-04-21 APROXI… http… FALSE
## 12 Kast … Legis… Proy… 3 2026-06-15 2026-06-15 Boletí… http… FALSE
## 13 Comis… Legis… Sesi… 2 2026-07-05 2026-07-05 APROXI… http… FALSE
## 14 Anunc… Ejecu… Medi… 2 2026-06-23 2026-06-23 Kast a… http… FALSE
## 15 Trami… Legis… Proy… 3 2026-06-23 2026-07-23 Ingres… http… TRUE
Para que el gráfico funcione como una pieza editorial y no como un Gantt genérico, se codifican tres dimensiones a la vez, distribuidas en dos filas (una por ámbito):
| dimensión | codifica | valores |
|---|---|---|
| color del punto | ámbito | Legislativo (azul petróleo #2C5F6F) · Ejecutivo
(burdeos #8B3A3A) |
| símbolo | tipo de hito | Proyecto de Ley (◆) · Sesión Comisión (●) · Medida Ejecutiva (▲) |
| tamaño | uniforme para todos los hitos | 13px fijos — se eliminó la variación por relevancia política por ser un criterio demasiado subjetivo |
| color de la estela | evento específico (no el ámbito) | cada proceso con rango de fechas puede tener su propio color en
colores_rango, para distinguir visualmente “momentos
políticos” distintos aunque compartan el mismo ámbito (ej. Escuelas
Protegidas vs. reforma al SAE, ambos Legislativo) |
Nota técnica: color, símbolo y tamaño se calculan
como valores literales por fila en el chunk {r paleta}
(columnas color_hex, symbol_val,
size_px), y el gráfico se arma en tres llamadas a
add_markers() separadas (una por tipo de hito), en vez de
pasarle a plotly los datos categóricos crudos
(color=~ambito, symbol=~tipo) para que los
traduzca él mismo. Esto es deliberado: combinar dos argumentos
categóricos en una misma llamada puede hacer que plotly
ignore las paletas nombradas y aplique su propio orden alfabético por
defecto.
Además se agregan líneas verticales punteadas para marcar el cambio
de mes y un fondo cálido tipo papel, para alejarse de la grilla blanca
por defecto de plotly.
colores_ambito <- c(
"Legislativo" = "#2C5F6F",
"Ejecutivo" = "#8B3A3A"
)
simbolos_tipo <- c(
"Proyecto de Ley" = "diamond",
"Sesión Comisión" = "circle",
"Medida Ejecutiva" = "triangle-up"
)
# IMPORTANTE — por qué esto se calcula acá y no se deja como argumento de plotly:
# pasarle a add_markers() dos argumentos categóricos a la vez (color=~ambito y
# symbol=~tipo) hace que plotly, en ciertas versiones, ignore las paletas nombradas
# que le pasamos y aplique su propio orden alfabético por defecto para alguna de
# las dos dimensiones (por eso podían aparecer símbolos o colores que no correspondían
# a lo definido arriba). La forma a prueba de fallos es calcular acá, fila por fila,
# el color, el símbolo y el tamaño en píxeles ya resueltos como valores literales, y
# más abajo separar los datos en tres grupos reales (uno por tipo de hito) para que
# cada uno se dibuje con su propio color/símbolo fijo, sin que plotly tenga que inferir nada.
hitos <- hitos %>%
mutate(
color_hex = case_when(
ambito == "Ejecutivo" ~ colores_ambito[["Ejecutivo"]],
ambito == "Legislativo" ~ colores_ambito[["Legislativo"]],
TRUE ~ "#999999"
),
symbol_val = case_when(
tipo == "Proyecto de Ley" ~ simbolos_tipo[["Proyecto de Ley"]],
tipo == "Sesión Comisión" ~ simbolos_tipo[["Sesión Comisión"]],
tipo == "Medida Ejecutiva" ~ simbolos_tipo[["Medida Ejecutiva"]],
TRUE ~ "circle"
),
# Tamaño uniforme para todos los marcadores: se eliminó la variación por
# "relevancia" porque asignarle un valor de 1 a 3 a cada hito resultaba
# demasiado subjetivo entre distintas personas del equipo. La columna
# `relevancia` puede seguir existiendo en el csv (por si se retoma más
# adelante), pero ya no afecta el tamaño del punto.
size_px = 13,
# posición numérica en el eje y (en vez de dejar que plotly reparta las dos
# categorías con su espaciado fijo por defecto): así se puede acercar o alejar
# "Ejecutivo" y "Legislativo" ajustando solo estos dos números.
y_pos = case_when(
ambito == "Ejecutivo" ~ 1,
ambito == "Legislativo" ~ 1.35,
TRUE ~ 1.2
)
)
# Color de las estelas (barras de rango): por defecto sigue el color del ámbito,
# pero se puede fijar un color propio por evento cuando dos procesos del mismo
# ámbito conviene distinguir visualmente como "momentos políticos" distintos.
colores_rango <- c(
"Ingreso 'Escuelas Protegidas' (18156-04)" = "#2C5F6F",
"Tramitación reforma al SAE (Elección Mutua)" = "#B08D57"
)
color_estela <- function(evento, ambito) {
if (evento %in% names(colores_rango)) {
colores_rango[[evento]]
} else {
colores_ambito[[as.character(ambito)]]
}
}
# Meses en español, mapeados a mano para no depender del locale del sistema
# (format(fecha, "%B") de R queda en inglés si el locale de la máquina no está en español,
# lo cual varía entre Windows/Mac/Linux y entre computadores del equipo).
meses_es <- c("enero", "febrero", "marzo", "abril", "mayo", "junio",
"julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre")
fecha_es <- function(fecha) {
paste0(format(fecha, "%d"), " de ", meses_es[as.integer(format(fecha, "%m"))],
" de ", format(fecha, "%Y"))
}
fig <- plot_ly()
# Barras translúcidas para procesos con rango de fechas (tramitaciones)
rangos <- hitos %>% filter(es_rango)
for (i in seq_len(nrow(rangos))) {
# add_segments() solo genera 2 puntos de datos (inicio y fin), y plotly únicamente
# activa el hover exactamente sobre un punto de datos real, no en cualquier lugar
# del trazo. Por eso se generan puntos intermedios cada 2 días a lo largo de todo
# el rango, todos con el mismo texto: así el hover funciona en toda la banda,
# no solo en sus extremos.
fechas_rango <- seq(rangos$fecha_inicio[i], rangos$fecha_fin[i], by = "2 days")
texto_rango <- paste0(
"<b>", rangos$evento[i], "</b><br>",
fecha_es(rangos$fecha_inicio[i]), " – ", fecha_es(rangos$fecha_fin[i]),
"<br><br>", rangos$detalle[i]
)
fig <- fig %>% add_trace(
type = "scatter", mode = "lines",
x = fechas_rango, y = rep(rangos$y_pos[i], length(fechas_rango)),
line = list(color = color_estela(rangos$evento[i], rangos$ambito[i]), width = 18),
opacity = 0.22,
showlegend = FALSE,
text = rep(texto_rango, length(fechas_rango)),
hoverinfo = "text",
hoverlabel = list(bgcolor = "#FAF7F0", font = list(family = "Georgia", size = 12, color = "#3B3B3B"),
bordercolor = "#D8D2C4")
)
}
# Separadores de mes (líneas verticales punteadas + etiqueta)
meses <- seq(as.Date("2026-03-01"), as.Date("2026-08-01"), by = "month")
shapes_mes <- lapply(meses, function(m) {
list(type = "line", x0 = m, x1 = m, y0 = 0, y1 = 1, yref = "paper",
line = list(color = "#D8D2C4", width = 1, dash = "dot"))
})
anotaciones_mes <- lapply(meses[-length(meses)], function(m) {
list(x = m + 14, y = 1.06, yref = "paper", showarrow = FALSE,
text = toupper(meses_es[as.integer(format(m, "%m"))]), font = list(size = 10, color = "#A39B8B", family = "Georgia"))
})
# Marcadores por hito: se dibujan en TRES llamadas separadas (una por tipo de hito),
# cada una usando el subconjunto real de datos de ese tipo, con color/símbolo/tamaño
# ya resueltos como valores literales (color_hex, symbol_val, size_px). Esto evita
# por completo que plotly tenga que inferir una paleta categórica —fuente del bug
# de colores/símbolos cruzados— y de paso genera automáticamente una leyenda
# correcta con las tres etiquetas (una por cada llamada/subconjunto).
tipos_presentes <- c("Proyecto de Ley", "Sesión Comisión", "Medida Ejecutiva")
for (tp in tipos_presentes) {
datos_tp <- hitos %>% filter(tipo == tp)
if (nrow(datos_tp) == 0) next
fig <- fig %>% add_markers(
data = datos_tp,
x = ~fecha_inicio, y = ~y_pos,
marker = list(
color = ~color_hex,
symbol = ~symbol_val,
size = ~size_px,
line = list(width = 1.5, color = "#FAF7F0"),
opacity = 0.95
),
customdata = ~url,
name = tp,
legendgroup = tp,
showlegend = TRUE,
text = ~paste0(
"<b>", evento, "</b><br>",
fecha_es(fecha_inicio),
"<br><i>", tipo, "</i><br><br>",
detalle,
ifelse(!is.na(url) & url != "", "<br><br>📰 <i>Clic para ver la nota de prensa</i>", "")
),
hoverinfo = "text",
hoverlabel = list(bgcolor = "#FAF7F0", font = list(family = "Georgia", size = 12, color = "#3B3B3B"),
bordercolor = "#D8D2C4")
)
}
# Layout editorial: fondo cálido, tipografía serif, sin grilla vertical
fig <- fig %>% layout(
title = list(
text = "<b>Agenda Educativa 2026</b><br><sup>Legislativo · Ejecutivo (marzo-julio)</sup>",
font = list(family = "Georgia", size = 20, color = "#3B3B3B"),
x = 0.02
),
paper_bgcolor = "#FAF7F0",
plot_bgcolor = "#FAF7F0",
font = list(family = "Georgia", color = "#3B3B3B"),
xaxis = list(
title = "", showgrid = FALSE, zeroline = FALSE,
range = c("2026-03-18", "2026-07-31"),
tickfont = list(size = 11)
),
yaxis = list(
title = "", showgrid = TRUE, gridcolor = "#EDE8DC",
zeroline = FALSE, tickfont = list(size = 12),
tickvals = c(1, 1.35),
ticktext = c("Ejecutivo", "Legislativo"),
range = c(0.8, 1.55)
),
legend = list(orientation = "h", y = -0.15, font = list(size = 11)),
margin = list(t = 90, b = 60),
shapes = shapes_mes,
annotations = anotaciones_mes
)
# Clic sobre un punto con nota de prensa asociada -> abre el link en pestaña nueva.
# Los puntos sin URL (campo vacío en el csv) simplemente no hacen nada al clic.
fig <- fig %>% onRender("
function(el) {
el.on('plotly_click', function(d) {
var url = d.points[0].customdata;
if (url && url !== '') {
window.open(url, '_blank');
}
});
}
")
fig
plotly, para que combine mejor
con un documento de análisis político y no se vea como un “dashboard
corporativo”.size_px (en el chunk {r paleta}) o aumentar
fig.width/fig.height.plotly solo activa el hover
exactamente sobre un punto de datos real: con únicamente 2 puntos (como
hace add_segments()), el tooltip solo aparecía pegado a los
extremos de la banda. Si una estela se ve “entrecortada” en vez de una
línea continua, es porque el rango de fechas es muy corto para el
by = "2 days" fijado — se puede achicar ese intervalo en el
chunk {r timeline-estilizada}.url del csv muestran “📰 Clic
para ver la nota de prensa” en el hover, y al hacer clic sobre el punto
se abre el link en una pestaña nueva. Los hitos sin URL (campo vacío) no
hacen nada al clic. No es necesario que todos los eventos tengan una
nota asociada.hitos a un .csv
compartidohitos_educacion.csv (mantener la columna tipo
para conservar la codificación visual; relevancia ya no
afecta el gráfico, ver nota sobre tamaño uniforme).colores_rango en el chunk {r paleta}
usando el nombre exacto del evento como llave; si no se
agrega, la estela hereda el color de su ámbito por defecto.tipo de hito
(además de los tres actuales: Proyecto de Ley, Sesión Comisión, Medida
Ejecutiva), hay que sumarlo en dos lugares del chunk
{r paleta}: el case_when de
symbol_val y la lista tipos_presentes del
chunk del gráfico, o de lo contrario ese hito se dibuja con el símbolo
por defecto (“circle”) y no aparece como su propia línea en la
leyenda..Rmd se vuelve parte fija del flujo del
Observatorio, considerar mover la paleta de colores
(colores_ambito, simbolos_tipo) a un archivo
tema_observatorio.R compartido, para mantener consistencia
visual entre informes.