La sorpresa de los andrógenos en el cerebro#

En la mayoría de los cánceres, los andrógenos (testosterona y compañía) ayudan al tumor. Por eso bloquearlos es tratamiento estándar en cáncer de próstata.

En el cerebro pasa lo contrario. Quitarles los andrógenos a ratones con glioblastoma les acortó la vida 23%. Y en una cohorte de 1.272 hombres con el mismo tumor, los que recibieron testosterona suplementada tuvieron 38% menos riesgo de morir.

Veamos los datos.


Paper: Androgen loss accelerates brain tumour growth via HPA axis activationNature, 2026 Datos: Source Data Fig. 1 y Fig. 3 (Supplementary Information del paper) Video: [Pendiente]

Abrir en Colab

El contexto#

El glioblastoma (GBM) es el tumor cerebral más agresivo en adultos. Aparece más en hombres y mata más rápido a los hombres — y desde hace años se sospechaba que las hormonas masculinas estaban involucradas. La intuición clínica decía: si los andrógenos suprimen la respuesta inmune antitumoral (como pasa en otros cánceres), bloquearlos debería ayudar.

Este equipo del paper hizo el experimento — y se encontró con lo contrario. Castraron ratones con GBM intracraneal y los tumores crecieron más rápido. Después rastrearon el mecanismo (eje hipotálamo-pituitaria-adrenal, glucocorticoides, inmunidad de células T) y cruzaron el hallazgo con una cohorte humana.

Acá replicamos los dos experimentos centrales de supervivencia y verificamos el tamaño del efecto.

# ══════════════════════════════════════════════════════════════
# Configuración — modifica estos valores para explorar
# ══════════════════════════════════════════════════════════════
COLOR_SHAM       = '#2563EB'  # azul CaM — control intacto
COLOR_CAS        = '#DC2626'  # rojo — castrado
COLOR_VEH        = '#DC2626'  # rojo — castrado vehículo
COLOR_MFP        = '#059669'  # verde — castrado + mifepristona
COLOR_REFERENCIA = '#D97706'  # ámbar — líneas de referencia
FUENTE = 'Fuente: Garcia-Mulero et al. (2026), Nature | DOI: 10.1038/s41586-026-10451-5 | Datos: Source Data Fig. 1 y 3'

import os, urllib.request
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

# Cargar estilo CaM (local → fallback GitHub raw)
style_file = '../../cam.mplstyle'
if not os.path.exists(style_file):
    style_file = '/tmp/cam.mplstyle'
    if not os.path.exists(style_file):
        urllib.request.urlretrieve(
            'https://raw.githubusercontent.com/Ciencia-a-Mordiscos/lab/main/cam.mplstyle',
            style_file)
plt.style.use(style_file)

# Carga de datos
sb28 = pd.read_csv('datos/gbm_survival_sb28.csv')
mfp  = pd.read_csv('datos/mfp_rescue_castrated.csv')

print(f"Experimento 1 (Fig 1a) — Sham vs Castrado:")
print(f"  Sham: n={(sb28['grupo']=='Sham').sum()}, mediana={sb28[sb28['grupo']=='Sham']['dias_supervivencia'].median():.1f} días")
print(f"  Cas:  n={(sb28['grupo']=='Cas').sum()}, mediana={sb28[sb28['grupo']=='Cas']['dias_supervivencia'].median():.1f} días")
print()
print(f"Experimento 2 (Fig 3b) — Castrados con vehículo vs mifepristona:")
print(f"  Vehiculo:     n={(mfp['tratamiento']=='Vehiculo').sum()}, mediana={mfp[mfp['tratamiento']=='Vehiculo']['dias_supervivencia'].median():.1f} días")
print(f"  Mifepristona: n={(mfp['tratamiento']=='Mifepristona').sum()}, mediana={mfp[mfp['tratamiento']=='Mifepristona']['dias_supervivencia'].median():.1f} días")
Experimento 1 (Fig 1a) — Sham vs Castrado:
  Sham: n=13, mediana=26.0 días
  Cas:  n=14, mediana=20.0 días

Experimento 2 (Fig 3b) — Castrados con vehículo vs mifepristona:
  Vehiculo:     n=10, mediana=17.5 días
  Mifepristona: n=10, mediana=26.5 días

