Volver al Artículo
Cuaderno de Artículo
Descargar código fuente

Estado de la ciencia, tecnología e innovación en gestión del riesgo de desastres

Figuras y tablas interactivas

Autor/a
Afiliación

Mauricio Romero Torres

Unidad Nacional para la Gestión del Riesgo de Desastres (UNGRD)

Fecha de publicación

1 de julio de 2026

Resumen

Aquí se presenta un panorama de la ciencia, tecnología e innovación (CTeI) en gestión del riesgo de desastres (GRD) en Colombia con énfasis para el cuatrienio 2022-2026. Se analizan varias dimensiones: la oferta de financiación de Minciencias y el Sistema General de Regalías; las demandas territoriales declaradas por los Consejos Departamentales de Ciencia, Tecnología e Innovación (CODECTI) para los bienios 2023-2024 y 2025-2026; producción científica en GRD desde 2020 clasificada según los procesos de la Ley 1523 de 2012 (conocimiento, reducción y manejo del riesgo), los postgrados disponibles en GRD, y los grupos de investigación con alta relación a GRD. Todas las figuras y tablas pueden replicarse. Consulte el código en el repositorio.

Palabras clave

gestión del riesgo de desastres, ciencia, tecnología e innovación, conocimiento del riesgo

In [1]:
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 [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: 

  1. Financiación de la ciencia y la tecnología
    1. Financiación de la ciencia en convocatorias nacionales.
    2. Demandas territoriales para el Sistema General de Regalías.
  2. Capacidades en ciencia y la tecnología
    1. Inventario de los institutos y grupos de investigación en el país.
    2. Inventario de postgrados en GRD.
  3. Producción científica colombiana en GRD
    1. Tamaño de la producción científica por temáticas, departamentos y afiliaciones.
    2. Ciencia abierta.
    3. Producción de tecnologías en el país.
  4. Transferencia de ciencia y tecnología, e impacto local
    1. 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

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. 

Caja 1. Las principales convocatorias de ciencia y tecnología lanzadas por Minciencias entre 2022 y 2026.

  • 1045 de 2023. Conocimiento para la transformación y agregación de valor de la cadena láctea en los departamentos de Cauca, Nariño y Putumayo. Recursos por $5.472.000.000
  • 37 de 2024. Convocatoria de la Asignación para la Ciencia, Tecnología e Innovación del Sistema General de Regalías para la Conformación de un Listado de Macroproyectos Nacionales Elegibles. Recursos por $500.000.000.000
  • 51 de 2025. Convocatoria para el Fortalecimiento de Capacidades de Ciencia, Tecnología e Innovación en el Departamento de Córdoba Alineado con los Retos Estratégicos de CTeI del Plan Bienal 2025-2026. Recursos por $45.249.920.00
  • 48 de 2026. Convocatoria de la Asignación para la Ciencia, Tecnología e Innovación del Sistema General de Regalías (SGR) para Proveer Soluciones de Ciencia, Tecnología e Innovación para el Aprovechamiento Sostenible de la Biodiversidad, la Productividad y el Fortalecimiento Territorial. Recursos por $749.982.430.291
  • 52 de 2026. Convocatoria para el Fortalecimiento de Capacidades Territoriales en Situaciones de Emergencia. Recursos por  $100.000.000.000
  • 976 de 2026. Convocatoria Colombia Inteligente 2026. Recursos por $24.000.000.000

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.

Caja 2. Definiciones de tipologías de productos de de CTeI acorde con Minciencias empleadas en la medición del estado de la ciencia. En el Anexo 2 encuentre el detalle de fuente, registro o mecanismo de verificación, indicador, criterio de calidad o clasificación para las tipologías de productos.

  • Actividades de Ciencia y Tecnología (ACTI). Son actividades sistemáticas estrechamente relacionadas con la producción, promoción, difusión y aplicación de los conocimientos científicos y técnicos en todos los campos de la ciencia y la tecnología. Incluye I+D, formación científica y tecnológica, servicios científicos y tecnológicos, administración y otras actividades de apoyo para CTeI y actividades conducentes a la innovación.
  • Generación de conocimiento. Productos que aportan nuevo conocimiento al estado del arte de un área científica, tecnológica o de investigación-creación. Constituyen resultados originales derivados de procesos de investigación. Principales tipos de productos: Artículos de investigación, libros resultado de investigación, capítulos de libro resultado de investigación, productos de investigación-creación, patentes, variedades vegetales o animales, entre otros.
  • Desarrollo tecnológico e innovación. Productos derivados de procesos de investigación, desarrollo tecnológico o innovación orientados a la generación de soluciones, métodos, herramientas, productos, procesos o servicios que atienden necesidades o problemas específicos y pueden generar transformaciones o impactos. Principales tipos de productos: Productos tecnológicos, software, prototipos, diseños industriales, plantas piloto, procesos o procedimientos, innovaciones de producto/proceso/servicio, empresas de base tecnológica, entre otros.
  • Apropiación social del conocimiento. Procesos y productos mediante los cuales el conocimiento científico, tecnológico y de innovación es intercambiado, comprendido, utilizado y apropiado por diferentes actores sociales, promoviendo su participación y transformación de realidades. Principales tipos de productos: Procesos de apropiación social, estrategias pedagógicas, eventos científicos de apropiación, contenidos de divulgación, circulación de conocimiento especializado, participación ciudadana en CTeI, entre otros.
  • Formación de recurso humano para CTeI. Productos asociados con la formación de capacidades y talento humano para la investigación, el desarrollo tecnológico y la innovación, mediante procesos de formación académica y científica. Principales tipos de productos: Tesis de doctorado y maestría, trabajos de grado, proyectos de investigación de estudiantes, formación de investigadores, dirección de trabajos de grado, entre otros.

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
})
Figura 1: Productos de nuevo conocimiento que declaran financiador (Sí/No), hoja 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
  );
}
Figura 2

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 }
)
Figura 3: Principales fuentes de financiación nacionales de productos de conocimiento (top 10), hoja 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 }
)
Figura 4: Principales fuentes de financiacion internacional de productos de conocimiento (top 10), hoja 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)"
})
Figura 5: Demandas territoriales del bienio 2023-2024 con relación ALTA a la gestión del riesgo de desastres, por departamento: mapa (izquierda) y ranking descendente (derecha). Pase el cursor sobre cada departamento del mapa para ver el detalle de los retos asociados.

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)"
})
Figura 6: Demandas territoriales del bienio 2025-2026 con relación ALTA a la gestión del riesgo de desastres, por departamento: mapa (izquierda) y ranking descendente (derecha). Pase el cursor sobre cada departamento del mapa para ver el detalle de los retos asociados.

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;
}
Figura 7: Grupos de investigación colombianos con relación a la gestión del riesgo de desastres, clasificados por Minciencias, hoja 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

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
})
Figura 8: Comparación de la producción científica en GRD en Argentina, Brasil, Chile, Colombia y Mexico entre los años 2000 a 2025. Colombia produjo un total de 2.892 ítems entre los años 2000 a 2025 ocupando el cuarto lugar entre las mayores economías de latinoamérica (producción científica potencial. Fuente Scopus).

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
})
Figura 9: Instituciones colombianas con mayor producción científica en GRD (top 15), 2022-2025, hoja 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
})
Figura 10: 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.

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
})
Figura 11: Producción científica por procesos de gestión del riesgo. Según la base de datos de vigilancia tecnológica, entre 2020 y 2026 (junio), la producción científica osciló entre un mínimo de 61 productos en 2020 y un máximo de 170 en 2025 (izquierda). Asimismo, al desagregar la producción científica por proceso de gestión del riesgo (conocimiento del riesgo, reducción del riesgo y manejo de desastres), se observa que el proceso de conocimiento del riesgo concentra la mayor proporción de nuevo conocimiento generado.

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
  });
}
Figura 12: Producción científica por amenazas. La frecuencia de producción de nuevo conocimiento por amenaza muestra que la amenaza sísmica presenta la mayor producción, seguida de los movimientos en masa, las inundaciones y los incendios forestales. La asignación a una amenaza para cada producto de conocimiento se realizó mediante un método híbrido según la taxonomía HIPs de UNDRR & ICS (2025d). La cual contiene 281 amenazas clasificadas en 8 clústeres. Se empleó un clasificador determinístico local (diccionario TEMA→HIPs + palabras clave, sin LLM) cubrió ~75 % de los registros válidos; el 25 % restante, con temas genéricos sin correspondencia léxica, se revisó manualmente mediante lectura de título y objetivo por Claude Sonnet 4.5 (Anthropic), validando cada asignación contra el catálogo oficial antes de incorporarla.

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
  });
}
Figura 13: Frecuencia de productos de vigilancia CTeI por subprocesos y otras temáticas. Se destacan temáticas como hidrología, infraestructural cambio climático o vulnerabilidad. Estos productos requieres un análisis más detallado para establacer su relación con por ejemplo el tipo de vulnerabilidad a una amenaza especifica.

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
})
Figura 14: Frecuencia de productos de nuevo conocimiento por tipo (artículo científico, artículo de congreso, capítulo de libro, etc.), hoja 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 }
)
Figura 15: Principales revistas científicas de publicación (top 15, tipo 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;
}
Figura 16: Proporción de productos que declaran datos abiertos y código abierto, hoja 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"
})
Figura 17: Proporción de género (masculino/femenino) inferido lingüísticamente en la lista nacional de investigadores en GRD, hoja 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.

