SHEET_ID = "1x83kWcmNX5OJ1-aP36_liBzp7iJOz228xAkrAb5-RkE"
sheetCsvUrl = (sheet) => `https://docs.google.com/spreadsheets/d/${SHEET_ID}/gviz/tq?tqx=out:csv&sheet=${sheet}`
colors = ({
blue: "#2a78d6", blueDark: "#184f95",
green: "#008300", magenta: "#e87ba4", yellow: "#eda100",
aqua: "#1baf7a", orange: "#eb6834", violet: "#4a3aa7", red: "#e34948",
textPrimary: "#0b0b0b", textSecondary: "#52514e", muted: "#898781",
grid: "#e1e0d9", surface: "#fcfcfb"
})
SEQ = ["#cde2fb", "#9ec5f4", "#5598e7", "#2a78d6", "#1c5cab", "#184f95", "#0d366b"]
PROCESO_COLORS = ({ Conocimiento: colors.blue, Reduccion: colors.aqua, Manejo: colors.yellow })
FONT = "12px system-ui, -apple-system, 'Segoe UI', sans-serif"
tooltipEl = {
const el = document.createElement("div");
el.style.cssText = `position:fixed;pointer-events:none;z-index:1000;background:${colors.surface};
border:1px solid rgba(11,11,11,0.10);border-radius:6px;padding:8px 10px;font:${FONT};
color:${colors.textPrimary};box-shadow:0 2px 10px rgba(0,0,0,0.14);opacity:0;
transition:opacity .08s;max-width:320px;line-height:1.45`;
document.body.appendChild(el);
invalidation.then(() => el.remove());
return el;
}
function showTooltip(event, rows) {
tooltipEl.replaceChildren();
for (const r of rows) {
const div = document.createElement("div");
if (r.label) {
const strong = document.createElement("span");
strong.style.fontWeight = "600";
strong.textContent = r.value !== undefined ? `${r.label}: ` : r.label;
div.appendChild(strong);
}
if (r.value !== undefined) {
const span = document.createElement("span");
if (r.muted) span.style.color = colors.textSecondary;
span.textContent = String(r.value);
div.appendChild(span);
}
tooltipEl.appendChild(div);
}
const left = Math.min(event.clientX + 16, window.innerWidth - 336);
tooltipEl.style.left = `${left}px`;
tooltipEl.style.top = `${event.clientY + 16}px`;
tooltipEl.style.opacity = 1;
}
function hideTooltip() {
tooltipEl.style.opacity = 0;
}
function countTop(rows, column, n = Infinity, { fillna, truncate, dropna = true } = {}) {
const counts = new Map();
for (const row of rows) {
let v = row[column];
if (v === undefined || v === null || v === "") {
if (fillna !== undefined) v = fillna;
else if (dropna) continue;
}
v = String(v).normalize("NFC").trim().replace(/\s+/g, " ");
if (truncate && v.length > truncate) v = v.slice(0, truncate - 1) + "…";
counts.set(v, (counts.get(v) || 0) + 1);
}
let arr = Array.from(counts, ([label, value]) => ({ label, value }));
arr.sort((a, b) => d3.descending(a.value, b.value));
if (n !== Infinity) arr = arr.slice(0, n);
return arr;
}
function pivotByYearProceso(rows) {
const procesos = Object.keys(PROCESO_COLORS);
const filtered = rows.filter(d => d["AÑO"] != null && procesos.includes(d.PROCESO));
const years = filtered.map(d => +d["AÑO"]);
const minY = d3.min(years), maxY = d3.max(years);
const grouped = d3.rollup(filtered, v => v.length, d => +d["AÑO"], d => d.PROCESO);
const out = [];
for (let y = minY; y <= maxY; y++) {
const row = { year: y };
for (const p of procesos) row[p] = grouped.get(y)?.get(p) ?? 0;
out.push(row);
}
return out;
}
function barChartH(data, { title, color = colors.blue, marginLeft = 240, width = 700 } = {}) {
const barH = 22, gap = 8, marginTop = title ? 40 : 16, marginRight = 60, marginBottom = 10;
const height = data.length * (barH + gap) + marginTop + marginBottom;
const x = d3.scaleLinear().domain([0, d3.max(data, d => d.value) * 1.15]).range([0, width - marginLeft - marginRight]);
const y = d3.scaleBand().domain(data.map(d => d.label)).range([marginTop, height - marginBottom]).padding(0.28);
const rectH = Math.min(barH, y.bandwidth());
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", marginLeft).attr("y", 20)
.attr("font-size", 14).attr("font-weight", 600).attr("fill", colors.textPrimary).text(title);
const xAxis = svg.append("g").attr("transform", `translate(${marginLeft},0)`);
xAxis.selectAll("line").data(x.ticks(5)).join("line")
.attr("x1", d => x(d)).attr("x2", d => x(d))
.attr("y1", marginTop - 6).attr("y2", height - marginBottom)
.attr("stroke", colors.grid);
xAxis.selectAll("text").data(x.ticks(5)).join("text")
.attr("x", d => x(d)).attr("y", marginTop - 12)
.attr("text-anchor", "middle").attr("fill", colors.muted).attr("font-size", 10)
.text(d3.format(","));
const bars = svg.append("g").selectAll("rect").data(data).join("rect")
.attr("x", marginLeft)
.attr("y", d => y(d.label) + (y.bandwidth() - rectH) / 2)
.attr("width", d => Math.max(1, x(d.value)))
.attr("height", rectH)
.attr("rx", 4)
.attr("fill", color);
svg.append("g").selectAll("text").data(data).join("text")
.attr("x", marginLeft - 8).attr("y", d => y(d.label) + y.bandwidth() / 2)
.attr("dy", "0.35em").attr("text-anchor", "end").attr("fill", colors.textSecondary)
.text(d => d.label);
svg.append("g").selectAll("text").data(data).join("text")
.attr("x", d => marginLeft + x(d.value) + 6).attr("y", d => y(d.label) + y.bandwidth() / 2)
.attr("dy", "0.35em").attr("fill", colors.textPrimary).attr("font-weight", 600)
.text(d => d.value);
bars
.on("pointerenter pointermove", function (event, d) {
d3.select(this).attr("fill", colors.blueDark);
showTooltip(event, [{ label: d.label, value: `${d.value} producto(s)` }]);
})
.on("pointerleave", function () {
d3.select(this).attr("fill", color);
hideTooltip();
});
return svg.node();
}
function barChartV(data, { title, color = colors.blue, width = 700, height = 480, rotateLabels = -30 } = {}) {
const marginTop = 40, marginRight = 20, marginBottom = 130, marginLeft = 60;
const x = d3.scaleBand().domain(data.map(d => d.label)).range([marginLeft, width - marginRight]).padding(0.3);
const y = d3.scaleLinear().domain([0, d3.max(data, d => d.value) * 1.15]).range([height - marginBottom, marginTop]);
const barW = Math.min(24, x.bandwidth());
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", marginLeft).attr("y", 20)
.attr("font-size", 14).attr("font-weight", 600).attr("fill", colors.textPrimary).text(title);
const yAxis = svg.append("g");
yAxis.selectAll("line").data(y.ticks(5)).join("line")
.attr("x1", marginLeft).attr("x2", width - marginRight)
.attr("y1", d => y(d)).attr("y2", d => y(d)).attr("stroke", colors.grid);
yAxis.selectAll("text").data(y.ticks(5)).join("text")
.attr("x", marginLeft - 8).attr("y", d => y(d)).attr("dy", "0.32em")
.attr("text-anchor", "end").attr("fill", colors.muted).attr("font-size", 10)
.text(d3.format(","));
const bars = svg.append("g").selectAll("rect").data(data).join("rect")
.attr("x", d => x(d.label) + (x.bandwidth() - barW) / 2)
.attr("y", d => y(d.value))
.attr("width", barW)
.attr("height", d => y(0) - y(d.value))
.attr("rx", 4)
.attr("fill", color);
svg.append("g").selectAll("text").data(data).join("text")
.attr("x", d => x(d.label) + x.bandwidth() / 2).attr("y", d => y(d.value) - 6)
.attr("text-anchor", "middle").attr("fill", colors.textPrimary).attr("font-weight", 600).attr("font-size", 11)
.text(d => d.value);
svg.append("g").selectAll("text").data(data).join("text")
.attr("x", 0).attr("y", 0)
.attr("text-anchor", "end").attr("fill", colors.muted).attr("font-size", 11)
.attr("transform", d => `translate(${x(d.label) + x.bandwidth() / 2},${height - marginBottom + 14}) rotate(${rotateLabels})`)
.text(d => d.label);
bars
.on("pointerenter pointermove", function (event, d) {
d3.select(this).attr("fill", colors.blueDark);
showTooltip(event, [{ label: d.label, value: `${d.value} producto(s)` }]);
})
.on("pointerleave", function () {
d3.select(this).attr("fill", color);
hideTooltip();
});
return svg.node();
}
function stackedBarV(data, keys, colorMap, { title, width = 640, height = 420 } = {}) {
const marginTop = 56, marginRight = 20, marginBottom = 34, marginLeft = 50;
const x = d3.scaleBand().domain(data.map(d => d.year)).range([marginLeft, width - marginRight]).padding(0.22);
const stacked = d3.stack().keys(keys)(data);
const yMax = d3.max(stacked[stacked.length - 1], d => d[1]) || 1;
const y = d3.scaleLinear().domain([0, yMax * 1.12]).range([height - marginBottom, marginTop]);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", marginLeft).attr("y", 20)
.attr("font-size", 14).attr("font-weight", 600).attr("fill", colors.textPrimary).text(title);
const yAxis = svg.append("g");
yAxis.selectAll("line").data(y.ticks(5)).join("line")
.attr("x1", marginLeft).attr("x2", width - marginRight)
.attr("y1", d => y(d)).attr("y2", d => y(d)).attr("stroke", colors.grid);
yAxis.selectAll("text").data(y.ticks(5)).join("text")
.attr("x", marginLeft - 8).attr("y", d => y(d)).attr("dy", "0.32em")
.attr("text-anchor", "end").attr("fill", colors.muted).attr("font-size", 10)
.text(d3.format(","));
const layer = svg.append("g").selectAll("g").data(stacked).join("g")
.attr("fill", d => colorMap[d.key]);
layer.selectAll("rect")
.data(d => d.map(v => ({ y0: v[0], y1: v[1], year: v.data.year, values: v.data, key: d.key })))
.join("rect")
.attr("x", d => x(d.year))
.attr("y", d => y(d.y1) + 1)
.attr("height", d => Math.max(0, y(d.y0) - y(d.y1) - 2))
.attr("width", x.bandwidth())
.on("pointerenter pointermove", function (event, d) {
showTooltip(event, [
{ label: "Año", value: d.year },
...keys.map(k => ({ label: k, value: d.values[k], muted: k !== d.key }))
]);
})
.on("pointerleave", () => hideTooltip());
svg.append("g").selectAll("text").data(data).join("text")
.attr("x", d => x(d.year) + x.bandwidth() / 2).attr("y", height - marginBottom + 16)
.attr("text-anchor", "middle").attr("fill", colors.muted).attr("font-size", 10)
.text(d => d.year);
const legend = svg.append("g").attr("transform", `translate(${marginLeft}, ${marginTop - 30})`);
keys.forEach((k, i) => {
const gx = legend.append("g").attr("transform", `translate(${i * 120},0)`);
gx.append("rect").attr("width", 10).attr("height", 10).attr("rx", 2).attr("fill", colorMap[k]);
gx.append("text").attr("x", 14).attr("y", 9).attr("fill", colors.textSecondary).attr("font-size", 11).text(k);
});
return svg.node();
}
function seqColor(maxCount) {
return d3.scaleSequential(t => d3.interpolateRgbBasis(SEQ)(t)).domain([0, maxCount || 1]);
}
function choropleth(records, geo, { title, valueLabel = "producto(s)", width = 700, height = 640 } = {}) {
const byName = new Map(records.map(r => [r.name, r]));
const maxCount = d3.max(records, r => r.count) || 1;
const mapTop = 40, mapBottom = 46;
const projection = d3.geoMercator().fitSize([width, height - mapTop - mapBottom], geo);
const path = d3.geoPath(projection);
const color = seqColor(maxCount);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", 0).attr("y", 20)
.attr("font-size", 14).attr("font-weight", 600).attr("fill", colors.textPrimary).text(title);
const g = svg.append("g").attr("transform", `translate(0,${mapTop})`);
g.selectAll("path").data(geo.features).join("path")
.attr("d", path)
.attr("fill", d => {
const rec = byName.get(d.properties.name);
if (!rec) return "#eee";
return rec.count === 0 ? colors.grid : color(rec.count);
})
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.on("pointerenter pointermove", function (event, d) {
d3.select(this).attr("stroke-width", 1.8);
const rec = byName.get(d.properties.name);
const lines = rec && rec.lines && rec.lines.length ? rec.lines : ["Sin datos registrados"];
showTooltip(event, [
{ label: d.properties.name, value: `${rec ? rec.count : 0} ${valueLabel}` },
...lines.map(l => ({ label: "", value: `• ${l}`, muted: true }))
]);
})
.on("pointerleave", function () {
d3.select(this).attr("stroke-width", 0.8);
hideTooltip();
});
const legendWidth = 160, legendHeight = 10;
const swatchSize = 10, swatchGap = 8;
const gradX = width - legendWidth - 12, legendY = height - 28;
const zeroX = gradX - swatchGap - swatchSize;
const gradId = `grad-${Math.random().toString(36).slice(2)}`;
const grad = svg.append("defs").append("linearGradient").attr("id", gradId);
SEQ.forEach((c, i) => grad.append("stop").attr("offset", `${(i / (SEQ.length - 1)) * 100}%`).attr("stop-color", c));
svg.append("rect").attr("x", zeroX).attr("y", legendY).attr("width", swatchSize).attr("height", legendHeight)
.attr("rx", 2).attr("fill", colors.grid);
svg.append("text").attr("x", zeroX + swatchSize / 2).attr("y", legendY - 4)
.attr("text-anchor", "middle").attr("font-size", 10).attr("fill", colors.muted).text("0");
svg.append("rect").attr("x", gradX).attr("y", legendY).attr("width", legendWidth).attr("height", legendHeight)
.attr("rx", 2).attr("fill", `url(#${gradId})`);
svg.append("text").attr("x", gradX + legendWidth).attr("y", legendY - 4)
.attr("text-anchor", "end").attr("font-size", 10).attr("fill", colors.muted).text(maxCount);
svg.append("text").attr("x", zeroX + (gradX + legendWidth - zeroX) / 2).attr("y", legendY + legendHeight + 12)
.attr("text-anchor", "middle").attr("font-size", 10).attr("fill", colors.muted).text(valueLabel);
return svg.node();
}
function rankBarChart(records, { title, width = 300 } = {}) {
const data = records.filter(d => d.count > 0).sort((a, b) => d3.descending(a.count, b.count));
const max = d3.max(data, d => d.count) || 1;
const maxDomain = d3.max(records, d => d.count) || 1;
const color = seqColor(maxDomain);
return html`<div style="width:${width}px;flex:0 0 auto;font:${FONT}">
${title ? html`<div style="font-weight:600;font-size:14px;margin-bottom:12px;color:${colors.textPrimary}">${title}</div>` : ""}
<style>
.bar-chart{display:flex;flex-direction:column;gap:8px}
.bar-row{display:grid;grid-template-columns:150px 1fr 34px;align-items:center;gap:10px}
.bar-row .name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:${colors.textSecondary}}
.bar-row .track{background:${colors.grid};border-radius:6px;height:14px;overflow:hidden}
.bar-row .fill{height:100%;border-radius:6px}
.bar-row .val{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;color:${colors.textPrimary}}
</style>
<div class="bar-chart">
${data.map(d => {
const w = (d.count / max * 100).toFixed(1);
return html`<div class="bar-row">
<span class="name" title="${d.name}">${d.name}</span>
<div class="track"><div class="fill" style="width:${w}%;background:${color(d.count)}"></div></div>
<span class="val">${d.count.toLocaleString("es-CO")}</span>
</div>`;
})}
</div>
</div>`;
}
function demandFigure(records, geo, { mapTitle, barTitle, valueLabel = "demanda(s) ALTA", mapWidth = 460, mapHeight = 520, barWidth = 300 } = {}) {
const left = choropleth(records, geo, { title: mapTitle, valueLabel, width: mapWidth, height: mapHeight });
const right = rankBarChart(records, { title: barTitle, width: barWidth });
const wrap = document.createElement("div");
wrap.style.cssText = "display:flex;gap:24px;flex-wrap:nowrap;align-items:flex-start;overflow-x:auto";
wrap.append(left, right);
return wrap;
}In [1]:
In [2]:
INSTITUCION_FIX = ({
"Universidad de Los Andes": "Universidad de los Andes",
"Politecnico Colombiano Jaime Isaza Cadavid": "Politécnico Colombiano Jaime Isaza Cadavid",
"Universidad del Atlantico": "Universidad del Atlántico",
"Universidad Nacional de Colombia, Sede Manizales": "Universidad Nacional de Colombia, sede Manizales",
"Universidad de la Sabana": "Universidad de La Sabana",
"Universidad Distrital Francisco Jose de Caldas": "Universidad Distrital Francisco José de Caldas",
"Servicio Geologico Colombiano": "Servicio Geológico Colombiano",
"Universidad Nacional de Colombia, sede Medellin": "Universidad Nacional de Colombia, sede Medellín",
"Universidad Nacional de Colombia, Sede Medellín": "Universidad Nacional de Colombia, sede Medellín",
"Universidad Nacional de Colombia, sede Bogota": "Universidad Nacional de Colombia, sede Bogotá",
"Universidad de Medellin": "Universidad de Medellín",
"Universidad de Ibague": "Universidad de Ibagué",
"Universidad Santo Tomas": "Universidad Santo Tomás",
"Universidad de la Salle": "Universidad de La Salle",
"Universidad de la Guajira": "Universidad de La Guajira",
"Fundacion Universitaria Autónoma de las Américas": "Fundación Universitaria Autónoma de las Américas",
"Universidad Catolica de Colombia": "Universidad Católica de Colombia",
"Instituto de Investigaciones Marinas y Costeras (INVEMAR)": "Instituto de Investigaciones Marinas y Costeras (Invemar)",
})
vigilancia = d3.csv(sheetCsvUrl("Vigilancia_CTeI"), (d) => {
const row = d3.autoType(d);
const inst = (row["INSTITUCION COLOMBIANA"] ?? "").trim().replace(/\s+/g, " ");
row["INSTITUCION COLOMBIANA"] = INSTITUCION_FIX[inst] ?? inst;
return row;
})In [1]:
import json
import urllib.request
import pandas as pd
SHEET_ID = "1x83kWcmNX5OJ1-aP36_liBzp7iJOz228xAkrAb5-RkE"
GEOJSON_URL = "https://raw.githubusercontent.com/caticoa3/colombia_mapa/master/co_2018_MGN_DPTO_POLITICO.geojson"
def sheet_csv_url(sheet: str) -> str:
return f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet={sheet}"
# Map the geojson's official DIVIPOLA names onto the sheets' department spelling.
NAME_MAP = {
"AMAZONAS": "Amazonas", "ANTIOQUIA": "Antioquia", "ARAUCA": "Arauca",
"ARCHIPIÉLAGO DE SAN ANDRÉS, PROVIDENCIA Y SANTA CATALINA": "San Andrés, Providencia y Santa Catalina",
"ATLÁNTICO": "Atlántico", "BOGOTÁ, D.C.": "Bogotá D.C.", "BOLÍVAR": "Bolívar", "BOYACÁ": "Boyacá",
"CALDAS": "Caldas", "CAQUETÁ": "Caquetá", "CASANARE": "Casanare", "CAUCA": "Cauca", "CESAR": "Cesar",
"CHOCÓ": "Chocó", "CUNDINAMARCA": "Cundinamarca", "CÓRDOBA": "Córdoba", "GUAINÍA": "Guainía",
"GUAVIARE": "Guaviare", "HUILA": "Huila", "LA GUAJIRA": "La Guajira", "MAGDALENA": "Magdalena",
"META": "Meta", "NARIÑO": "Nariño", "NORTE DE SANTANDER": "Norte de Santander", "PUTUMAYO": "Putumayo",
"QUINDIO": "Quindío", "RISARALDA": "Risaralda", "SANTANDER": "Santander", "SUCRE": "Sucre",
"TOLIMA": "Tolima", "VALLE DEL CAUCA": "Valle del Cauca", "VAUPÉS": "Vaupés", "VICHADA": "Vichada",
}
with urllib.request.urlopen(GEOJSON_URL) as resp:
_geo = json.load(resp)
for _f in _geo["features"]:
_f["properties"]["name"] = NAME_MAP[_f["properties"]["DPTO_CNMBR"]]
ALL_DEPT_NAMES = sorted(set(NAME_MAP.values()))
ojs_define(colombia_geo=_geo)In [2]:
def demandas_records(sheet: str, name_fix: dict) -> list[dict]:
df = pd.read_csv(sheet_csv_url(sheet))
df["departamento"] = df["DEPARTAMENTO"].str.strip().replace(name_fix)
df["relacion"] = df["RELACION CON GRD (ALTA, MEDIA, BAJA)"].astype(str).str.strip().str.upper()
alta = df[df["relacion"].isin(["ALTA", "ALTO"])]
counts = alta.groupby("departamento").size()
retos = alta.groupby("departamento")["RETO"].apply(lambda s: list(dict.fromkeys(s))[:3])
return [
{
"name": n,
"count": int(counts.get(n, 0)),
"lines": [r[:70] for r in retos.get(n, [])],
}
for n in ALL_DEPT_NAMES
]
# The DEPARTAMENTO cell for San Andrés is truncated at the source; reconstruct it.
demandas_2022 = demandas_records(
"Demandas-Territoriales-2022-24",
{"rés, Providencia y Santa": "San Andrés, Providencia y Santa Catalina"},
)
# This sheet spells the capital district "Bogotá"; align it with "Bogotá D.C.".
demandas_2025 = demandas_records("Demandas-Territoriales-2025-26", {"Bogotá": "Bogotá D.C."})
ojs_define(demandas_2022=demandas_2022, demandas_2025=demandas_2025)In [3]:
from collections import Counter, defaultdict
# Reconcile the free-text DEPARTAMENTO column (57 raw variants: accent inconsistencies,
# city names, faculty/institute names, foreign countries, explicit "no encontrado") with
# the official DIVIPOLA department names used by the geojson.
FIX = {
"Antioquía": "Antioquia", "Bogotá": "Bogotá D.C.", "Bogotá, D.C.": "Bogotá D.C.",
"Bolivar": "Bolívar", "Guajira": "La Guajira", "Medellin": "Antioquia",
"Quindio": "Quindío", "San Andrés y Providencia": "San Andrés, Providencia y Santa Catalina",
}
# Multi-department credits (e.g. co-authored across two departments) count toward both.
COMBO = {"Bolívar / Atlántico": ["Bolívar", "Atlántico"]}
# Foreign countries, generic faculty/institute names, and explicit "unknown" placeholders
# carry no usable department-level geography and are excluded from the map.
EXCLUDE = {
"#NAME?", "Alemania", "Arabia Saudita", "Argentina", "Cambridge, UK", "Chile",
"Colombia / Mexico", "Ecuador", "España", "Francia", "Japon", "Mexico", "México",
"N/D", "No aplica", "No encontrado",
"CENIGAA (Centro de Investigaciones GeoAgroAmbiental)",
"Departamento de Física y Geociencias",
"Departamento de Geociencias y Medio Ambiente",
"Departamento de Ingeniería Civil / Facultad de Ingeniería",
"Escuela de Ciencias e Ingeniería – Programa de Ciencias del Sistema Tierra",
"Facultad de Ingeniería", "Observatorio Astronómico Nacional", "Portugal",
}
_df_vig = pd.read_csv(sheet_csv_url("Vigilancia_CTeI")).dropna(subset=["ID"])
_counts: Counter = Counter()
_top_temas: defaultdict = defaultdict(Counter)
for _, row in _df_vig.dropna(subset=["DEPARTAMENTO DE COLOMBIA"]).iterrows():
raw = row["DEPARTAMENTO DE COLOMBIA"].strip()
depts = COMBO.get(raw)
if depts is None:
if raw in EXCLUDE:
continue
depts = [FIX.get(raw, raw)]
for d in depts:
if d in ALL_DEPT_NAMES:
_counts[d] += 1
if pd.notna(row["TEMA"]):
_top_temas[d][row["TEMA"]] += 1
vigilancia_map_data = [
{
"name": n,
"count": int(_counts.get(n, 0)),
"lines": [t for t, _ in _top_temas[n].most_common(3)],
}
for n in ALL_DEPT_NAMES
]
ojs_define(vigilancia_map_data=vigilancia_map_data)1 Introducción
Para monitorear la implementación de la CTeI en el Marco de Sendai, el progreso en las 58 acciones de la Hoja de Ruta de Ciencia y Tecnología constituye un referente para determinar el estado de la ciencia y la tecnología en GRD en los países. Una evaluación del estado de la CTeI es un diagnóstico sistemático y periódico de las capacidades científicas, tecnológicas e institucionales. También un diagnóstico de la producción, disponibilidad y aplicación del conocimiento, la innovación, la articulación entre ciencia, política y sociedad y las brechas que limitan la generación y utilización de evidencia, herramientas y soluciones para comprender, anticipar, prevenir y reducir el riesgo de desastres (UNDRR, 2016, 2022, 2024).
Colombia no ha realizado evaluaciones sistemáticas del estado de la CTeI en GRD. Realizarlas constituye un instrumento de inteligencia estratégica para el SNGRD al analizar sus capacidades e infraestructuras, la pertinencia y aplicación del conocimiento frente a las necesidades de la gestión del riesgo, así como sus procesos de adopción, transferencia, articulación e implementación territorial. De esta manera, sus resultados pueden orientar las decisiones de inversión, coordinación institucional, fortalecimiento de capacidades y formulación de agendas de investigación, innovación, desarrollo tecnológico y política científica.
La evolución de las evaluaciones promovidas por UNDRR han transitado del análisis de la producción de conocimiento e inventario de capacidades hacia la consideración de su aplicación, innovación, gobernanza, capacidades, localización y articulación con otros actores y sectores (UNDRR, 2020, 2022, 2024).
Esta sección presenta la primera evaluación nacional del estado de la CTeI de los desastres a través de cuatro dimensiones:
- Financiación de la ciencia y la tecnología
- Financiación de la ciencia en convocatorias nacionales.
- Demandas territoriales para el Sistema General de Regalías.
- Capacidades en ciencia y la tecnología
- Inventario de los institutos y grupos de investigación en el país.
- Inventario de postgrados en GRD.
- Producción científica colombiana en GRD
- Tamaño de la producción científica por temáticas, departamentos y afiliaciones.
- Ciencia abierta.
- Producción de tecnologías en el país.
- Transferencia de ciencia y tecnología, e impacto local
- Impacto de proyectos de CTeI en el MEGIR.
Esta evaluación tiene una correspondencia directa o parcial en 15–18 acciones de la Hoja de Ruta de Ciencia y Tecnología (Anexo 1). Sin embargo, no identifica los nuevos problemas o tendencias científicas y tecnológicas que están cambiando en GRD, lo cual se abordará en la actualización del documento de líneas de investigación en GRD. Por favor consulte la metodología de la evaluación del estado en cada sección y en la sección de Anexos.
2 Financiación de la ciencia y tecnología de los desastres
2.1 Convocatorias nacionales de CTeI
Junto con los vacíos de conocimiento, las necesidades de la sociedad, y el talento humano, la financiación es uno de los motores dinamizadores de la CTeI. En Colombia está bien establecido que Minciencias y el Sistema General de Regalías constituyen la principal fuente de financiación pública para proyectos de CTeI en todas las áreas de conocimiento, incluyendo la GRD. A continuación se analiza la participación de la GRD en las convocatorias Minciencias.
Durante el último cuatrienio (2022–2026), Minciencias lanzó 92 convocatorias, de las cuales 41 declaran en sus términos de referencia la posibilidad de postular proyectos o formar capital humano en temáticas asociadas a la GRD, lo que evidencia una oferta de financiación sostenida en el tiempo y diversificada hacia todas las áreas de conocimiento en GRD, no solo las ingenierías. Así, la ciencia de los desastres se ha posicionado como relevante en gran parte de las convocatorias disponibles, reflejando una creciente alineación entre la agenda de financiamiento de Minciencias y las prioridades nacionales y de los territorios.
In [4]:
financiacionNal = d3.csv(sheetCsvUrl("Financiacion-Nal"))In [5]:
convocatorias = financiacionNal
.slice(18, 67) // filas 20-68 de la hoja (ID N-19 a N-66); fila 1 = encabezado
.filter(d => d["CONTIENE GRD"]?.trim() === "Si")
.map(d => ({
institucion: (d.INSTITUCION_FINANCIADORA ?? "").trim(),
anio: (d["AÑO"] ?? "").trim(),
area: (d.AREA ?? "").trim(),
titulo: (d["TITULO OPORTUNIDAD DE FINANCIACION"] ?? "").trim(),
monto: (d.MONTO ?? "").trim(),
url: (d.URL ?? "").trim()
}))
YEAR_COLORS = ({ 2022: colors.blueDark, 2023: colors.blue, 2024: colors.aqua, 2025: colors.yellow, 2026: colors.orange })In [6]:
{
const COLS = [
{ key: "institucion", label: "Institución financiadora", sortable: true, width: "170px" },
{ key: "anio", label: "Año", sortable: true, width: "70px" },
{ key: "area", label: "Área", sortable: true },
{ key: "titulo", label: "Título de la convocatoria", sortable: true },
{ key: "monto", label: "Monto", sortable: false, width: "220px" },
{ key: "url", label: "Enlace", sortable: false, width: "84px" }
];
const anios = Array.from(new Set(convocatorias.map(d => d.anio))).sort();
const state = { query: "", anio: "", sortKey: "anio", sortDir: -1 };
const root = document.createElement("div");
root.style.cssText = `font:${FONT};color:${colors.textPrimary}`;
const controls = document.createElement("div");
controls.style.cssText = "display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:10px";
const searchInput = document.createElement("input");
searchInput.type = "search";
searchInput.placeholder = "Buscar por institución, área o título...";
searchInput.style.cssText = `flex:1;min-width:220px;padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const yearSelect = document.createElement("select");
yearSelect.style.cssText = `padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const optAll = document.createElement("option");
optAll.value = "";
optAll.textContent = "Todos los años";
yearSelect.appendChild(optAll);
for (const a of anios) {
const opt = document.createElement("option");
opt.value = a;
opt.textContent = a;
yearSelect.appendChild(opt);
}
const countEl = document.createElement("div");
countEl.style.cssText = `color:${colors.textSecondary};font-size:11px;white-space:nowrap;margin-left:auto`;
controls.append(searchInput, yearSelect, countEl);
const tableWrap = document.createElement("div");
tableWrap.style.cssText = `max-height:520px;overflow:auto;border:1px solid ${colors.grid};border-radius:8px`;
const table = document.createElement("table");
table.style.cssText = "width:100%;border-collapse:collapse;font-size:12px";
const thead = table.createTHead();
const headRow = thead.insertRow();
headRow.style.cssText = `position:sticky;top:0;background:${colors.surface};z-index:1`;
for (const col of COLS) {
const th = document.createElement("th");
th.textContent = col.label;
th.style.cssText = `text-align:left;padding:8px 10px;border-bottom:2px solid ${colors.grid};cursor:${col.sortable ? "pointer" : "default"};white-space:nowrap;user-select:none${col.width ? `;width:${col.width}` : ""}`;
if (col.sortable) {
const arrow = document.createElement("span");
arrow.style.cssText = "margin-left:4px;opacity:0.5;font-size:10px";
th.appendChild(arrow);
th.addEventListener("click", () => {
if (state.sortKey === col.key) state.sortDir *= -1;
else { state.sortKey = col.key; state.sortDir = 1; }
render();
});
}
headRow.appendChild(th);
}
const tbody = table.createTBody();
table.append(thead, tbody);
tableWrap.appendChild(table);
root.append(controls, tableWrap);
function badge(anio) {
const span = document.createElement("span");
span.textContent = anio || "s/a";
span.style.cssText = `display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;color:#fff;background:${YEAR_COLORS[anio] ?? colors.muted}`;
return span;
}
function render() {
const q = state.query.trim().toLowerCase();
const filtered = convocatorias.filter(d => {
if (state.anio !== "" && d.anio !== state.anio) return false;
if (q === "") return true;
return (d.institucion + " " + d.area + " " + d.titulo).toLowerCase().includes(q);
});
filtered.sort((a, b) => {
let av = a[state.sortKey], bv = b[state.sortKey];
if (state.sortKey === "anio") { av = +av || 0; bv = +bv || 0; }
if (av < bv) return -1 * state.sortDir;
if (av > bv) return 1 * state.sortDir;
return 0;
});
Array.from(headRow.children).forEach((th, i) => {
const arrow = th.querySelector("span");
if (arrow) arrow.textContent = state.sortKey === COLS[i].key ? (state.sortDir === 1 ? "▲" : "▼") : "";
});
tbody.replaceChildren();
for (const d of filtered) {
const tr = tbody.insertRow();
tr.style.borderBottom = `1px solid ${colors.grid}`;
tr.addEventListener("mouseenter", () => tr.style.background = "rgba(42,120,214,0.06)");
tr.addEventListener("mouseleave", () => tr.style.background = "");
const instTd = tr.insertCell();
instTd.textContent = d.institucion;
instTd.style.cssText = "padding:6px 10px;vertical-align:top";
const anioTd = tr.insertCell();
anioTd.style.cssText = "padding:6px 10px;vertical-align:top";
anioTd.appendChild(badge(d.anio));
[d.area, d.titulo, d.monto].forEach(v => {
const td = tr.insertCell();
td.textContent = v || "";
td.style.cssText = "padding:6px 10px;vertical-align:top";
});
const linkTd = tr.insertCell();
linkTd.style.cssText = "padding:6px 10px;vertical-align:top";
if (d.url) {
const a = document.createElement("a");
a.href = d.url;
a.target = "_blank";
a.rel = "noopener";
a.textContent = "Ver ↗";
a.style.color = colors.blue;
linkTd.appendChild(a);
}
}
countEl.textContent = `${filtered.length} de ${convocatorias.length} convocatorias`;
}
searchInput.addEventListener("input", () => { state.query = searchInput.value; render(); });
yearSelect.addEventListener("change", () => { state.anio = yearSelect.value; render(); });
render();
return root;
return table;
}Tabla 1. Convocatorias nacionales de CTeI relacionadas con la GRD. Tabla interactiva con opciones de búsqueda y filtros por año y columnas ordenables.
Entre 2022 y 2026 existieron seis convocatorias que incorporaron líneas temáticas explícitas en gestión del riesgo de desastres (Caja 1), en particular las convocatorias 1045 de 2023, 51 de 2025 y la 52 de 2026 que se dirigen a atender situaciones de emergencia y desastre.
No obstante, para tener la capacidad de monitorear la inversión en ciencia y tecnología en GRD como lo recomienda la Hoja de Ruta en Ciencia y Tecnología (3.1.2 y 3.1.3), se requiere conocer los proyectos relacionados con GRD financiados por las convocatorias (i.e. financiación disponible y movilizada) por medio de un sistema de información que sistematice los proyectos y sus productos. Actualmente ScienTI posee una limitada capacidad de consulta del conocimiento producido por los proyectos financiados en el país, aspecto que además limita su aprovechamiento por medio de acciones de transferencia de conocimiento y tecnología.
2.2 Financiación nacional e internacional en productos de conocimiento
Esta medición considera principalmente productos de conocimiento (ver definición de conocimiento en @box-tipologias Caja 2 y tipologías de productos en Anexo 2). Entre 2020 y 2025 se analizó una muestra de 507 productos de nuevo conocimiento, a partir de la cual se extrajo información de financiador de las secciones Agradecimientos o Financiación (Anexo 4). Este análisis establece la presencia o ausencia de financiación, más no su monto.
La mayoría de productos de nuevo conocimiento tuvo financiación (Figura 2). Se encontró que el 62,6% (321 de 513) reporta tener financiación de algún tipo, mientras que el 37,4% (192) no declaró financiación.
In [7]:
barChartH(countSiNo(financiacionCTeI, "DECLARA_FINANCIADOR"), {
title: "Declaración de financiador", marginLeft: 60, width: 400
})Financiacion-CTeI.
In [8]:
function siYNacInt(rows, tipo) {
return rows.filter(d =>
(d.DECLARA_FINANCIADOR ?? "").toString().trim().toLowerCase() === "si" &&
(d.FINANCIADOR_NAL_O_INTERNAL ?? "").toString().trim().toLowerCase() === tipo
);
}En cuanto a la institución y país de origen de la financiación nacional e internacional, Minciencias constituye la principal fuente de financiación pública para proyectos de CTeI en todas las áreas de conocimiento en Colombia, incluyendo la GRD, financiando el 12,8% (41 de 321) de los productos con financiación declarada (Figura 3).
In [9]:
barChartH(
countTop(siYNacInt(financiacionCTeI, "nacional"), "FINANCIADOR", 10, { truncate: 45 }),
{ title: "Principales financiadores nacionales (top 10)", marginLeft: 280, width: 720 }
)Financiacion-CTeI.
A nivel internacional las fuentes de financiación fueron: Japan Society for the Promotion of Science; Ministry of Education, Culture, Sports, Science, and Technology of Japan; y UKRI’s Global Challenge Research Fund (Figura 4).
In [10]:
barChartH(
countTop(siYNacInt(financiacionCTeI, "internacional"), "FINANCIADOR", 10, { truncate: 45 }),
{ title: "Principales financiadores internacionales (top 10)", marginLeft: 280, width: 720 }
)Financiacion-CTeI.
2.3 Demandas territoriales de los bienios 2023-2024 y 2025-2026
Las demandas territoriales se definen como los problemas, las necesidades o las oportunidades de los territorios que pueden solucionarse mediante la CTeI como lo declara la Ley 2056 de 2020 (Congreso de Colombia, 2020). Cada dos años, los CODECTI tienen la responsabilidad de generar las declaraciones de las demandas territoriales, las cuales junto con las líneas temáticas priorizadas por Minciencias determinarán la elegibilidad de proyectos en CTeI. A continuación se analiza la relación del número de demandas territoriales con alta relación a GRD para los bienios 2023–2024 y 2025–2026.
2.3.1 Bienio 2023-2024
El ejercicio de priorización de demandas territoriales de los CODECTI produjeron en 27 departamentos 71 declaraciones (de un total de 397) con alta relación a la GRD (retos 1 a 6).
In [11]:
demandFigure(demandas_2022, colombia_geo, {
mapTitle: "Mapa de demandas territoriales con relación ALTA a GRD, por departamento (bienio 2023-2024)",
barTitle: "Ranking de departamentos por demandas ALTA (bienio 2023-2024)"
})2.3.2 Bienio 2025-2026
Para el bienio 2025–2026 se presentaron 20 declaraciones (de un total de 430) con alta relación con la GRD en 14 departamentos (retos 1 y 6). Se destaca que el departamento de Caldas en los dos bienios ha presentado consistentemente demandas territoriales en GRD.
In [12]:
demandFigure(demandas_2025, colombia_geo, {
mapTitle: "Mapa de demandas territoriales con relación ALTA a GRD, por departamento (bienio 2025-2026)",
barTitle: "Ranking de departamentos por demandas ALTA (bienio 2025-2026)"
})Se concluye que en el bienio 2023–2024 existió un mayor número de demandas territoriales que en el bienio 2025–2026. Se recomienda que para el bienio 2027–2028 se promuevan las demandas territoriales relacionadas con GRD para brindar oportunidades de financiación para proyectos en los departamentos.
3 Capacidades nacionales en ciencia y la tecnología
3.1 Institutos y grupos de investigación
Un grupo de investigación, desarrollo tecnológico o de innovación es un conjunto de personas que interactúan para investigar y generar productos de conocimiento según un plan de trabajo. Un grupo se reconoce como tal al demostrar resultados verificables derivados de proyectos y actividades de CTeI. Un centro o instituto de investigación es una organización pública, privada o mixta independiente que tiene como misión institucional desarrollar diversas actividades de investigación (básica o aplicada), con líneas de investigación declaradas y un propósito científico específico. En Colombia existen 6.322 grupos reconocidos de investigación, desarrollo tecnológico o innovación (Minciencias, 2025), así como 40 centros/institutos de investigación reconocidos como actores del SNCTeI.
Acorde a la Plataforma ScienTI de Minciencias se estima existen 58 grupos de investigación con una relación directa en GRD (Tabla 3). Los grupos corresponden a cuatro grandes áreas de conocimiento: ingeniería y tecnología (18 grupos), ciencias naturales (15 grupos), ciencias médicas y de la salud (7 grupos), ciencias sociales (7 grupos). Las ciudades con mayor cantidad de grupos son Bogotá, D.C. (20), Medellín (6), Cali (5), y Barranquilla (5). Sin embargo, es probable que una proporción significativa del conocimiento relacionado con la GRD se produzca por grupos con líneas de investigación no declaradas explícitamente a desastres en la plataforma ScienTI. Por ejemplo, el número de grupos de investigación puede ser mayor al sumar áreas relacionadas como 15 grupos en geotecnia, 7 grupos de soluciones basadas en la naturaleza, 6 grupos en astrofísica, 3 grupos en ordenamiento territorial, o alguno de los 358 grupos en salud pública citando algunas áreas.
Así mismo, se identificaron 13 institutos de investigación de los cuales ocho poseen reconocimiento de Minciencias y cuatro centros de ciencia reconocidos. Estos valores pueden ser mayores entendiendo que cerca del 80% de los 40 centros/institutos de investigación y de los 155 centro de ciencia reconocidos podrían tener capacidades científicas para investigar en ciencia de los desastres (i.e., investigar en al menos uno de los ocho grupos de amenazas definidos por los Hazard Information Profiles de UNDRR-ISC). Estas capacidades muestran que desde hace más de una década en Colombia existen institutos, centros y grupos de investigación bien establecidos, altamente productivos, y con reconocimiento.
In [13]:
LIBRO_DATOS_ID = "188MHG-swqZfxKezyw8_U0psrdbC-B3X4"
libroDatosCsvUrl = (sheet) => `https://docs.google.com/spreadsheets/d/${LIBRO_DATOS_ID}/gviz/tq?tqx=out:csv&sheet=${encodeURIComponent(sheet)}`
CATEGORIA_COLORS = ({
A1: colors.blueDark,
A: colors.blue,
B: colors.aqua,
C: colors.yellow,
RECONOCIDO: colors.violet,
"": colors.muted
})In [14]:
grupos = {
const rows = await d3.text(libroDatosCsvUrl("Grupos_investigación")).then(d3.csvParseRows);
// Columnas A, B, C, D, F, H, L, N de la hoja
return rows.slice(1)
.filter(r => (r[0] ?? "").trim() !== "" && (r[2] ?? "").trim() !== "")
.map(r => ({
id: (r[0] ?? "").trim(),
actualizacion: (r[1] ?? "").trim(),
nombre: (r[2] ?? "").trim(),
institucion: (r[3] ?? "").trim().replace(/\s+/g, " "),
ciudad: (r[5] ?? "").trim(),
area: (r[7] ?? "").trim(),
gruplac: (r[11] ?? "").trim(),
categoria: (r[13] ?? "").trim().toUpperCase()
}));
}In [15]:
{
const COLS = [
{ key: "id", label: "ID", sortable: true, width: "44px" },
{ key: "actualizacion", label: "Actualización", sortable: true, width: "104px" },
{ key: "nombre", label: "Nombre del grupo", sortable: true },
{ key: "institucion", label: "Institución", sortable: true },
{ key: "ciudad", label: "Ciudad", sortable: true, width: "128px" },
{ key: "area", label: "Área OCDE", sortable: true },
{ key: "gruplac", label: "GrupLAC", sortable: false, width: "84px" },
{ key: "categoria", label: "Categoría", sortable: true, width: "96px" }
];
const categorias = Array.from(new Set(grupos.map(d => d.categoria))).sort();
const state = { query: "", categoria: "", sortKey: "nombre", sortDir: 1 };
const root = document.createElement("div");
root.style.cssText = `font:${FONT};color:${colors.textPrimary}`;
const controls = document.createElement("div");
controls.style.cssText = "display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:10px";
const searchInput = document.createElement("input");
searchInput.type = "search";
searchInput.placeholder = "Buscar por nombre, institución, ciudad o área...";
searchInput.style.cssText = `flex:1;min-width:220px;padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const catSelect = document.createElement("select");
catSelect.style.cssText = `padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const optAll = document.createElement("option");
optAll.value = "";
optAll.textContent = "Todas las categorías";
catSelect.appendChild(optAll);
for (const c of categorias) {
const opt = document.createElement("option");
opt.value = c;
opt.textContent = c === "" ? "Sin clasificar" : c;
catSelect.appendChild(opt);
}
const countEl = document.createElement("div");
countEl.style.cssText = `color:${colors.textSecondary};font-size:11px;white-space:nowrap;margin-left:auto`;
controls.append(searchInput, catSelect, countEl);
const tableWrap = document.createElement("div");
tableWrap.style.cssText = `max-height:520px;overflow:auto;border:1px solid ${colors.grid};border-radius:8px`;
const table = document.createElement("table");
table.style.cssText = "width:100%;border-collapse:collapse;font-size:12px";
const thead = table.createTHead();
const headRow = thead.insertRow();
headRow.style.cssText = `position:sticky;top:0;background:${colors.surface};z-index:1`;
for (const col of COLS) {
const th = document.createElement("th");
th.textContent = col.label;
th.style.cssText = `text-align:left;padding:8px 10px;border-bottom:2px solid ${colors.grid};cursor:${col.sortable ? "pointer" : "default"};white-space:nowrap;user-select:none${col.width ? `;width:${col.width}` : ""}`;
if (col.sortable) {
const arrow = document.createElement("span");
arrow.style.cssText = "margin-left:4px;opacity:0.5;font-size:10px";
th.appendChild(arrow);
th.addEventListener("click", () => {
if (state.sortKey === col.key) state.sortDir *= -1;
else { state.sortKey = col.key; state.sortDir = 1; }
render();
});
}
headRow.appendChild(th);
}
const tbody = table.createTBody();
table.append(thead, tbody);
tableWrap.appendChild(table);
root.append(controls, tableWrap);
function badge(cat) {
const span = document.createElement("span");
span.textContent = cat === "" ? "s/c" : cat;
span.style.cssText = `display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;color:#fff;background:${CATEGORIA_COLORS[cat] ?? colors.muted}`;
return span;
}
function render() {
const q = state.query.trim().toLowerCase();
const filtered = grupos.filter(d => {
if (state.categoria !== "" && d.categoria !== state.categoria) return false;
if (q === "") return true;
return (d.nombre + " " + d.institucion + " " + d.ciudad + " " + d.area)
.toLowerCase().includes(q);
});
filtered.sort((a, b) => {
let av = a[state.sortKey], bv = b[state.sortKey];
if (state.sortKey === "id") { av = +av || 0; bv = +bv || 0; }
if (av < bv) return -1 * state.sortDir;
if (av > bv) return 1 * state.sortDir;
return 0;
});
Array.from(headRow.children).forEach((th, i) => {
const arrow = th.querySelector("span");
if (arrow) arrow.textContent = state.sortKey === COLS[i].key ? (state.sortDir === 1 ? "▲" : "▼") : "";
});
tbody.replaceChildren();
for (const d of filtered) {
const tr = tbody.insertRow();
tr.style.borderBottom = `1px solid ${colors.grid}`;
tr.addEventListener("mouseenter", () => tr.style.background = "rgba(42,120,214,0.06)");
tr.addEventListener("mouseleave", () => tr.style.background = "");
[d.id, d.actualizacion, d.nombre, d.institucion, d.ciudad, d.area].forEach(v => {
const td = tr.insertCell();
td.textContent = v || "";
td.style.cssText = "padding:6px 10px;vertical-align:top";
});
const gruplacTd = tr.insertCell();
gruplacTd.style.cssText = "padding:6px 10px;vertical-align:top";
if (d.gruplac) {
const a = document.createElement("a");
a.href = d.gruplac;
a.target = "_blank";
a.rel = "noopener";
a.textContent = "Ver ↗";
a.style.color = colors.blue;
gruplacTd.appendChild(a);
}
const catTd = tr.insertCell();
catTd.style.cssText = "padding:6px 10px;vertical-align:top";
catTd.appendChild(badge(d.categoria));
}
countEl.textContent = `${filtered.length} de ${grupos.length} grupos`;
}
searchInput.addEventListener("input", () => { state.query = searchInput.value; render(); });
catSelect.addEventListener("change", () => { state.categoria = catSelect.value; render(); });
render();
return root;
}Grupos_investigación del libro de datos. Tabla interactiva: buscador, filtro por categoría y columnas ordenables.
En la siguiente sección se analiza la producción científica relacionada con la gestión del riesgo de desastres (GRD). Sin embargo, existen limitaciones para atribuir esta producción a los grupos de investigación identificados. Los metadatos bibliográficos de bases de datos como Scopus registran las afiliaciones institucionales de los autores, pero los metadatos no contienen la pertenencia a estos grupos de investigación en la mayoría de los casos. En consecuencia, no es posible establecer una correspondencia directa entre la producción científica identificada y un grupo de investigación particular. Estos grupos e investigadores producen investigaciones relevantes para la ciencia de los desastres, pero su contribución sólo puede identificarse mediante el análisis de publicaciones indexadas en Scopus más no por ScienTi de Minciencias.
Esta situación evidencia, por una parte, el carácter inherentemente interdisciplinario de la ciencia de los desastres, cuya producción trasciende las estructuras formales como los institutos y grupos de investigación. Por otra parte, la existencia de una producción significativa sobre GRD generada desde grupos que no se reconocen dentro de este campo también puede indicar una fragmentación disciplinaria, en la que conocimientos relevantes para el riesgo de desastres se producen desde comunidades científicas aisladas y sin una articulación explícita bajo el marco conceptual común de la GRD.
3.2 Inventario de postgrados en gestión del riesgo de desastres
Existe interés en las universidades en ofrecer programas de postgrado en GRD. Hay disponibles 10 especializaciones y 13 programas de maestrías en GRD (Tabla 2). No existe un programa acreditado de doctorado, sino el desarrollo de trabajos de doctorado en GRD en programas de doctorados acreditados existentes, particularmente en programas de ingenierías.
In [16]:
postgrados = {
const rows = await d3.text(libroDatosCsvUrl("Postgrados")).then(d3.csvParseRows);
const body = rows.slice(1);
const stopIdx = body.findIndex(r => (r[1] ?? "").trim() === "Ya no se ofertan");
const activos = stopIdx === -1 ? body : body.slice(0, stopIdx);
return activos
.filter(r => (r[1] ?? "").trim() !== "")
.map(r => ({
ies: (r[1] ?? "").trim(),
tipo: (r[2] ?? "").trim(),
sector: (r[3] ?? "").trim(),
programa: (r[4] ?? "").trim(),
departamento: (r[5] ?? "").trim(),
municipio: (r[6] ?? "").trim(),
modalidad: (r[7] ?? "").trim(),
semestres: (r[8] ?? "").trim(),
snies: (r[9] ?? "").trim(),
linkU: (r[10] ?? "").trim(),
linkP: (r[11] ?? "").trim(),
}));
}
TIPO_POSTGRADO_COLORS = ({ "Especialización": colors.aqua, "Maestria": colors.blue })In [17]:
{
const COLS = [
{ key: "ies", label: "Nombre de la IES", sortable: true },
{ key: "tipo", label: "Tipo", sortable: true, width: "110px" },
{ key: "sector", label: "Sector", sortable: true, width: "80px" },
{ key: "programa", label: "Nombre del programa", sortable: true },
{ key: "departamento", label: "Departamento", sortable: true, width: "130px" },
{ key: "municipio", label: "Municipio", sortable: true, width: "110px" },
{ key: "modalidad", label: "Modalidad", sortable: true, width: "110px" },
{ key: "semestres", label: "Semestres", sortable: true, width: "80px" },
{ key: "snies", label: "Código SNIES", sortable: true, width: "100px" },
{ key: "linkU", label: "Link universidad", sortable: false, width: "90px" },
{ key: "linkP", label: "Link programa", sortable: false, width: "90px" }
];
const tipos = Array.from(new Set(postgrados.map(d => d.tipo))).sort();
const state = { query: "", tipo: "", sortKey: "ies", sortDir: 1 };
const root = document.createElement("div");
root.style.cssText = `font:${FONT};color:${colors.textPrimary}`;
const controls = document.createElement("div");
controls.style.cssText = "display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:10px";
const searchInput = document.createElement("input");
searchInput.type = "search";
searchInput.placeholder = "Buscar por institución, programa, departamento o municipio...";
searchInput.style.cssText = `flex:1;min-width:220px;padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const tipoSelect = document.createElement("select");
tipoSelect.style.cssText = `padding:7px 10px;border:1px solid ${colors.grid};border-radius:6px;font:${FONT};background:${colors.surface};color:${colors.textPrimary}`;
const optAll = document.createElement("option");
optAll.value = "";
optAll.textContent = "Todos los tipos";
tipoSelect.appendChild(optAll);
for (const t of tipos) {
const opt = document.createElement("option");
opt.value = t;
opt.textContent = t;
tipoSelect.appendChild(opt);
}
const countEl = document.createElement("div");
countEl.style.cssText = `color:${colors.textSecondary};font-size:11px;white-space:nowrap;margin-left:auto`;
controls.append(searchInput, tipoSelect, countEl);
const tableWrap = document.createElement("div");
tableWrap.style.cssText = `max-height:520px;overflow:auto;border:1px solid ${colors.grid};border-radius:8px`;
const table = document.createElement("table");
table.style.cssText = "width:100%;border-collapse:collapse;font-size:12px";
const thead = table.createTHead();
const headRow = thead.insertRow();
headRow.style.cssText = `position:sticky;top:0;background:${colors.surface};z-index:1`;
for (const col of COLS) {
const th = document.createElement("th");
th.textContent = col.label;
th.style.cssText = `text-align:left;padding:8px 10px;border-bottom:2px solid ${colors.grid};cursor:${col.sortable ? "pointer" : "default"};white-space:nowrap;user-select:none${col.width ? `;width:${col.width}` : ""}`;
if (col.sortable) {
const arrow = document.createElement("span");
arrow.style.cssText = "margin-left:4px;opacity:0.5;font-size:10px";
th.appendChild(arrow);
th.addEventListener("click", () => {
if (state.sortKey === col.key) state.sortDir *= -1;
else { state.sortKey = col.key; state.sortDir = 1; }
render();
});
}
headRow.appendChild(th);
}
const tbody = table.createTBody();
table.append(thead, tbody);
tableWrap.appendChild(table);
root.append(controls, tableWrap);
function badge(tipo) {
const span = document.createElement("span");
span.textContent = tipo || "s/t";
span.style.cssText = `display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;color:#fff;background:${TIPO_POSTGRADO_COLORS[tipo] ?? colors.muted}`;
return span;
}
function linkCell(url) {
const td = document.createElement("td");
td.style.cssText = "padding:6px 10px;vertical-align:top";
if (url) {
const a = document.createElement("a");
a.href = url;
a.target = "_blank";
a.rel = "noopener";
a.textContent = "Ver ↗";
a.style.color = colors.blue;
td.appendChild(a);
}
return td;
}
function render() {
const q = state.query.trim().toLowerCase();
const filtered = postgrados.filter(d => {
if (state.tipo !== "" && d.tipo !== state.tipo) return false;
if (q === "") return true;
return (d.ies + " " + d.programa + " " + d.departamento + " " + d.municipio).toLowerCase().includes(q);
});
filtered.sort((a, b) => {
let av = a[state.sortKey], bv = b[state.sortKey];
if (state.sortKey === "semestres") { av = +av || 0; bv = +bv || 0; }
if (av < bv) return -1 * state.sortDir;
if (av > bv) return 1 * state.sortDir;
return 0;
});
Array.from(headRow.children).forEach((th, i) => {
const arrow = th.querySelector("span");
if (arrow) arrow.textContent = state.sortKey === COLS[i].key ? (state.sortDir === 1 ? "▲" : "▼") : "";
});
tbody.replaceChildren();
for (const d of filtered) {
const tr = tbody.insertRow();
tr.style.borderBottom = `1px solid ${colors.grid}`;
tr.addEventListener("mouseenter", () => tr.style.background = "rgba(42,120,214,0.06)");
tr.addEventListener("mouseleave", () => tr.style.background = "");
const iesTd = tr.insertCell();
iesTd.textContent = d.ies;
iesTd.style.cssText = "padding:6px 10px;vertical-align:top";
const tipoTd = tr.insertCell();
tipoTd.style.cssText = "padding:6px 10px;vertical-align:top";
tipoTd.appendChild(badge(d.tipo));
[d.sector, d.programa, d.departamento, d.municipio, d.modalidad, d.semestres, d.snies].forEach(v => {
const td = tr.insertCell();
td.textContent = v || "";
td.style.cssText = "padding:6px 10px;vertical-align:top";
});
tr.append(linkCell(d.linkU), linkCell(d.linkP));
}
countEl.textContent = `${filtered.length} de ${postgrados.length} programas`;
}
searchInput.addEventListener("input", () => { state.query = searchInput.value; render(); });
tipoSelect.addEventListener("change", () => { state.tipo = tipoSelect.value; render(); });
render();
return root;
}Tabla 2. Inventario de postgrados en gestión del riesgo de desastres, hoja Postgrados. Tabla interactiva: buscador, filtro por tipo y columnas ordenables.
4 Producción científica de Colombia en GRD
4.1 Tamaño de la producción científica por instituciones, procesos y amenazas
Está bien establecido que los principales actores que generan conocimiento científico y tecnológico en Colombia son principalmente universidades (Fig. 3) y esta tendencia se mantiene desde hace 25 años. En el país existe una sólida tradición en investigación científica en GRD que se ejemplifica en el aumento de productos de conocimiento publicados en los últimos 25 años con cerca de tres mil ítems (artículos, libros, capítulos de libros y reviews). (Figura 8). Existen investigadores bien establecidos, altamente productivos, con reconocimiento mundial y liderados por investigadores principales como Juan Carlos Reyes (Universidad de los Ándes), Julián Carrillo (Universidad Militar), y Omar Darío Cardona (Universidad Nacional de Colombia, sede Manizales), todos en riesgo sísmico. Como un reflejo de la producción científica concentrada en las universidades, la producción científica se realiza en los departamentos de Bogotá D.C., Antioquia, Atlántico, Valle del Cauca y Bolívar (Fig. 3)
In [18]:
calcPaises = {
const rows = await d3.csv(sheetCsvUrl("Calc_Data"), d3.autoType);
return rows
.filter(d => Number.isInteger(d["Año"]) && d["Año"] >= 2000 && d["Año"] <= 2025)
.map(d => ({
year: d["Año"],
Colombia: d.Colombia, Argentina: d.Argentina, Brasil: d.Brazil,
Chile: d.Chile, Mexico: d.Mexico
}));
}
PAIS_COLORS = ({ Colombia: colors.blue, Argentina: colors.violet, Brasil: colors.orange, Chile: colors.aqua, Mexico: colors.green })
function multiLineChart(data, series, colorMap, { title, width = 720, height = 440 } = {}) {
const marginTop = 64, marginRight = 20, marginBottom = 30, marginLeft = 56;
const years = data.map(d => d.year);
const x = d3.scaleLinear().domain(d3.extent(years)).range([marginLeft, width - marginRight]);
const maxVal = d3.max(data, d => d3.max(series, k => d[k]));
const y = d3.scaleLinear().domain([0, maxVal * 1.08]).nice().range([height - marginBottom, marginTop]);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", marginLeft).attr("y", 20)
.attr("font-size", 14).attr("font-weight", 600).attr("fill", colors.textPrimary).text(title);
// Leyenda (el ancho del texto se mide con canvas porque el SVG aún no está
// adjunto al DOM en este punto, y getComputedTextLength() da 0 en ese caso).
const measureCtx = document.createElement("canvas").getContext("2d");
measureCtx.font = "11px system-ui, -apple-system, 'Segoe UI', sans-serif";
let lx = 0;
const legend = svg.append("g").attr("transform", `translate(${marginLeft},40)`);
for (const key of series) {
const g = legend.append("g").attr("transform", `translate(${lx},0)`);
g.append("circle").attr("r", 4).attr("cy", -4).attr("fill", colorMap[key]);
g.append("text").attr("x", 10).attr("y", 0)
.attr("font-size", 11).attr("fill", colors.textSecondary).text(key);
lx += 24 + measureCtx.measureText(key).width;
}
// Líneas guía horizontales delgadas
const yTicks = y.ticks(6);
svg.append("g").selectAll("line").data(yTicks).join("line")
.attr("x1", marginLeft).attr("x2", width - marginRight)
.attr("y1", d => y(d)).attr("y2", d => y(d))
.attr("stroke", colors.grid).attr("stroke-width", 1);
svg.append("g").selectAll("text").data(yTicks).join("text")
.attr("x", marginLeft - 8).attr("y", d => y(d)).attr("dy", "0.32em")
.attr("text-anchor", "end").attr("fill", colors.muted).attr("font-size", 10)
.text(d3.format(","));
// Eje X (años)
const uniqueYears = Array.from(new Set(years));
const xTicks = uniqueYears.filter(yr => yr % 5 === 0 || yr === uniqueYears[0] || yr === uniqueYears[uniqueYears.length - 1]);
svg.append("g").selectAll("text").data(xTicks).join("text")
.attr("x", d => x(d)).attr("y", height - marginBottom + 16)
.attr("text-anchor", "middle").attr("fill", colors.muted).attr("font-size", 10)
.text(d => d);
const line = d3.line().x(d => x(d.year)).y(d => y(d.value)).curve(d3.curveMonotoneX);
const seriesData = series.map(key => ({
key, color: colorMap[key],
values: data.map(d => ({ year: d.year, value: d[key] }))
}));
svg.append("g").selectAll("path").data(seriesData).join("path")
.attr("fill", "none")
.attr("stroke", d => d.color)
.attr("stroke-width", d => d.key === "Colombia" ? 3 : 2)
.attr("d", d => line(d.values));
// Interacción: línea guía vertical + tooltip con los 5 países del año más cercano
const crosshair = svg.append("line")
.attr("y1", marginTop).attr("y2", height - marginBottom)
.attr("stroke", colors.muted).attr("stroke-width", 1).attr("opacity", 0);
svg.append("rect")
.attr("x", marginLeft).attr("y", marginTop)
.attr("width", width - marginLeft - marginRight).attr("height", height - marginTop - marginBottom)
.attr("fill", "transparent")
.on("pointerenter pointermove", function (event) {
const [mx] = d3.pointer(event);
const yr = Math.max(uniqueYears[0], Math.min(uniqueYears[uniqueYears.length - 1], Math.round(x.invert(mx))));
const row = data.find(d => d.year === yr);
if (!row) return;
crosshair.attr("x1", x(yr)).attr("x2", x(yr)).attr("opacity", 1);
const ordered = series.map(k => ({ label: k, raw: row[k] })).sort((a, b) => b.raw - a.raw);
showTooltip(event, [
{ label: `Año ${yr}` },
...ordered.map(d => ({ label: d.label, value: d3.format(",")(d.raw) }))
]);
})
.on("pointerleave", function () {
crosshair.attr("opacity", 0);
hideTooltip();
});
return svg.node();
}In [19]:
multiLineChart(calcPaises, ["Colombia", "Argentina", "Brasil", "Chile", "Mexico"], PAIS_COLORS, {
title: "Producción científica en GRD por país, 2000-2025", width: 720, height: 440
})4.1.1 Producción científica por instituciones
In [20]:
barChartH(countTop(vigilancia.filter(d => d["AÑO"] >= 2022 && d["AÑO"] <= 2025), "INSTITUCION COLOMBIANA", 15), {
title: "Instituciones colombianas con mayor producción científica en GRD, 2022-2025 (top 15)",
marginLeft: 300, width: 720
})Vigilancia_CTeI. n = 553 productos con institución colombiana identificada, de 583 registros totales en el periodo.
4.1.2 Producción científica por departamentos
Producción científica en GRD por departamento de la institución colombiana de afiliación, ranking descendente. Cobertura parcial: el 12,5% de los registros no tiene departamento de la institución colombiana identificado, y se excluyeron afiliaciones extranjeras.
In [21]:
rankBarChart(vigilancia_map_data, {
title: "Producción científica en GRD por departamento de la institución colombiana",
width: 700
})4.1.3 Producción científica por procesos de la gestión del riesgo de desastres
En cuanto a la producción científica por procesos de la gestión del riesgo de desastres, para el periodo 2020–2026 (junio) se observa que el proceso de conocimiento del riesgo concentra el 76,2% de la producción (696 de 913 productos clasificados), seguido por reducción del riesgo con 17,3% (158) y manejo de desastres con 6,5% (59) Figura 11.
In [22]:
stackedBarV(pivotByYearProceso(vigilancia), Object.keys(PROCESO_COLORS), PROCESO_COLORS, {
title: "Evolución anual por proceso GRD", width: 400, height: 460
})4.1.4 Producción científica para las principales amenazas
Frecuencia de la producción científica en GRD por tipo de amenaza (n = 561 productos con amenaza identificada). El riesgo sísmico concentra el 31,9% de la producción (179 productos), seguido por movimientos en masa con 10,9% (61) e inundación con 8,9% (50); el resto de amenazas registran individualmente menos del 7% cada una Figura 12.
In [23]:
{
const RENAME = {
"Tsunami caribe": "Tsunami", "Tsunami pacifico": "Tsunami",
"Disturbio geomagnético": "Amenazas extraterrestres",
"Riesgos biosanitarios": "Biosanitarios",
};
const AMENAZAS = new Set([
"Amenazas extraterrestres", "Avenidas torrenciales", "Biosanitarios", "Ciclones tropicales",
"Covid", "El Niño", "Erosion costera", "Erosion fluvial",
"Geoamenzas", "Incendios forestales", "Inundación", "Inundación costera", "Inundación urbana",
"Movimientos en masa", "Natech", "Nivel del mar, aumento", "Olas de calor", "Olas de calor marinas",
"Riesgo sismico", "Sequia", "Tormentas convectivas", "Tsunami",
"Variabilidad climatica", "Volcanes",
]);
const rows = vigilancia
.map(d => {
const tema = (d.TEMA ?? "").trim();
return { TEMA: RENAME[tema] ?? tema };
})
.filter(d => AMENAZAS.has(d.TEMA));
return barChartH(countTop(rows, "TEMA"), {
title: "Producción por las principales amenazas", marginLeft: 220, width: 720
});
}4.1.5 Producción científica por subprocesos y otras temáticas
La clasificación anterior de productos de nuevo conocimiento enfatizó una clasificación hacia la amenaza. Así mismo, se clasificaron los productos en otras categorias como los subprocesos de la GRD y en otros conceptos generales como cambio climático cuando no existia una categoria desde la GRD.
In [24]:
{
const RENAME = {
"Vulnerabilidad física": "Vulnerabilidad",
"Vulnerabilidad de ecosistemas costeros": "Vulnerabilidad",
};
const OTROS_TEMAS = new Set([
"Adaptación", "Agricultura", "Animales", "Asentamientos", "Asistencia humanitaria",
"Busqueda y rescate", "Calidad del aire", "Cambio climatico", "Cambio climatico, paleoclima",
"Educacion", "Evaluación del riesgo", "Comunicacion", "Erosion del suelo", "Geologia",
"Gestion del Riesgo, Teoria", "Hidroclimatologia", "Hidrologia", "Historia", "Infraestructura",
"Infraestructura resiliente", "Linea base - Oceanos", "Linea base - Rios",
"Obras de estabilizacion", "Ordenamiento Territorial", "Percepcion del riesgo",
"Protección financiera", "Preparacion y respuesta", "Post-desastre", "Procesos sociales",
"Reduccion", "Recuperacion postpandemia", "Residuos solidos", "Resiliencia",
"Ciudades resilientes", "Respuesta", "Respuesta humanitaria", "Riesgo financiero",
"Salud publica", "Salud Mental", "Sistemas de Alerta Temprana",
"Soluciones basadas en la naturaleza", "Tecnologías de teledetección", "Viviendas/albergues",
"Vulnerabilidad",
]);
const rows = vigilancia
.map(d => {
const tema = (d.TEMA ?? "").trim();
return { TEMA: RENAME[tema] ?? tema };
})
.filter(d => OTROS_TEMAS.has(d.TEMA));
return barChartH(countTop(rows, "TEMA"), {
title: "Producción por subprocesos y otras temáticas", marginLeft: 280, width: 720
});
}4.1.6 Principales tipos de productos de nuevo conocimiento
In [25]:
barChartV(countTop(vigilancia, "TIPO", Infinity, { fillna: "Sin especificar" }), {
title: "Frecuencia de productos de vigilancia CTeI por tipo", width: 720, height: 520
})Vigilancia_CTeI.
4.1.7 Principales revistas científicas y libros dónde autores colombianos publican
In [26]:
barChartH(
countTop(vigilancia.filter(d => d.TIPO === "Articulo_cientifico"), "FUENTE", 15, { truncate: 45 }),
{ title: "Principales revistas científicas (top 15)", marginLeft: 280, width: 720 }
)Articulo_cientifico), hoja Vigilancia_CTeI.
4.2 Ciencia y datos abiertos
Las prácticas de ciencia abierta siguen siendo bajas en la producción de nuevo conocimiento en GRD. Sobre una muestra de 482 productos con dato declarado, solo el 10,4% (50) reporta haber puesto sus datos a disposición en acceso abierto, frente a un 89,6% (432) que no lo hace. De estos últimos, el 8,3% (36) declara algún acceso alternativo a los datos, por ejemplo bajo solicitud. La publicación de código abierto es igual de infrecuente; solo el 7,3% de los productos (35 de 480) la reporta.
In [27]:
financiacionCTeI = d3.csv(sheetCsvUrl("Financiacion-CTeI"), d3.autoType)
function countSiNo(rows, column) {
const counts = new Map([["Sí", 0], ["No", 0]]);
for (const row of rows) {
const v = (row[column] ?? "").toString().trim().toLowerCase();
if (v === "si") counts.set("Sí", counts.get("Sí") + 1);
else if (v === "no") counts.set("No", counts.get("No") + 1);
}
return Array.from(counts, ([label, value]) => ({ label, value }));
}In [28]:
{
const opts = { marginLeft: 40, width: 230 };
const wrap = document.createElement("div");
wrap.style.cssText = "display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;overflow-x:auto";
wrap.append(
barChartH(countSiNo(financiacionCTeI, "DATOS_ABIERTOS"), { title: "Datos abiertos", ...opts }),
barChartH(countSiNo(financiacionCTeI, "CODIGO_ABIERTO"), { title: "Código abierto", ...opts })
);
return wrap;
}Financiacion-CTeI.
4.3 Género en la producción de conocimiento
Se analizó una tabla de investigadores de 259 registros del monitoreo de capacidades nacionales de CTeI en GRD. Este listado de nombres se considera como una lista nacional expertos en GRD que se ha empleado para los ejercicios de prospectiva en determinar las líneas de investigación en GRD. Para cada nombre se determinó el género que sugiere lingüísticamente, aplicando reglas onomásticas del español y de nombres colombianos, además de criterios para casos de origen extranjero (alemán, italiano, ruso, catalán, vasco). De los 259 nombres, 176 corresponden a género masculino (68 %) y 76 a género femenino (29 %), mientras que 7 (3 %) no pudieron clasificarse por tratarse de nombres de género ambiguo o indeterminado (Figura 17).
In [29]:
generoInvestigadores = {
const rows = await d3.text(libroDatosCsvUrl("Investigadores")).then(d3.csvParseRows);
const body = rows.slice(1).filter(r => (r[0] ?? "").trim() !== "");
const counts = { Masculino: 0, Femenino: 0 };
for (const r of body) {
const g = (r[16] ?? "").trim();
if (g === "Masculino" || g === "Femenino") counts[g]++;
}
return [
{ label: "Masculino", value: counts.Masculino },
{ label: "Femenino", value: counts.Femenino }
];
}
GENERO_COLORS = ({ Masculino: colors.blue, Femenino: colors.orange })
function pieChart(data, colorMap, { title, width = 320, height = 280, valueLabel = "" } = {}) {
const marginTop = title ? 34 : 10;
const legendH = 26;
const radius = Math.min(width, height - marginTop - legendH) / 2 - 6;
const cx = width / 2, cy = marginTop + radius + 6;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]).attr("width", width).attr("height", height)
.attr("style", `max-width:100%;height:auto;font:${FONT}`);
if (title) svg.append("text").attr("x", width / 2).attr("y", 20)
.attr("text-anchor", "middle").attr("font-size", 14).attr("font-weight", 600)
.attr("fill", colors.textPrimary).text(title);
const total = d3.sum(data, d => d.value);
const pie = d3.pie().value(d => d.value).sort(null);
const arc = d3.arc().innerRadius(radius * 0.55).outerRadius(radius);
const arcs = pie(data);
const g = svg.append("g").attr("transform", `translate(${cx},${cy})`);
const paths = g.selectAll("path").data(arcs).join("path")
.attr("d", arc)
.attr("fill", d => colorMap[d.data.label])
.attr("stroke", colors.surface)
.attr("stroke-width", 2);
g.append("text").attr("text-anchor", "middle").attr("dy", "-0.15em")
.attr("font-size", 20).attr("font-weight", 700).attr("fill", colors.textPrimary)
.text(total);
g.append("text").attr("text-anchor", "middle").attr("dy", "1.3em")
.attr("font-size", 10).attr("fill", colors.muted)
.text(valueLabel);
paths
.on("pointerenter pointermove", function (event, d) {
d3.select(this).attr("opacity", 0.85);
showTooltip(event, [{ label: d.data.label, value: `${d.data.value} (${(d.data.value / total * 100).toFixed(1)}%)` }]);
})
.on("pointerleave", function () {
d3.select(this).attr("opacity", 1);
hideTooltip();
});
// Leyenda centrada (ancho de texto medido con canvas: el SVG aún no está
// adjunto al DOM aquí, así que getComputedTextLength() daría 0).
const measureCtx = document.createElement("canvas").getContext("2d");
measureCtx.font = "11px system-ui, -apple-system, 'Segoe UI', sans-serif";
const items = data.map(d => ({
...d,
w: 22 + measureCtx.measureText(`${d.label} (${(d.value / total * 100).toFixed(0)}%)`).width
}));
const totalW = d3.sum(items, d => d.w);
let lx = (width - totalW) / 2;
const legend = svg.append("g").attr("transform", `translate(0,${height - legendH + 6})`);
for (const d of items) {
const item = legend.append("g").attr("transform", `translate(${lx},0)`);
item.append("circle").attr("r", 4).attr("cy", -4).attr("fill", colorMap[d.label]);
item.append("text").attr("x", 10).attr("y", 0)
.attr("font-size", 11).attr("fill", colors.textSecondary)
.text(`${d.label} (${(d.value / total * 100).toFixed(0)}%)`);
lx += d.w;
}
return svg.node();
}In [30]:
pieChart(generoInvestigadores, GENERO_COLORS, {
title: "Género de investigadores en GRD", valueLabel: "investigadores"
})Investigadores. n = 252 investigadores clasificados como masculino o femenino, de 259 registros totales (7 con género ambiguo o indeterminado se excluyen).
Estos resultados sugieren una brecha de género significativa en la conformación del talento humano dedicado a la investigación en gestión del riesgo de desastres en Colombia, con una subrepresentación de mujeres —cercana a un tercio del total— frente a una mayoría masculina. Dado que esta tabla de datos nutre los ejercicios de prospectiva para definir las líneas de investigación en GRD, dicha asimetría podría traducirse en una menor incidencia de perspectivas, prioridades o enfoques metodológicos propios de investigadoras en la agenda científica del sector, así como en los espacios de decisión y liderazgo asociados a ella.
Esto plantea la necesidad de revisar los mecanismos de identificación, vinculación y visibilización de investigadoras en GRD, con miras a fortalecer la participación de las mujeres tanto en el registro de capacidades como en los procesos de formulación de la agenda nacional de CTeI en gestión del riesgo, evitando que la desigualdad observada en el talento humano se traslade a los resultados y prioridades de la política pública en la materia.
4.4 Producción de tecnologías en el país
5 Transferencia de ciencia y tecnología, e impacto local
5.1 Marco Estratégico de Gestión Integral del Riesgo de Desastres (MEGIR)
El MEGIR (https://portal.gestiondelriesgo.gov.co/marcoestrategico), es una línea orientadora del proceso de implementación del Plan Nacional para la Gestión del Riesgo de Desastres 2015-2030 (PNGRD), que emplea encuestas anuales para monitorear la inclusión de las políticas públicas de gestión de riesgo en los niveles territoriales (departamental y municipal), sectores nacionales, y sociedad-sector privado. El MEGIR tiene el propósito de fortalecer los procesos sociales de la gestión del riesgo en el marco de la Ley 1523 de 2012 y las capacidades mínimas a nivel territorial y sectorial.
Para realizar los diagnósticos territoriales y sectoriales en los 32 departamentos y 1102 municipios del país, el MEGIR realiza una encuesta con nueve componentes agrupados en cuatro ejes: i) Gobernanza territorial, ii) Gestión de los procesos de planificación e inversión, iii) Fortalecimiento operacional y iv) Gestión local, comunitaria y privada.
Desde 2024, el MEGIR en su Componente 3 “Acordar planes de fortalecimiento institucional y/o hojas de ruta, para fortalecer capacidades en gestión del riesgo de desastres en cada sector estratégico (Art 2, 8, 12 y 14. Capítulo I Ley 1523 de 2012)” pregunta sobre la “Participación en Ciencia, Tecnología e Innovación (CTI)”.
En el reporte MEGIR de 2025, solo 6 departamentos respondieron “SI” a la participación en actividades de CTeI: Antioquia, Caquetá, Cauca, Quindío, Tolima, y Vichada. Sin embargo, ninguno realizó gestiones o inversiones de su presupuesto en CTeI.
En cuanto a los municipios, 71 municipios de 1103 respuestas indicaron que participaron en actividades de CTeI, y 92 municipios respondieron que sí se realizó gestión o inversión de recursos para CTeI. Así mismo, 12 municipios reportaron ejecutar proyectos, y 12 municipios formular proyectos.