Medio digital estático construido con Eleventy, Tailwind CSS y Vanilla JS, alimentado por contenidos de Discourse.
El sitio toma los topics del foro de Aldea Pucela, los normaliza, genera páginas estáticas y se despliega automáticamente en GitHub Pages.
Eleventycomo generador estáticoTailwind CSSpara toda la UIVanilla JSpara interacciones del clienteDiscourse APIcomo fuente de datosGitHub Actions+GitHub Pagespara build y despliegue
El código de esta web se distribuye bajo la licencia GNU AGPL v3.0.
Consulta el texto completo en:
LICENSE
La fuente principal es la categoría de Discourse:
https://foro.aldeapucela.org/c/9.json
Durante la build:
- Se consulta la categoría para obtener la lista de topics.
- Se enriquecen los artículos con datos del topic individual.
- Se sanea el HTML del primer post para su renderizado editorial.
- Se genera:
- portada
- páginas de tema
- páginas de artículo
- un JSON estático en
/api/articulos.json
Node.js >= 20npm
npm installServidor de desarrollo con Eleventy:
npm run startPor defecto sirve en:
Si necesitas levantar una preview simple de la carpeta dist en otro puerto, primero genera la build:
npm run build
python3 -m http.server 4000 -d distEsto es útil cuando se quiere probar integración con embeds o revisar el HTML final compilado.
npm run buildLa salida se genera en:
dist/
npm run start: arranca Eleventy en modo servidornpm run build: limpiadist, compila Tailwind y genera el sitionpm run clean: eliminadistnpm run build:css: compila Tailwind adist/assets/styles/main.css
.
├── .github/workflows/build.yml
├── eleventy.config.js
├── package.json
├── tailwind.config.js
├── src
│ ├── _data
│ │ └── articulos.js
│ ├── _includes
│ │ └── base.njk
│ ├── api
│ │ └── articulos.json.njk
│ ├── assets
│ │ ├── favicon.svg
│ │ ├── js
│ │ │ ├── main.js
│ │ │ └── matomo.js
│ │ └── styles
│ │ └── main.css
│ ├── articulo.njk
│ ├── etiqueta.njk
│ └── index.njk
└── dist
El fichero clave es:
src/_data/articulos.js
Responsabilidades:
- consultar la API de Discourse
- extraer
tags, autor, fechas, extracto, imagen y HTML - sanear el contenido HTML
- corregir elementos problemáticos del HTML de Discourse
- lightboxes
- emojis
- captions de dimensiones
onebox
- ordenar artículos
- primero fijados (
pinned) - después por fecha de publicación descendente
- primero fijados (
- mantener caché local para evitar descargas innecesarias
Para reducir peticiones y evitar volver a bajar topics sin cambios, el proyecto guarda una caché local en:
.cache/articulos.json.cache/autores.json.cache/audio-manifest.json
La caché reutiliza artículos ya descargados si no han cambiado según updatedAt.
Notas:
- no hace falta commitear esta caché
- si quieres forzar una reconstrucción completa desde Discourse, puedes borrarla manualmente
En cada npm run build el proyecto hace esto:
- consulta el índice de la categoría de Discourse (
/c/9.json) - compara cada topic con la caché local por
updatedAt - solo entra al JSON completo de los topics que han cambiado o no están cacheados
- resuelve autores reutilizando caché cuando es posible
- resuelve audio de una de estas dos formas:
- preferida: leyendo un manifiesto JSON único definido en
AUDIO_MANIFEST_URL - fallback: haciendo una petición
HEADpor artículo al MP3 remoto
Eso significa que ya no se “bajan todos los artículos” en cada build: siempre se consulta el índice de la categoría, pero el detalle solo se pide para los topics nuevos o modificados.
Para escalar mejor, el proyecto puede consumir un único manifiesto remoto en vez de hacer una comprobación por audio.
Variables opcionales:
AUDIO_MANIFEST_URL: URL JSON del manifiesto de audiosAUDIO_MANIFEST_TTL_MS: tiempo de caché local del manifiesto en milisegundos. Por defecto,600000(10 minutos)
Formato admitido del manifiesto:
{
"generatedAt": "2026-03-28T10:00:00.000Z",
"items": [
{
"id": "123",
"isAvailable": true,
"src": "https://media.aldeapucela.org/audios/123.mp3",
"downloadUrl": "https://media.aldeapucela.org/audios/123.mp3",
"mimeType": "audio/mpeg",
"sizeBytes": 1843200,
"duration": "03:12"
}
]
}También se aceptan estas variantes:
audiosen lugar deitemsitemsByIdcomo objeto- claves
articleIdotopicIden lugar deid
Si el manifiesto falla temporalmente, el build intenta usar la copia local en .cache/audio-manifest.json. Si tampoco existe, vuelve al modo anterior de HEAD por artículo.
Además de las páginas HTML, Eleventy genera:
dist/api/articulos.json
Ese JSON puede usarse desde frontend para consultas ligeras o integraciones adicionales.
El despliegue se hace con:
.github/workflows/build.yml
Triggers configurados:
- push a
main - ejecución manual
- cron cada 15 minutos
La tarea:
- instala dependencias
- ejecuta
npm run build - sube
dist/como artefacto de Pages - despliega automáticamente
El sitio está preparado para funcionar sin hardcodear el dominio público en la mayoría de rutas:
- los enlaces internos usan rutas relativas
- el embed de Discourse resuelve con la URL actual del navegador cuando hace falta
Variables opcionales relevantes para feeds:
SITE_URL: URL pública base del sitioPODCAST_FEED_URL: URL pública final del feed de podcastPODCAST_OWNER_NAME: nombre público del propietario del podcast en el RSSPODCAST_OWNER_EMAIL: email público usado por Spotify/iTunes para verificar y mostrar la titularidad del podcast
Para compatibilidad con Spotify e iTunes, el feed de podcast publica el bloque itunes:owner con name y email, y usa una carátula cuadrada específica distinta de la imagen social general.
La página de artículo integra comentarios usando el embed oficial de Discourse en modo fullApp.
Detalles relevantes:
- el iframe se carga también cuando el tema todavía no tiene respuestas
- la sección conserva únicamente su cabecera y deja la interacción al iframe
- si el iframe falla, queda un enlace textual de recuperación
- el contador se actualiza en cliente sin recargar el iframe
La configuración de Discourse y el override visual del tema están documentados en docs/discourse-full-app-setup.md. En local, localhost:8000 y 127.0.0.1:8000 deben tratarse como orígenes distintos: usa el primero para las pruebas previstas o autoriza también el segundo.
Reglas del proyecto:
- los estilos de componentes se importan dentro del flujo de Tailwind
- la UI se resuelve con utilidades de Tailwind y capas de componentes puntuales
- la lógica JS usa hooks
js-*para no depender de clases visuales
Prueba a:
rm -f .cache/articulos.json
npm run buildDepende de la configuración de Discourse y de los orígenes permitidos para embed. Consulta docs/discourse-full-app-setup.md y usa exactamente el mismo host autorizado en el navegador.
Para algunas pruebas conviene servir dist/ en:
http://localhost:4000
Los onebox de Discourse se normalizan durante la build. Si un caso concreto sigue saliendo feo, revisa:
- el HTML original del topic
- el saneado en
src/_data/articulos.js
npm install
npm run build
npm run startY para revisar la salida final:
python3 -m http.server 4000 -d distContenido y proyecto publicados bajo: