Imagen representativa de la arquitectura

doc-arquitectura

Árbol completo del proyecto, flujo de build, dependencias entre módulos, CSS, configuración y CI/CD.

15 de junio de 2026, 00:00 16 de junio de 2026, 00:00 Admin, Ernesto Uriszar
documentacion
arquitecturadiagramacomponentesdisenoestructuraglassmorphism

doc-arquitectura — MarkUP

Última revisión: 2026-06-16 LOC total: ~3,500+ líneas en 35+ archivos fuente (src/)

Este documento describe la arquitectura del proyecto MarkUP en detalle: el árbol completo de archivos, cómo fluyen los datos durante el build, cómo se relacionan los módulos entre sí, cómo está organizado el CSS, la configuración de las herramientas y el pipeline de CI/CD.


1. Árbol completo del proyecto

A continuación se muestra el árbol completo del proyecto con una breve descripción de cada archivo/directorio. Los elementos marcados con ⚠️ son archivos huérfanos o problemáticos.

markeup/
├── .astro/                          ← Generado por Astro (types, settings)

├── .github/                         ← CI/CD
│   ├── lighthouse/
│   │   └── lighthouserc.json        ← Configuración de Lighthouse CI (umbrales de rendimiento)
│   └── workflows/
│       ├── deploy.yml               ← Workflow de deploy a Cloudflare Pages
│       └── lighthouse.yml           ← Workflow de Lighthouse CI automático

├── .obsidian/                       ← Configuración del vault Obsidian en Mac
│   ├── app.json                     ← Configuración general de la app Obsidian
│   ├── appearance.json              ← Tema y apariencia de Obsidian
│   ├── core-plugins.json            ← Plugins core activados en Obsidian
│   └── workspace.json               ← Estado del workspace (paneles abiertos, etc.)

├── .vscode/
│   └── settings.json                ← Configuración de VSCode específica del proyecto (+ Cline)

├── public/                          ← Archivos estáticos servidos en la raíz del sitio
│   ├── img/                         ← 40 imágenes estáticas en formato WebP (~40 archivos)
│   └── pagefind/                    ← Índice de búsqueda generado por Pagefind durante el build

├── scripts/                         ← Scripts auxiliares (Node.js)
│   ├── dev-search.js                ← Genera el índice Pagefind para desarrollo
│   ├── export-blog.js               ← Exporta un blog por categoría a un directorio de salida
│   ├── optimize-images.js           ← Convierte imágenes a WebP usando sharp
│   └── sync-obsidian.js             ← ⚠️ OBSOLETO — Watcher con chokidar (reemplazado por watchexec)