6 Conclusiones

Caja 3 | Mensajes clave del estado de la ciencia de los desastres.

Convocatorias nacionales: La ciencia de los desastres se ha posicionado en las  convocatorias Minciencias dentro de su agenda de financiamiento. Minciencias lanzó 92 convocatorias de CTeI, de las cuales 41 permitieron postular proyectos o capital humano, con líneas temáticas explícitas en GRD.

Demandas territoriales: Los departamentos sí postulan demandas territoriales en GRD. En el bienio 2023–2024, 27 departamentos declararon 71 demandas territoriales vinculadas a la GRD. Para el bienio 2025–2026, 15 departamentos presentaron 20 demandas territoriales. Existe una articulación territorial de la CTeI con las prioridades de gestión del riesgo que debe mantenerse y promoverse en cada bienio.

Grupos e institutos de investigación: Colombia tiene grupos consolidados en GRD. Existen 58 grupos de investigación con alta relación a GRD, concentrados en ingeniería y ciencias naturales. Los grupos se localizan en Bogotá, Medellín, Cali y Barranquilla..

Generación de conocimiento creciente: Colombia tiene producción científica sólida y creciente en GRD liderada por universidades. Entre 2020 y 2025 Colombia produjo 2.974 productos científicos en GRD. La producción se concentra en la Universidad Nacional de Colombia, la Universidad de los Andes y la Universidad de Antioquia, evidenciando capacidades sólidas, pero concentrada en Universidades. 

Formación posgradual: Existe una oferta activa de 10 especializaciones y 13 maestrías en GRD; sin embargo, no existe un programa de doctorado acreditado específico en GRD.

MEGIR: La participación territorial en CTeI para GRD es baja, sin inversión departamental real. Existen pocos registros en las encuestas de departamentos y municipios. Aunque sí existe participación en actividades de CTeI, pocos municipios invierten, y muy pocos formulan o ejecutan proyectos. La promoción continua desde los CDGRD y CMGRD hacia los CODECTI es necesaria.