Diagrama de arquitectura del sistema MarkeUP

doc-arquitectura-MarkeUp

Arquitectura completa. Arbol de archivos, flujo de datos en build, dependencias entre modulos, CSS y CI/CD.

Admin 15 de junio de 2026, 00:00 14 min de lectura

Orden de lectura recomendado: doc-readme → doc-guia → doc-arquitectura → doc-operacion → doc-auditoria

Revisión: 2026-07-08 · Build: 192 páginas · Componentes Astro: 18 · Archivos fuente: 75+ (incluyendo hermes/)


markeup/
├── src/
│   ├── generar_articulo.py            ← Script Python: genera posts automáticos desde URLs con Ollama
│   ├── telegram_bot.py                ← Bot de Telegram para interactuar con el vault
│   ├── components/
│   │   ├── blog/
│   │   │   ├── Pagination.astro       ← Navegación entre páginas con DaisyUI join
│   │   │   ├── PostCard.astro         ← Tarjeta clickable con ResponsiveImage + lazy loading
│   │   │   └── PostHero.astro         ← Hero con imagen responsiva + fetchpriority="high"
│   │   ├── common/
│   │   │   ├── BackgroundImage.astro  ← Fondo con gradiente mask (top + bottom simultaneous)
│   │   │   ├── Breadcrumb.astro       ← Breadcrumb navegable + JSON-LD Schema.org
│   │   │   ├── Button.astro           ← Botón reutilizable DaisyUI
│   │   │   ├── CategoryHeader.astro   ← Cabecera con título, contador y opción back
│   │   │   ├── DocCard.astro          ← Tarjeta para documentación (icono + label + título)
│   │   │   ├── FeatureCard.astro      ← Tarjeta con icono para sección About
│   │   │   ├── Footer.astro           ← Footer con enlaces (RSS, GitHub, año)
│   │   │   ├── Header.astro           ← Menú responsive con drawer móvil + selector de temas
│   │   │   ├── Icon.astro             ← SVG icon inline (calendar, user, search, etc.)
│   │   │   ├── ResponsiveImage.astro  ← <picture> + srcset WebP + aspect-ratio + fetchpriority
│   │   │   ├── StyledBadge.astro      ← Badge primary/secondary unificado
│   │   │   ├── TableOfContents.astro  ← TOC colapsable con DaisyUI
│   │   │   └── Ventajas.astro         ← Banner rotativo de frases
│   │   └── graph/
│   │       ├── ForceGraph2D.astro     ← Grafo 2D con D3.js force simulation
│   │       └── ForceGraph3D.astro     ← Grafo 3D con Three.js + 3d-force-graph
│   ├── content/
│   │   ├── posts/                     ← 63 posts en 5 categorías (Biodanza, Documentación, Literatura, Opinión, Tecnología)
│   │   │   ├── Biodanza/              ← 34 posts (bienestar, energía, filosofía oriental)
│   │   │   ├── Documentación/         ← 5 docs del sistema
│   │   │   ├── Literatura/            ← 2 posts (Pessoa)
│   │   │   ├── Opinión/               ← 1 post
│   │   │   ├── Tecnología/            ← 21 posts (dev, IA, herramientas)
│   │   │   ├── img/                   ← Imágenes fuente (34+ archivos) — procesadas a public/img/
│   │   │   └── hermes/                ← 🆕 Sistema de gestión IA del vault (12 archivos)
│   │   │       ├── SOUL.md            ← Personalidad y reglas de Hermes Agent
│   │   │       ├── memory/            ← vault-index.md, conventions.md, backlog.md
│   │   │       ├── skills/            ← crear-post, ampliar-post, generar-wikilinks, auditar-vault, documentar
│   │   │       ├── templates/         ← post-template.md (frontmatter YAML 21 campos)
│   │   │       ├── rules/             ← frontmatter-spec, wikilinks-spec, style-guide
│   │   │       └── logs/sessions/     ← Trazabilidad de cada interacción
│   │   └── content.config.ts          ← Define colección "posts": Zod schema + loader glob
│   ├── layouts/
│   │   └── BaseLayout.astro           ← Layout único: HTML shell, Header, Breadcrumb, Footer, OG tags, Lenis
│   ├── lib/
│   │   ├── posts.ts                   ← getAllPosts, getPostsByCategory, getAllCategories, paginate, cleanSlug, getAdjacentPosts
│   │   ├── tags.ts                    ← getAllTags, getPostsByTag
│   │   ├── graph.ts                   ← generateGraphData: nodos (post/category/tag) + enlaces
│   │   ├── images.ts                  ← buildSrcSet, formatSrcSet, DEFAULT_SIZES, CARD_SIZES
│   │   ├── smooth-scroll.js           ← Lenis init/destroy + anchor interception + ViewTransitions re-init
│   │   ├── remark-wikilink.js         ← Plugin Remark: [[wikilink]] → <a href="/posts/slug">
│   │   └── posts.test.ts             ← 6 tests unitarios (Vitest)
│   ├── pages/
│   │   ├── index.astro                ← / (home con hero, stats, categorías, últimos posts, CTA)
│   │   ├── 404.astro                  ← /404
│   │   ├── posts/index.astro          ← /posts (listado completo)
│   │   ├── posts/[...slug].astro      ← /posts/:slug (post individual)
│   │   ├── category/[category]/index.astro     ← /category/:cat
│   │   ├── category/[category]/page/[page].astro ← /category/:cat/page/:n
│   │   ├── categories/index.astro     ← /categories (grid de categorías)
│   │   ├── tags/index.astro           ← /tags (nube de tags)
│   │   ├── tags/[tag].astro           ← /tags/:tag
│   │   ├── graph.astro                ← /graph (grafo 3D)
│   │   ├── graph-2d.astro             ← /graph-2d (grafo 2D)
│   │   ├── search.astro               ← /search (Pagefind)
│   │   ├── feed.xml.ts                ← /feed.xml (RSS)
│   │   ├── animated-sections.astro    ← /animated-sections (demo)
│   │   ├── test-tipografia.astro      ← /test-tipografia (test)
│   │   └── admin/                     ← /admin, /admin/login
│   └── styles/
│       ├── global.css                 ← Barrel: @import base + components + utilities
│       ├── base.css                   ← @tailwind, @font-face (Roboto Flex + Material Symbols), @layer base
│       ├── components.css             ← @layer components: glass-card, post-card, post-hero, etc.
│       └── utilities.css              ← @layer utilities: text-responsive, container-responsive
├── scripts/
│   ├── process-images.js              ← Crop 1920×1080 + variantes 640w/1024w/1280w WebP
│   ├── optimize-images.js             ← Conversión batch a WebP
│   ├── sync-images.js                 ← Sincronización bidireccional src/content/img ↔ public/img
│   ├── compress-audio.js              ← Compresión ffmpeg (límite 10MB Cloudflare)
│   ├── export-blog.js                 ← Exportación offline por categoría
│   └── dev-search.js                  ← Pagefind index para desarrollo local
├── public/
│   ├── img/                           ← 136 imágenes WebP (34 × 4 tamaños)
│   ├── fonts/                         ← Roboto Flex (woff2-variations) + Material Symbols
│   └── pagefind/                      ← Índice de búsqueda (generado en build)
├── astro.config.mjs                   ← site, aliases, plugins, chunkSizeWarningLimit
├── tailwind.config.cjs                ← DaisyUI 6 temas, typography
├── tsconfig.json                      ← strict + path aliases
├── vitest.config.ts                   ← Configuración de tests
└── .github/workflows/                 ← deploy.yml, lighthouse.yml