├── src/
│   ├── components/                  ← Componentes reutilizables de Astro
│   │   ├── blog/                    ← Componentes relacionados con el listado de posts
│   │   │   ├── Pagination.astro     ← Paginación responsive (anterior/siguiente)
│   │   │   ├── Pagination.css       ← Estilos de la paginación
│   │   │   ├── PostCard.astro       ← Tarjeta clickable de post (con hover y overlay)
│   │   │   ├── PostCard.css         ← Estilos de la tarjeta (con glassmorphism desde Junio 2026)
│   │   │   ├── PostHero.astro       ← Hero de post individual (con o sin imagen de portada)
│   │   │   └── PostHero.css         ← Estilos del hero
│   │   ├── common/                  ← Componentes compartidos (header, footer, botones, tema)
│   │   │   ├── Button.astro         ← Botón reutilizable (6 variantes × 5 tamaños)
│   │   │   ├── Footer.astro         ← Footer simple con enlaces
│   │   │   ├── Footer.css           ← Estilos del footer
│   │   │   ├── Header.astro         ← Menú responsive con glassmorphism
│   │   │   ├── Header.css           ← Estilos del header (sin overflow:hidden desde Junio 2026)
│   │   │   ├── ThemeSelector.astro  ← Selector de 6 temas DaisyUI
│   │   │   └── ThemeSelector.css    ← Estilos del selector de tema
│   │   └── graph/
│   │       └── ForceGraph3D.astro   ← Grafo 3D interactivo (3d-force-graph + Three.js)
│   │
│   ├── content/                     ← Contenido del sitio
│   │   ├── posts/                   ← ✅ Colección de posts (markdown con frontmatter)
│   │   │   ├── Biodanza/            ← 12 posts + 4 subposts (cuatro-elementos)
│   │   │   ├── Literatura/          ← 2 posts (Fernando Pessoa)
│   │   │   ├── documentacion/       ← 5 archivos de documentación del sistema
│   │   │   ├── img/                 ← ⚠️ Imágenes duplicadas de public/img/
│   │   │   ├── sample-folder-based-post/  ← Post demo con imágenes adjuntas
│   │   │   └── *.md                 ← 7 posts de tecnología + 2 de prueba
│   │   └── content.config.ts        ← Schema Zod + loader glob (define la colección "posts")
│   │
│   ├── layouts/
│   │   └── BaseLayout.astro         ← Layout base: View Transitions, Header, Footer, slot
│   │
│   ├── lib/                         ← Lógica del negocio
│   │   ├── graph.ts                 ← generateGraphData() — genera datos para el grafo 3D (~180 LOC)
│   │   ├── posts.ts                 ← CRUD de posts: getAllPosts, getPostsByCategory, etc.
│   │   ├── posts.test.ts            ← Tests unitarios (6 tests con Vitest)
│   │   ├── remark-wikilink.js       ← Plugin Remark: procesa [[wikilinks]] → /posts/slug
│   │   └── tags.ts                  ← getAllTags(), getPostsByTag()
│   │
│   ├── pages/                       ← 15 rutas que definen la URL structure
│   │   ├── 404.astro                ← Página de error 404
│   │   ├── admin/
│   │   │   ├── index.astro          ← Panel de administración (stats + rebuild)
│   │   │   └── login.astro          ← Login con JWT
│   │   ├── animated-sections.astro  ← Página demo con secciones animadas
│   │   ├── categories/index.astro   ← Listado de todas las categorías (con glassmorphism desde Junio 2026)
│   │   ├── category/[category]/
│   │   │   ├── index.astro          ← Posts filtrados por categoría
│   │   │   └── page/[page].astro    ← Paginación de categoría
│   │   ├── feed.xml.ts              ← RSS feed global
│   │   ├── graph.astro              ← Página del grafo 3D (con glassmorphism desde Junio 2026)
│   │   ├── index.astro              ← Página principal (con hero glassmorphism unificado)
│   │   ├── posts/
│   │   │   ├── [...slug].astro      ← Página de post individual
│   │   │   └── index.astro          ← Listado de todos los posts (con glassmorphism desde Junio 2026)
│   │   ├── search.astro             ← Página de búsqueda con Pagefind (262 LOC)
│   │   └── tags/
│   │       ├── [tag].astro          ← Posts filtrados por tag
│   │       └── index.astro          ← Tag cloud (nube de tags)
│   │
│   ├── styles/
│   │   └── global.css               ← 202 LOC: grids fluidos, tipografía responsive, wikilinks
│   │
│   ├── types/
│   │   └── pagefind.d.ts            ← Tipos TypeScript para la API de Pagefind
│   │
│   ├── content.config.ts            ← Schema Zod + loader glob (duplicado para claridad)
│   └── env.d.ts                     ← Tipos de entorno de Astro

├── auto-commit.sh                   ← Script ejecutado por watchexec: git add → commit → push
├── astro.config.mjs                 ← Configuración de Astro: site, aliases, plugins remark
├── cline-instructions.md            ← Instrucciones/prompts para la IA Cline
├── package.json                     ← Dependencias npm y scripts
├── tailwind.config.cjs              ← Configuración de Tailwind + DaisyUI (6 temas)
├── tsconfig.json                    ← TypeScript strict + path aliases
├── vitest.config.ts                 ← Configuración de Vitest

├── nohup.out                        ← ⚠️ Log de nohup (archivo huérfano, se puede eliminar)
├── watch-native.log                 ← ⚠️ Log de watchexec (vacío, se puede eliminar)
└── watch-native-error.log           ← ⚠️ Log de error de watcher (contiene error de sintaxis)