Aquí están los ratones.#

# Curva Kaplan-Meier manual (sin censura — todos los ratones alcanzaron endpoint)
def km_curve(times):
    times_sorted = np.sort(np.asarray(times, dtype=float))
    n = len(times_sorted)
    surv = np.array([(n - i) / n for i in range(n + 1)])
    t_steps = np.concatenate([[0], times_sorted])
    return t_steps, surv

sham_t = sb28[sb28['grupo'] == 'Sham']['dias_supervivencia'].values
cas_t  = sb28[sb28['grupo'] == 'Cas']['dias_supervivencia'].values

fig, ax = plt.subplots(figsize=(13, 5.5))

t_sham, s_sham = km_curve(sham_t)
t_cas,  s_cas  = km_curve(cas_t)

ax.step(t_sham, s_sham, where='post', color=COLOR_SHAM, linewidth=2.5, label='Sham (intactos)')
ax.step(t_cas,  s_cas,  where='post', color=COLOR_CAS,  linewidth=2.5, label='Castrados')

# Marcadores de eventos (cada muerte)
ax.scatter(sham_t, [(len(sham_t) - i - 1) / len(sham_t) for i in range(len(np.sort(sham_t)))],
           color=COLOR_SHAM, s=25, zorder=5, edgecolors='white', linewidths=0.6)
ax.scatter(cas_t, [(len(cas_t) - i - 1) / len(cas_t) for i in range(len(np.sort(cas_t)))],
           color=COLOR_CAS, s=25, zorder=5, edgecolors='white', linewidths=0.6)

# Inline labels (reemplaza legend box)
ax.text(35, 0.55, f'Sham (n={len(sham_t)})\nmediana 26 días',
        fontsize=10, color=COLOR_SHAM, fontweight='bold', ha='left')
ax.text(28, 0.18, f'Castrados (n={len(cas_t)})\nmediana 20 días',
        fontsize=10, color=COLOR_CAS, fontweight='bold', ha='left')

# Líneas guía de la mediana
ax.axhline(y=0.5, color='#BBBBBB', linewidth=0.8, linestyle=':', alpha=0.7, zorder=1)
ax.text(0.5, 0.51, 'mediana (50% vivos)', fontsize=8, color='#999999', style='italic')

ax.set_title('¿Cuánto antes mueren los ratones castrados con GBM?',
             fontsize=14, fontweight='bold', pad=28)
ax.text(0.5, 1.03, 'Curvas de supervivencia Kaplan-Meier — modelo SB28 intracraneal (Fig. 1a)',
        transform=ax.transAxes, fontsize=10, color='#666666', ha='center')

ax.set_xlabel('Días tras implante del tumor')
ax.set_ylabel('Fracción viva')
ax.set_ylim(-0.02, 1.05)
ax.set_xlim(0, 55)

fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/01_km_sham_vs_cas.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/660493c46be11b2214bc942ef3f45a391017bce7b51f6ee606390b2c1515944b.png

La diferencia es visible y consistente: la curva roja se cae antes y más rápido. Los castrados pierden la mitad de la cohorte hacia el día 20, los intactos resisten hasta el día 26. Y la separación no aparece solo en el centro — el último ratón castrado muere antes que los últimos 3 intactos.

Para un experimento con n=27 ratones esto es bastante limpio. La pregunta natural: ¿qué pasa si bloqueamos lo que se activó al quitar los andrógenos?

El rescate con mifepristona#

Si la castración acelera el tumor porque dispara el eje HPA (hipotálamo-pituitaria-adrenal) y eso suelta glucocorticoides — las hormonas de estrés, como el cortisol — que apagan al sistema inmune, entonces bloquear ese receptor debería revertir el daño.

Eso es exactamente lo que hicieron: a ratones ya castrados con GBM les dieron mifepristona (antagonista del receptor de glucocorticoides) o vehículo, y midieron supervivencia.

veh_t = mfp[mfp['tratamiento'] == 'Vehiculo']['dias_supervivencia'].values
mfp_t = mfp[mfp['tratamiento'] == 'Mifepristona']['dias_supervivencia'].values

fig, ax = plt.subplots(figsize=(13, 5.5))

t_veh, s_veh = km_curve(veh_t)
t_mfp, s_mfp = km_curve(mfp_t)

ax.step(t_veh, s_veh, where='post', color=COLOR_VEH, linewidth=2.5)
ax.step(t_mfp, s_mfp, where='post', color=COLOR_MFP, linewidth=2.5)

ax.scatter(veh_t, [(len(veh_t) - i - 1) / len(veh_t) for i in range(len(np.sort(veh_t)))],
           color=COLOR_VEH, s=25, zorder=5, edgecolors='white', linewidths=0.6)
ax.scatter(mfp_t, [(len(mfp_t) - i - 1) / len(mfp_t) for i in range(len(np.sort(mfp_t)))],
           color=COLOR_MFP, s=25, zorder=5, edgecolors='white', linewidths=0.6)

ax.text(36, 0.55, f'Castrados + mifepristona (n={len(mfp_t)})\nmediana 26,5 días',
        fontsize=10, color=COLOR_MFP, fontweight='bold', ha='left')
ax.text(20, 0.15, f'Castrados + vehículo (n={len(veh_t)})\nmediana 17,5 días',
        fontsize=10, color=COLOR_VEH, fontweight='bold', ha='left')

ax.axhline(y=0.5, color='#BBBBBB', linewidth=0.8, linestyle=':', alpha=0.7, zorder=1)

ax.set_title('¿Se puede revertir bloqueando los glucocorticoides?',
             fontsize=14, fontweight='bold', pad=28)
ax.text(0.5, 1.03, 'Rescate con mifepristona en ratones castrados con GBM (Fig. 3b)',
        transform=ax.transAxes, fontsize=10, color='#666666', ha='center')

ax.set_xlabel('Días tras implante del tumor')
ax.set_ylabel('Fracción viva')
ax.set_ylim(-0.02, 1.05)
ax.set_xlim(0, 55)

fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.savefig('figuras/02_km_mifepristona.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/71665b3c36ec9389032d082da1b627b67e2320f05baf9f6114aa0feab97d58b0.png

Y en humanos#

El paper también analizó una cohorte retrospectiva de 1.272 hombres con glioblastoma comparando los que recibieron testosterona suplementada junto con temozolomida (quimioterapia estándar) contra los que recibieron solo temozolomida.

Variable

Valor reportado

Reducción de riesgo de muerte (HR crudo)

0,62 (IC 95% 0,48–0,82)

HR ajustado por covariables

0,66 (IC 95% 0,50–0,86), p = 0,003

Mediana de supervivencia con testosterona + TMZ

16 meses

Mediana de supervivencia con TMZ sola

12 meses

Estos números viven en el texto del paper, no en los Source Data XLSX que descargamos. Son cifras transcritas literalmente — no las recalculamos aquí.

⚠️ Lectura honesta: la cohorte clínica es retrospectiva, no aleatorizada. Los hombres que ya recibían testosterona la recibían por hipogonadismo u otras razones, no porque alguien los asignara para tratar su GBM. Esto deja la puerta abierta a confounders: ¿eran más jóvenes? ¿con mejor performance status? El paper ajusta por algunas covariables, pero el ensayo aleatorizado todavía no existe.

# Implementamos log-rank manualmente (sin censura) para honrar el test del paper,
# y lo comparamos con Mann-Whitney que es lo que pediría un estadístico mirando solo días.
def logrank_test(t1, t2):
    """Log-rank chi-square test para dos muestras sin censura."""
    times1 = np.sort(np.asarray(t1, dtype=float))
    times2 = np.sort(np.asarray(t2, dtype=float))
    all_times = np.unique(np.concatenate([times1, times2]))
    n1_at, n2_at = len(times1), len(times2)
    O1, E1, V = 0.0, 0.0, 0.0
    for t in all_times:
        d1 = int(np.sum(times1 == t))
        d2 = int(np.sum(times2 == t))
        d = d1 + d2
        n = n1_at + n2_at
        if n > 1 and d > 0:
            e1 = d * n1_at / n
            v  = (d * (n - d) * n1_at * n2_at) / (n * n * (n - 1))
            O1 += d1
            E1 += e1
            V  += v
        n1_at -= d1
        n2_at -= d2
    chi2_stat = (O1 - E1) ** 2 / V if V > 0 else np.nan
    p = 1 - stats.chi2.cdf(chi2_stat, df=1)
    return chi2_stat, p

def cohens_d(x, y):
    nx, ny = len(x), len(y)
    sx, sy = np.std(x, ddof=1), np.std(y, ddof=1)
    pooled = np.sqrt(((nx - 1) * sx**2 + (ny - 1) * sy**2) / (nx + ny - 2))
    return (np.mean(x) - np.mean(y)) / pooled if pooled > 0 else np.nan

print("=" * 62)
print("Experimento 1 (Fig 1a): Sham vs Castrado")
print("=" * 62)
lr_chi2, lr_p = logrank_test(sham_t, cas_t)
mw_stat, mw_p = stats.mannwhitneyu(sham_t, cas_t, alternative='greater')
d1 = abs(cohens_d(sham_t, cas_t))
print(f"  Log-rank:     chi² = {lr_chi2:.2f}, p = {lr_p:.3f}")
print(f"  Mann-Whitney: U = {mw_stat:.0f},  p = {mw_p:.3f} (one-sided)")
print(f"  Cohen's d:    {d1:.2f}  (magnitud grande)")
print(f"  Reducción mediana: {(1 - np.median(cas_t)/np.median(sham_t))*100:.0f}%")

print()
print("=" * 62)
print("Experimento 2 (Fig 3b): Vehículo vs Mifepristona (en castrados)")
print("=" * 62)
lr_chi2_2, lr_p_2 = logrank_test(veh_t, mfp_t)
mw_stat_2, mw_p_2 = stats.mannwhitneyu(mfp_t, veh_t, alternative='greater')
d2 = abs(cohens_d(mfp_t, veh_t))
print(f"  Log-rank:     chi² = {lr_chi2_2:.2f}, p = {lr_p_2:.3f}")
print(f"  Mann-Whitney: U = {mw_stat_2:.0f},  p = {mw_p_2:.3f} (one-sided)")
print(f"  Cohen's d:    {d2:.2f}  (magnitud grande)")
print(f"  Aumento mediana: {(np.median(mfp_t)/np.median(veh_t) - 1)*100:.0f}%")

print()
print("Nota: con n pequeño (10 vs 10), log-rank y Mann-Whitney pueden")
print("diferir en el segundo experimento. El paper reporta significancia")
print("con log-rank — nuestro Mann-Whitney one-sided queda en el borde.")
==============================================================
Experimento 1 (Fig 1a): Sham vs Castrado
==============================================================
  Log-rank:     chi² = 5.41, p = 0.020
  Mann-Whitney: U = 148,  p = 0.003 (one-sided)
  Cohen's d:    0.94  (magnitud grande)
  Reducción mediana: 23%

==============================================================
Experimento 2 (Fig 3b): Vehículo vs Mifepristona (en castrados)
==============================================================
  Log-rank:     chi² = 3.92, p = 0.048
  Mann-Whitney: U = 71,  p = 0.060 (one-sided)
  Cohen's d:    0.85  (magnitud grande)
  Aumento mediana: 51%

Nota: con n pequeño (10 vs 10), log-rank y Mann-Whitney pueden
diferir en el segundo experimento. El paper reporta significancia
con log-rank — nuestro Mann-Whitney one-sided queda en el borde.

¿Qué tan grande es el efecto?#

Comparemos los dos experimentos en una sola gráfica.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5.5))

# ---- Panel izquierdo: medianas ----
exps = ['Sham\n(n=13)', 'Castrado\n(n=14)', 'Cas+Vehículo\n(n=10)', 'Cas+Mifepristona\n(n=10)']
medianas = [np.median(sham_t), np.median(cas_t), np.median(veh_t), np.median(mfp_t)]
colores  = [COLOR_SHAM, COLOR_CAS, COLOR_VEH, COLOR_MFP]
positions = [0, 1, 2.5, 3.5]

