# David Aragón — Portfolio Portfolio personal y blog técnico de David Aragón. Sitio estático construido con Astro 5, donde comparto proyectos en los que estoy trabajando (build in public), posts sobre IoT, desarrollo y decisiones técnicas, y una página de about con mi trayectoria. - **URL pública:** https://davidaragon.impresion3d.pro - **Autor:** David Aragón — de orfebre a fotógrafo a developer - **Repositorio:** https://gitlab.impresion3d.pro/root/davidaragon-portfolio ## Características - **Home** con hero, últimos 3 posts del blog, proyectos destacados y CTA de newsletter - **Blog** (`/blog`) con posts categorizados en `technical`, `business` y `personal`, con RSS en `/rss.xml` - **Projects** (`/projects`) con proyectos en estados `active`, `development` o `completed` - **About** (`/about`) con la trayectoria profesional completa - **Sitemap** autogenerado en `/sitemap-index.xml` por `@astrojs/sitemap` - **Newsletter** vía formulario en la home (acción `/api/subscribe`) - **Type-safe content** con Zod schemas en `src/content/config.ts` - **Búsqueda y SEO**: meta tags, Open Graph y RSS listos para compartir ## Stack técnico - **Astro 5** — framework de sitios estáticos con islands - **TypeScript** — tipado estricto en todo el código - **Tailwind CSS 3** — utility-first CSS con `@tailwindcss/typography` para prose - **Zod** — validación de frontmatter en content collections - **RSS** vía `@astrojs/rss` - **Sitemap** vía `@astrojs/sitemap` - **Nginx** como servidor estático en el contenedor de producción - **Docker** multi-stage (Node para build, nginx para servir) - **CI/CD** con Gitea Actions en `.gitea/workflows/ci-cd.yaml` - **Deploy** automático a Portainer vía webhook ## Quick start (local) Requisitos: Node.js >= 18.14.1 (recomendado 20). ```bash # 1. Instalar dependencias npm install # 2. Arrancar dev server con HMR npm run dev # -> http://localhost:4321 # 3. Build de producción (genera ./dist/) npm run build # 4. Preview local del build npm run preview ``` ## Despliegue (producción) El sitio se construye como imagen Docker y se publica en el registry de Gitea. Portainer hace pull y reinicia el contenedor cuando el CI dispara el webhook. ```bash # Construir y servir localmente con Docker Compose docker compose up --build # -> http://localhost:8080 # Producción: pull de la imagen publicada por CI docker compose -f docker-compose.prod.yml up -d # -> http://localhost:3001 ``` La imagen final (`nginx:alpine`) solo contiene los estáticos generados en `./dist/`. Ver `Dockerfile` para detalles del multi-stage. ## Cómo añadir contenido ### Nuevo post de blog Crea un archivo Markdown en `src/content/blog/` con nombre `YYYY-MM-DD-slug.md`. El frontmatter debe cumplir el schema de `src/content/config.ts`: ```markdown --- title: "Título del post" description: "Resumen corto para SEO y previews" publishDate: 2026-07-09 author: "David Aragón" # opcional, default: David Aragón tags: ["tag1", "tag2"] category: "technical" # technical | business | personal featured: false # true para destacar en home draft: false # true para ocultar del sitio público image: "/ruta/opcional.jpg" # opcional --- # Contenido del post en Markdown ``` Aparecerá automáticamente en `/blog/` y, si no es draft, en la home. ### Nuevo proyecto Crea un archivo Markdown en `src/content/projects/` con frontmatter: ```markdown --- title: "Nombre del proyecto" description: "Descripción de una línea" url: "https://proyecto.example.com" github: "https://github.com/user/repo" # opcional status: "development" # active | development | completed tags: ["React", "FastAPI"] startDate: 2026-07-01 featured: true # true para mostrar en home image: "/ruta/opcional.jpg" # opcional --- # Descripción completa del proyecto en Markdown ``` Aparecerá en `/projects/` y, si `featured: true`, también en la home. ## Estructura del proyecto ``` . ├── astro.config.mjs # Configuración Astro (site, integrations, markdown) ├── tailwind.config.cjs # Tailwind theme y content paths ├── tsconfig.json # TS estricto ├── package.json # Deps y scripts ├── Dockerfile # Multi-stage build (node -> nginx) ├── docker-compose.yml # Local dev/test ├── docker-compose.prod.yml # Producción (pull de registry) ├── nginx.conf # Configuración nginx ├── .gitea/workflows/ # Pipelines CI/CD │ └── ci-cd.yaml ├── public/ # Assets estáticos servidos tal cual (favicon, david-aragon.jpg, etc.) └── src/ ├── components/ │ ├── layout/ # Header, Footer │ └── ui/ # Card, Tag ├── content/ │ ├── config.ts # Schemas Zod para blog y projects │ ├── blog/ # Posts en Markdown │ └── projects/ # Proyectos en Markdown ├── layouts/ # BaseLayout, BlogLayout, ProjectLayout ├── pages/ │ ├── index.astro # Home │ ├── about.astro # About │ ├── blog/ # Index + [slug].astro │ ├── projects/ # Index + [slug].astro │ └── rss.xml.ts # Feed RSS (si está generado) ├── styles/ # CSS global └── utils/ # dateFormat.ts, readingTime.ts ``` ## CI/CD Pipeline definido en `.gitea/workflows/ci-cd.yaml`. Trigger: push a `main`. 1. **Build**: checkout, login en el registry de Gitea, build de la imagen Docker multi-stage 2. **Push**: publica `gitlab.impresion3d.pro/root/davidaragon-portfolio:latest` 3. **Deploy**: el webhook configurado en Portainer hace pull de la nueva imagen y reinicia el contenedor Secrets necesarios en el repo (Settings → Secrets): - `DOCKER_USERNAME` — usuario del registry (normalmente `root`) - `DOCKER_PASSWORD` — token personal con permisos de registry Ver `CI-CD-SETUP.md` para instrucciones detalladas de Portainer y registro de runners. ## Licencia Código del sitio: ver `LICENSE` si existe en el repo. El contenido (posts y proyectos) es © David Aragón. ## Créditos - Construido con [Astro](https://astro.build) - Estilado con [Tailwind CSS](https://tailwindcss.com) - Hospedado en infraestructura propia (Gitea + Portainer)