2. Flujo de datos en build time

Cuando ejecutas npm run build, ocurre lo siguiente:

Obsidian vault (notas.md + imágenes)
     ↓ Syncthing (sincronización en tiempo real)
src/content/posts/  (~30 posts .md)
     ↓ Build time (Astro 4.16 → Node.js)
     ├── 1. content.config.ts:
     │      Zod valida el frontmatter de cada post (título, slug, fecha, etc.)
     │      Si algún post tiene frontmatter inválido, el build falla con ZodError

     ├── 2. remark-wikilink.js:
     │      Procesa los [[enlaces]] estilo Obsidian y los convierte en
     │      enlaces HTML a /posts/slug-correspondiente
     │      Ejemplo: [[el-agua]] → <a href="/posts/el-agua">el-agua</a>

     ├── 3. posts.ts:
     │      Filtra posts (draft=false), los ordena por fecha,
     │      los agrupa por categoría, prepara la paginación

     ├── 4. graph.ts:
     │      Lee las relaciones de cada post (enlaces entre posts)
     │      y genera graph-data.json para el grafo 3D

     └── 5. sharp (scripts/optimize-images.js):
         Opcional — convierte imágenes a WebP para optimizar rendimiento

~150 páginas HTML estáticas
     + graph-data.json (datos para 3d-force-graph)
     + feed.xml (RSS con todos los posts)
     + pagefind/ (índice de búsqueda full-text)
     ↓ GitHub Actions → Cloudflare Pages (deploy automático)

¿Qué pasa si el build falla?

  1. ZodError: Frontmatter inválido (el error muestra qué archivo y qué campo)
  2. Error de import: Algún componente Astro tiene un error de sintaxis o import incorrecto
  3. Error de dependencia: Algún paquete npm falta o está corrupto

En todos los casos, GitHub Actions muestra el error en los logs y no despliega. No hay notificación automática (ver doc-auditoria sección 4 sobre vulnerabilidades).


3. Dependencias entre módulos

El código de src/ está organizado en capas. Cada capa solo depende de las que están debajo:

content.config.ts (Zod schema, loader glob)
      ↓ Define la colección "posts" y valida el frontmatter
posts.ts (getAllPosts, getPostsByCategory, getAllCategories,
          getFeaturedPostsByCategory, paginate, cleanSlug)
      ↓ Exporta funciones que leen la colección definida por content.config.ts
tags.ts (getAllTags, getPostsByTag)       ← depende de posts.ts
graph.ts (generateGraphData)              ← depende de posts.ts
remark-wikilink.js ([[slug]] → /posts/slug) ← independiente (solo procesa markdown)