Scripts del build:

  1. npm run imagesprocess-images.js: genera master 1920×1080 + variantes 640w/1024w/1280w en public/img/
  2. npm run build:astroastro build:
    • content.config.ts carga y valida todos los .md con Zod
    • remark-wikilink.js transforma [[slug]]<a href="/posts/slug">
    • Páginas Astro iteran sobre colecciones (getCollection("posts")) y generan HTML
    • Se generan 192 páginas en dist/
  3. npm run build:searchpagefind --site dist: indexa 192 páginas, 5933 palabras

content.config.ts (Zod schema)

posts.ts  ←  tags.ts  ←  graph.ts
      ↓           ↓          ↓
pages/*.astro (16+ rutas)

components/*.astro (18 componentes)

BaseLayout.astro (HTML shell, OG, Breadcrumb, Lenis)

Regla: lib/ nunca importa de pages/ ni components/. La dependencia es unidireccional hacia arriba.


ComponenteProps claveLoadingEstados
BaseLayouttitle, description, ogType, ogImage, ogImageAlt, preloadImage, breadcrumbTitleOG tags, preload link, Breadcrumb, slot
PostHeropost: Posteager + fetchpriority=highCon/sin imagen, título overlay, categorías, tags, metadatos
PostCardpost: Postlazy + CARD_SIZESCon/sin imagen, descripción truncada, overlay categoría, hover scale
ResponsiveImagesrc, alt, loading, sizes, fetchpriorityconfigurableLocal: <picture> + srcset · Externo: <img> simple
Breadcrumbpathname, title?Ruta raíz (1 item), intermedia (2+ items), con título personalizado
PaginationcurrentPage, totalPages, basePathBotones prev/next, página activa, deshabilitado en extremos
HeadercategoriesDesktop: horizontal · Móvil: drawer con acordeones
TableOfContentsheadings, showTOCColapsable, scroll-to-heading con Lenis
ForceGraph3Ddata: string (JSON)client:visibleCarga asíncrona Three.js, interactivo drag/zoom
ForceGraph2Dclient:visibleCarga asíncrona D3.js, nodos dragables, zoom

Principio: Máximo DaisyUI + Tailwind inline, mínimo CSS manual.

@layer base         ← base.css:       @font-face, headings, anchor links
@layer components   ← components.css: .glass-card, .post-card, .post-hero__image, .category-header
@layer utilities    ← utilities.css:  .text-responsive-*, .container-responsive

Overrides DaisyUI (fuera de @layer para máxima prioridad): botones, badges, headings, iconos, sombras de cards. Ver components.css para el código completo.

Carga condicional: animated-sections.css se importa solo desde animated-sections.astro.

Roboto Flex: Fuente variable cargada localmente (sin CDN) con 6 subsets + font-display: swap. Ejes tipográficos animados en hover: YTUC, YTCF, YTDE, slnt (500ms ease).


  • site: https://mybrain-limpio.pages.dev
  • trailingSlash: never
  • integrations: TailwindCSS
  • vite.aliases: @ → src, @layouts, @components, @lib, @styles
  • chunkSizeWarningLimit: 1000 kB
  • Remark: remarkWikilink (con validSlugs precargados del filesystem)
  • Rehype: rehypeSlug + rehypeAutolinkHeadings

  • plugins: DaisyUI, @tailwindcss/typography
  • themes: light, cupcake, corporate, dark, dim, sunset
  • darkTheme: dark

  • Extiende astro/tsconfigs/strict
  • Path aliases: @/*, @layouts/*, @components/*, @lib/*, @styles/*

on: push → branches: [main]
steps:
  - checkout
  - node 20, npm ci
  - npm run build
  - Cloudflare Pages deploy → mybrain-limpio

on: push + pull_request → branches: [main]
steps:
  - build
  - Lighthouse CI con umbrales: performance ≥ 0.8, a11y ≥ 0.9, best-practices ≥ 0.9, seo ≥ 0.9

ComandoFunción
npm run devServidor desarrollo localhost:4321
npm run buildBuild completo: images + astro + pagefind
npm run build:astroSolo build Astro → dist/
npm run build:searchSolo índice Pagefind
npm run previewPrevisualizar build producción
npm testVitest (6 tests)
npm run imagesProcesar imágenes (crop + variantes)
npm run images:watchModo vigilancia de imágenes
npm run compress-audioComprimir audios con ffmpeg
npm run exportExportar blog por categoría

Script Python externo al build que automatiza la creación de posts a partir de URLs. No forma parte del pipeline Astro, sino que se ejecuta manualmente cuando se quiere generar contenido rápidamente desde una fuente externa.

URL de entrada


┌─────────────────────────────────┐
│  1. extraer_texto(url)          │
│  requests → trafilatura/BS4     │
│  Extrae texto limpio del HTML   │
└──────────────┬──────────────────┘
               │ contenido (string)

┌─────────────────────────────────┐
│  2. detectar_categoria(contenido)│
│  Cuenta palabras clave por      │
│  categoría. Asigna la de mayor  │
│  puntuación. Default: "Documentación" │
└──────────────┬──────────────────┘
               │ categoria (string)

┌─────────────────────────────────┐
│  3. generar_tags(contenido)     │
│  Extrae palabras frecuentes,    │
│  filtra stopwords, normaliza a  │
│  1-2 palabras con guiones.      │
│  Mínimo 3, máximo 8 tags.       │
└──────────────┬──────────────────┘
               │ tags (list)

┌─────────────────────────────────┐
│  4. construir_prompt()          │
│  Arma prompt detallado con:     │
│  - Frontmatter YAML completo    │
│  - Estructura del artículo      │
│  - Formato Markdown avanzado    │
│  - Contenido fuente (15K chars) │
└──────────────┬──────────────────┘
               │ prompt (string)

┌─────────────────────────────────┐
│  5. Ollama API                  │
│  POST localhost:11434/api/generate│
│  Modelo: qwen2.5-coder:7b-64k   │
│  num_ctx: 65536, temp: 0.7       │
│  Timeout: 600s (10 min)          │
└──────────────┬──────────────────┘
               │ artículo Markdown

┌─────────────────────────────────┐
│  6. guardar_articulo()          │
│  Extrae bloque ```markdown      │
│  Guarda en tmp/ → mueve a       │
│  src/content/posts/<Categoría>/ │
└─────────────────────────────────┘

El script construye un prompt que especifica al modelo:

  • Frontmatter YAML obligatorio con todos los campos del schema Zod del blog: title, slug, description, pubDate, updatedDate, categories, tags (formato lista con guiones), image, imageAlt, canonicalUrl, aliases, noIndex, showTOC, hideCoverImage, allowComments, share
  • Estructura fija: introducción (4-5 párrafos), desarrollo con 4-6 secciones (H2, H3, H4, H5), conclusión (3-4 párrafos), referencias (mínimo 5 fuentes)
  • Formato Markdown avanzado: listas, bloques de nota (> [!NOTE], > [!TIP]), citas, negritas, cursivas
  • Restricción: no inventar información, basarse solo en el contenido proporcionado
  • Longitud: 3000 a 5000 palabras

El script mantiene un diccionario de palabras clave por cada categoría del blog:

CategoríaPalabras clave
Biodanzabiodanza, danza, movimiento, expresión, cuerpo, emoción, ritmo, música
Documentacióndocumentación, documentar, guía, manual, tutorial, cómo, paso a paso, referencia, api
Tecnologíatecnología, software, hardware, programación, código, ia, inteligencia artificial, machine learning, deep learning, redes, computación, algoritmo, datos, seguridad, cloud, devops
Opiniónopinión, reflexión, ensayo, análisis, crítica, perspectiva, personal, experiencia
Literaturaliteratura, poesía, novela, cuento, ensayo literario, autor, obra, lectura, escritura

El algoritmo cuenta las apariciones de cada keyword en el texto extraído. La categoría con más coincidencias gana. Si hay empate en 0 (sin coincidencias), se asigna “Documentación” por defecto.

El proceso de generación de etiquetas sigue estos pasos:

  1. Tokeniza el contenido en palabras de 3+ letras (incluye acentos y ñ)
  2. Filtra stopwords comunes en español (~40 palabras: “que”, “para”, “como”, “por”, etc.)
  3. Cuenta frecuencias y selecciona las 10 más frecuentes
  4. Inserta la primera palabra del slug del título como tag principal
  5. Normaliza cada tag: une pares de palabras con guiones, elimina caracteres no alfanuméricos
  6. Asegura mínimo 3 tags (añade “general” si faltan) y máximo 8

DependenciaPropósitoInstalación
OllamaServidor local de modelos LLMbrew install ollama
qwen2.5-coder:7b-64kModelo de lenguaje (7B params, 64K contexto)ollama pull qwen2.5-coder:7b-64k
requestsDescarga HTTP de la URL y llamada a API de Ollamauv pip install requests
trafilaturaExtracción de texto principal de HTML (opcional, fallback a BeautifulSoup)uv pip install trafilatura
beautifulsoup4Parseo HTML como fallback si trafilatura fallauv pip install beautifulsoup4

El script genera un archivo .md con frontmatter completo directamente en la carpeta de categoría correspondiente (src/content/posts/<Categoría>/[slug].md). Una vez generado:

  1. Syncthing lo sincroniza a Obsidian (si se ejecutó en el Mac de build)
  2. El post aparece en npm run build como cualquier otro contenido
  3. Si draft: false, se publica en el siguiente deploy
  4. Las imágenes referenciadas en el frontmatter (image: '/img/[slug].webp') deben añadirse manualmente después

DocumentoContenido
doc-readmeVisión general y stack
doc-guiaInstalación y configuración
doc-operacionComandos y troubleshooting
doc-auditoriaAuditoría y deuda técnica
markeup/
├── src/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   ├── lib/
│   ├── markeup_tools/
│   ├── pages/
│   ├── styles/
│   └── types/
├── scripts/
├── public/
└── package.json
📂 Árbol completo (generado 2026-07-09 20:33)
        ├── content.d.ts
    ├── types.d.ts
        ├── lighthouserc.json
        ├── deploy.yml
        ├── lighthouse.yml
├── .gitignore
    ├── app.json
    ├── appearance.json
    ├── community-plugins.json
    ├── core-plugins.json
    ├── graph.json
            ├── data.json
            ├── embeddings.json
            ├── main.js
            ├── manifest.json
            ├── styles.css
            ├── data.json
            ├── main.js
            ├── manifest.json
            ├── styles.css
    ├── workspace.json
    ├── settings.json
├── Modelfile
├── astro.config.mjs
├── cline-instructions.md
├── commit-local.sh
├── deploy.sh
            ├── 2026-07-09.md
├── package.json
    ├── _headers
        ├── Presuncion.mp3
        ├── euritmia-steiner.mp3
        ├── glandula-pineal.mp3
        ├── registros-akashicos.mp3
        ├── teoria-polivagal.mp3
        ├── teosofia.mp3
        ├── material-symbols-outlined.woff2
        ├── roboto-flex-cyrillic-ext.woff2
        ├── roboto-flex-cyrillic.woff2
        ├── roboto-flex-greek.woff2
        ├── roboto-flex-latin-ext.woff2
        ├── roboto-flex-vietnamese.woff2
        ├── roboto-flex.woff2
        ├── Neumorfismo.webp
        ├── Telegram-Ollama-Obsidian-Astro.webp
        ├── agni-yoga.webp
        ├── agua.webp
        ├── ancestros.webp
        ├── antroposofia-steiner-euritmia.webp
        ├── apps-chatgpt.webp
        ├── aura.webp
        ├── biodanza-danza-vida.webp
        ├── caretas-mascaras.webp
        ├── chackras.webp
        ├── chi.webp
        ├── cloudflare.webp
        ├── corrupcion.webp
        ├── docker.webp
        ├── documentacion.webp
        ├── el-ego.webp
        ├── el-libro-del-desasosiego-anatomia-del-tedio-existencial.webp
        ├── euritmia-steiner.webp
        ├── gestualidad-facial.webp
        ├── glandula-pineal.webp
        ├── grafo.webp
        ├── graphify.webp
        ├── grounding.webp
        ├── hermes-agent.webp
        ├── home.webp
        ├── inteligencia.webp
        ├── la-ansiedad.webp
        ├── la-mentira.webp
        ├── la_respiracion.webp
        ├── ladanzadelencuentro.webp
        ├── literatura.webp
        ├── material-design.webp
        ├── mesmerismo.webp
        ├── mirada.webp
        ├── motionsites.webp
        ├── musica-esferas.webp
        ├── npm-node.webp
        ├── nvidia-nim.webp
        ├── obsidian.webp
        ├── orgon.webp
        ├── pitagoras.webp
        ├── placeholder.webp
        ├── posts.webp
        ├── psicomotricidad-relacional.webp
        ├── roerich.webp
        ├── silva-mind-control.webp
        ├── sonidos-guturales.webp
        ├── syncthing.webp
        ├── tags.webp
        ├── tailwind.webp
        ├── tecnologiablog.webp
        ├── teoria-polivagal.webp
        ├── teosofia-desarrollo-personal.webp
        ├── trascendencia.webp
        ├── vault-cms-cover.webp
        ├── vitalidad.webp
Desarrollo
npm run devServidor desarrollo localhost:4321
Build
npm run buildBuild completo: images + astro + pagefind
npm run build:astroSolo build Astro → dist/
npm run build:searchSolo índice Pagefind
Imágenes
npm run imagesProcesar imágenes (crop + WebP)
npm run images:watchModo vigilancia de imágenes
npm run sync:imagesSincronizar src/img ↔ public/img
Audio
npm run compress-audioComprimir audios con ffmpeg
npm run compress-audio:watchModo vigilancia de audios
npm run compress-audio:forceRecomprimir todos los audios
Tools
npm run tools🧠 Menú interactivo MarkeUP Command Center v6
npm run tools:crearCrear post (atajo directo)
npm run tools:auditarAuditar vault (atajo directo)
npm run tools:statsDashboard stats (atajo directo)
npm run tools:hermesIniciar Hermes Agent (atajo directo)
npm run tools:ollamaEstado Ollama en JSON
npm run tools:telegramIniciar bot Telegram
Testing
npm run testEjecutar tests (Vitest)
npm run test:watchTests en modo watch
Deploy
npm run previewPrevisualizar build producción
npm run exportExportar blog por categoría
npm run auto-commitAuto-sync Android: images → commit → push
npm run dev:searchÍndice Pagefind para desarrollo