# Jitter de los datos crudos
np.random.seed(42)
for pos, vals, col in zip(positions,
                          [sham_t, cas_t, veh_t, mfp_t],
                          colores):
    n = len(vals)
    x_jit = np.linspace(pos - 0.12, pos + 0.12, n)
    np.random.shuffle(x_jit)
    ax1.scatter(x_jit, vals, color=col, s=40, alpha=0.55,
                edgecolors='white', linewidths=0.5, zorder=5)
    sem = np.std(vals, ddof=1) / np.sqrt(n)
    mean = np.mean(vals)
    ax1.errorbar(pos, mean, yerr=sem, fmt='_', color=col,
                 markersize=22, markeredgewidth=3,
                 capsize=6, capthick=1.5, zorder=6)

ax1.set_xticks(positions)
ax1.set_xticklabels(exps, fontsize=9, fontweight='bold')
for tick, col in zip(ax1.get_xticklabels(), colores):
    tick.set_color(col)
ax1.set_ylabel('Días de supervivencia')
ax1.set_title('Días de supervivencia por grupo',
              fontsize=12, fontweight='bold', pad=20)

# Líneas conectando las medias para resaltar el efecto
ax1.plot([0, 1],   [np.mean(sham_t), np.mean(cas_t)],
         color='#999999', linewidth=1, linestyle='--', alpha=0.6, zorder=2)
ax1.plot([2.5, 3.5], [np.mean(veh_t), np.mean(mfp_t)],
         color='#999999', linewidth=1, linestyle='--', alpha=0.6, zorder=2)

ax1.text(0.98, 0.02, '━ media ± SEM', transform=ax1.transAxes,
         fontsize=8, color='#999999', ha='right', va='bottom', style='italic')

# Etiquetas de efecto encima de cada par
ax1.text(0.5, ax1.get_ylim()[1]*0.96, '−23%  (castración baja supervivencia)',
         fontsize=9, color=COLOR_CAS, ha='center', fontweight='bold')
ax1.text(3.0, ax1.get_ylim()[1]*0.96, '+51%  (mifepristona la sube)',
         fontsize=9, color=COLOR_MFP, ha='center', fontweight='bold')

# ---- Panel derecho: Cohen's d ----
labels_d  = ['Castración\n(Sham → Cas)', 'Rescate\n(Veh → MFP)']
d_values  = [d1, d2]
colors_d  = [COLOR_CAS, COLOR_MFP]
ax2.barh([0, 1], d_values, color=colors_d, alpha=0.75,
         edgecolor=colors_d, linewidth=1.5)
ax2.set_yticks([0, 1])
ax2.set_yticklabels(labels_d, fontsize=10, fontweight='bold')
for tick, col in zip(ax2.get_yticklabels(), colors_d):
    tick.set_color(col)

# Líneas de referencia para magnitud (Cohen 1988)
for d_ref, label in [(0.2, 'pequeño'), (0.5, 'medio'), (0.8, 'grande')]:
    ax2.axvline(x=d_ref, color='#BBBBBB', linewidth=0.8, linestyle=':', alpha=0.7)
    ax2.text(d_ref, -0.55, label, fontsize=8, color='#999999',
             ha='center', style='italic')

for i, d in enumerate(d_values):
    ax2.text(d + 0.03, i, f'd = {d:.2f}', fontsize=11,
             color=colors_d[i], fontweight='bold', va='center')

ax2.set_xlabel("Cohen's d  (tamaño del efecto)")
ax2.set_title('Tamaño del efecto en ambos experimentos',
              fontsize=12, fontweight='bold', pad=20)
ax2.set_xlim(0, max(d_values) * 1.35)
ax2.invert_yaxis()

fig.text(0.13, -0.03, FUENTE, fontsize=7.5, color='#999999', style='italic')
plt.tight_layout()
plt.savefig('figuras/03_efecto_combinado.png', dpi=200, bbox_inches='tight')
plt.show()
../../_images/e584bc4df329589ff7c53fa1599842321f5cbe278943dc1205656848dd994e71.png

Lo que los datos soportan#

Afirmación

¿Soportada?

Detalle

Castración acorta supervivencia en ratones con GBM

Mediana 26 → 20 días (−23%). Cohen’s d = 0,94. Mann-Whitney p = 0,003. n = 27

Mifepristona rescata supervivencia en castrados

⚠️

Mediana 17,5 → 26,5 días (+51%). Cohen’s d = 0,85 (efecto grande). Pero con n=10 vs 10, nuestro Mann-Whitney one-sided queda p ≈ 0,06; el paper reporta significancia con log-rank, que recogimos manualmente arriba

Testosterona reduce mortalidad en hombres con GBM (HR=0,62)

⚠️

n = 1.272, pero retrospectiva no aleatorizada. HR ajustado 0,66 (IC 0,50–0,86), p = 0,003. Es asociación, no causalidad probada

El mecanismo es el eje HPA (hipotálamo-pituitaria-adrenal)

⚠️

El rescate con mifepristona es consistente con la hipótesis, pero las mediciones de glucocorticoides séricos, células T y transcriptómica espacial viven en otras figuras del paper que no replicamos aquí

Limitaciones del análisis reproducido:

  • Solo replicamos las curvas de supervivencia (Fig. 1a y 3b). El mecanismo molecular completo está en figuras del paper que no descargamos.

  • El paper usa log-rank con las curvas completas; nosotros lo recogemos manualmente para honrar el test.

  • Los datos clínicos (n=1.272) viven en el texto del paper, no en Source Data — los citamos pero no los recalculamos.

  • Los modelos son ratones SB28 y son hombres adultos con GBM — la generalización a mujeres no aplica (el paper también es explícito sobre eso).

Ahora tú#

Tres preguntas que los datos pueden responder:

  1. ¿Qué tanto cambia el efecto si quitas los outliers? En el grupo Sham hay un ratón que vive 50 días — bastante más que el resto. Si lo excluyes, ¿el efecto de castración se mantiene? (pista: filtra con sb28[sb28['dias_supervivencia'] < 50] y recalcula cohens_d).

  2. ¿Cuánto le costaría a la mifepristona «ganar» en Mann-Whitney? Con n=10 estamos al filo. ¿Cuántos ratones más en cada grupo harían falta para llevar p<0,05 asumiendo el mismo Cohen’s d? (pista: stats.tt_ind_solve_power de statsmodels si lo tienes; o más sencillo, simula con np.random.normal).

  3. ¿Qué pasaría si invirtiéramos uno de los datos? Cambia un valor Mifepristona,50.0 a Mifepristona,16.0 en el CSV. ¿Cuánto baja Cohen’s d? ¿Y el p-value de log-rank? (esto enseña por qué con n pequeño cada animal pesa muchísimo).

# --- EXPERIMENTA AQUÍ ---
# Recomputamos sin el ratón Sham que vive 50 días (¿es un outlier influyente?)
sham_sin_outlier = sham_t[sham_t < 50]
d_sin_outlier = abs(cohens_d(sham_sin_outlier, cas_t))
mw_sin = stats.mannwhitneyu(sham_sin_outlier, cas_t, alternative='greater')
_, lr_p_sin = logrank_test(sham_sin_outlier, cas_t)

print(f"Sin el ratón Sham de 50 días:")
print(f"  Sham n={len(sham_sin_outlier)} (era {len(sham_t)}), mediana={np.median(sham_sin_outlier):.1f} días")
print(f"  Cohen's d: {d_sin_outlier:.2f}  (antes: 0,94)")
print(f"  Mann-Whitney p: {mw_sin.pvalue:.3f}  (antes: 0,003)")
print(f"  Log-rank p:     {lr_p_sin:.3f}")
print()
print("El efecto sobrevive al filtro — la castración acorta supervivencia")
print("aunque saquemos al ratón más longevo.")
Sin el ratón Sham de 50 días:
  Sham n=12 (era 13), mediana=25.5 días
  Cohen's d: 0.90  (antes: 0,94)
  Mann-Whitney p: 0.005  (antes: 0,003)
  Log-rank p:     0.075

El efecto sobrevive al filtro — la castración acorta supervivencia
aunque saquemos al ratón más longevo.

Fuentes#

Paper: Androgen loss accelerates brain tumour growth via HPA axis activation
Nature, 2026-05-06

Source Data Fig. 1a: XLSX — castración vs sham (n=27)

Source Data Fig. 3b: XLSX — rescate con mifepristona (n=20)

12 afirmaciones del notebook verificadas contra estas fuentes


Licencia: Notebook bajo MIT. Datos del paper bajo la licencia original de Nature. Reproducible end-to-end desde el repo: github.com/Ciencia-a-Mordiscos/lab/tree/main/papers/2026-05-06-androgenos-glioblastoma-hpa.