pages/* (15 rutas Astro que importan funciones de lib/)

Regla importante: Ningún módulo de lib/ importa directamente de pages/ o components/. La dirección de las dependencias es siempre hacia abajo.

Layout y componentes (árbol de renderizado)

BaseLayout.astro (View Transitions, flex layout, slot para contenido)
├── Header.astro
│   ├── Header.css
│   ├── ThemeSelector.astro + ThemeSelector.css
│   └── Script de menú hamburguesa (responsive)
├── Footer.astro + Footer.css
└── <slot />  ← Aquí se inyecta el contenido de cada página

¿Qué es el <slot />? En Astro, el slot es donde se renderiza el contenido de la página que usa el layout. Por ejemplo, si index.astro usa BaseLayout, todo el HTML de index.astro se inserta en el slot.


4. Páginas y dependencias directas

Cada página importa funciones de lib/ y componentes de components/. A continuación se muestra qué usa cada una:

Página (ruta)LOCDependencias
index.astro (/)42BaseLayout
404.astro16BaseLayout
search.astro (/search)262BaseLayout, Pagefind (librería externa)
graph.astro (/graph)36BaseLayout, ForceGraph3D, generateGraphData
feed.xml.ts (/feed.xml)18getAllPosts
posts/index.astro (/posts)25BaseLayout, getAllPosts, PostCard
posts/[...slug].astro (/posts/:slug)24BaseLayout, getAllPosts, PostHero
categories/index.astro (/categories)27BaseLayout, getAllCategories
category/[category]/index.astro39BaseLayout, getAllCategories, getPostsByCategory, PostCard, Pagination
category/[category]/page/[page].astro49BaseLayout, getAllCategories, getPostsByCategory, PostCard, Pagination
tags/index.astro (/tags)42BaseLayout, getAllTags
tags/[tag].astro (/tags/:tag)63BaseLayout, getPostsByTag, getAllTags, PostCard
admin/index.astro (/admin)97BaseLayout (JS cliente para stats y rebuild)
admin/login.astro (/admin/login)72BaseLayout (JWT login)
animated-sections.astroBaseLayout (página demo)

Todas las páginas usan BaseLayout, que a su vez incluye Header y Footer. Esto significa que Header y Footer se renderizan en todas las páginas.


5. Componentes y sus estados

Cada componente Astro tiene una función específica y un conjunto de estados visuales:

ComponenteArchivosLOC totalFunciónEstados
PostCard.astro + .css104 + 181 = 285Tarjeta clickable que muestra un postNormal, hover (700ms), con overlay de categoría
PostHero.astro + .css107 + 235 = 342Hero del post (título, metadatos, imagen)Con imagen de fondo, sin imagen, responsive (móvil/escritorio)
Pagination.astro + .css54 + 84 = 138Navegación anterior/siguiente entre páginasNormal, deshabilitado (cuando no hay anterior/siguiente), sin páginas suficientes
Header.astro + .css140 + 311 = 451Menú de navegación principalEscritorio (menú horizontal), móvil (hamburguesa), con dropdowns
Footer.astro + .css11 + 44 = 55Pie de página simpleÚnico estado
Button.astro52Botón reutilizable6 variantes × 5 tamaños = 30 combinaciones
ThemeSelector.astro + .css66 + 128 = 194Selector de tema DaisyUICerrado, abierto (dropdown), con tema activo
ForceGraph3D.astro73Grafo 3D interactivoCargando (mientras Three.js inicializa), renderizado, error (si no hay datos)
BaseLayout.astro38Layout base que envuelve todoÚnico estado (contiene Header y Footer)

Ciclo de vida de un componente como PostCard

  1. Renderizado en servidor: Astro obtiene los datos del post (título, slug, imagen, categoría) y genera el HTML estático.
  2. Hidratación en cliente: El CSS se aplica, los hovers funcionan, los enlaces son navegables.
  3. Interacción: El usuario hace hover (700ms de transición), ve el overlay de categoría, y hace clic para ir al post.

6. Arquitectura CSS

Regla fundamental

Todo el CSS vive en archivos .css independientes (uno por componente). Está prohibido usar clases inline de Tailwind en los templates .astro. Esto mantiene el CSS limpio, reutilizable y fácil de mantener.

¿Por qué CSS separado y no Tailwind inline?

Archivos CSS (7 archivos, ~1,185 LOC total)

ArchivoLOCContenido
global.css202Grids fluidos (5 variantes), tipografía responsive con clamp(), estilos de wikilinks, animaciones globales
PostCard.css181Variables CSS, card clickable, overlay de categorías con glassmorphism, transiciones hover
PostHero.css235Hero con/sin imagen de fondo, badges de categorías y tags, metadatos responsive
Pagination.css84Navegación responsive, oculta páginas en móvil, centrado
Header.css311Variables CSS, navbar, botones glassmorphism, dropdowns, menú hamburguesa para móvil
Footer.css44Footer simple con enlaces, centrado, padding
ThemeSelector.css128Dropdown glassmorphism, items con secondary badges, animaciones de entrada/salida

Patrón BEM utilizado

.bloque                   → Componente principal (ej: .site-header)
.bloque__elemento         → Elemento hijo (ej: .site-header__btn)
.bloque--modificador      → Variante (ej: .site-header--transparent)

Glassmorphism unificado

El efecto glassmorphism (fondo semitransparente con blur) se usa en varios componentes. Todos siguen el mismo patrón:

SelectorBackgroundBlurHover
.site-header__btnoklch(var(--p) / 0.5)24pxoklch(var(--p) / 1)
.site-header__hamburgeroklch(var(--p) / 0.5)24pxoklch(var(--p) / 1)
.theme-selector__btnoklch(var(--p) / 0.5)24pxoklch(var(--p) / 1)
.post-card .btn.btn-primaryoklch(var(--p) / 0.5)24pxoklch(var(--p) / 1)
.site-header__dropdown-contenthsl(var(--b1) / 0.7)24px
.theme-selector__contenthsl(var(--b1) / 0.7)24px
.site-header__dropdown-itemoklch(var(--s) / 0.5)oklch(var(--s) / 1)
.theme-selector__itemoklch(var(--s) / 0.5)oklch(var(--s) / 1)

Glassmorphism en botones de acción (Hero, CTA) - Junio 2026

Desde la actualización de Junio 2026, todos los botones de la home (index.astro) y las páginas de categorías/posts siguen este patrón unificado con blur máximo:

SelectorBackgroundBlurHover
.btn-primary (Hero)oklch(var(--p) / 0.5)48pxOpaco (hover DaisyUI)
.btn-ghost (Hero)hsl(var(--b2) / 0.4)48pxGhost hover (DaisyUI)
.btn-outline (CTA)hsl(var(--b2) / 0.4)48pxOutline hover (DaisyUI)
Badge Herooklch(var(--p) / 0.4)48px
Stats Herohsl(var(--b1) / 0.4)24px
Cards seccion Aboutbg-base-200/60Borde primario
PostCardhsl(var(--b1) / 0.5)24pxImagen scale(1.25)

Principios del glassmorphism unificado:

Nota tecnica: oklch() es un espacio de color moderno que permite transparencias predecibles. hsl() se usa en algunos casos donde oklch no es compatible con ciertas variables de DaisyUI.


7. Configuración

astro.config.mjs

tailwind.config.cjs

tsconfig.json

vitest.config.ts


8. CI/CD

GitHub Actions — deploy.yml

# Se ejecuta en cada push a main
on: push → branches: [main]
jobs:
  - Checkout del repositorio
  - Setup Node.js 20
  - npm ci (working-dir: multiblog-obsidian)
  - npm run build (con variables de entorno: ADMIN_PASSWORD, JWT_SECRET)
  - cloudflare/wrangler-action: pages deploy → mybrain-limpio

⚠️ Nota importante: El working-directory en deploy.yml usa multiblog-obsidian. Este es el nombre que tenía el proyecto originalmente. En GitHub Actions, el directorio se crea con el nombre del repositorio en GitHub, que podría ser markeup o multiblog-obsidian. Si el build falla en GitHub Actions, esta es la primera causa a revisar.

GitHub Actions — lighthouse.yml

# Se ejecuta en cada push y pull request a main
on: push + pull_request → branches: [main]
steps:
  - Build del sitio
  - treosh/lighthouse-ci-action con lighthouserc.json
  - Umbrales mínimos:
      performance: ≥ 0.8
      accessibility: ≥ 0.9
      best-practices: ≥ 0.9
      seo: ≥ 0.9

9. Scripts npm

ComandoQué haceCuándo usarlo
npm run devInicia servidor de desarrollo en localhost:4321Mientras desarrollas o pruebas cambios
npm run buildBuild completo: astro build + pagefindAntes de hacer push para verificar que todo compila
npm run build:astroSolo build de Astro → genera dist/Si solo quieres ver el HTML sin búsqueda
npm run build:searchSolo indexar búsqueda con Pagefind (requiere dist/)Si ya tienes dist/ y solo quieres regenerar el índice
npm run previewPrevisualizar el build de producción localmentePara ver cómo queda el sitio antes de publicar
npm testEjecuta Vitest (6 tests)Después de modificar lib/posts.ts
npm run test:watchVitest en modo watch (se re-ejecuta al guardar)Mientras desarrollas cambios en la lógica
npm run export <cat> <dir>Exporta un blog por categoría a un directorioPara compartir contenido offline
npm run sync⚠️ OBSOLETO — Usar watchexec + launchd en su lugarNo usar
npm run optimize-imagesConvierte imágenes a WebP con sharpCuando añades imágenes nuevas al blog