guia — MarkUP
Última revisión: 2026-06-15 Propósito: Explicar cómo configurar cada pieza del sistema para que funcione correctamente.
1. Syncthing (sincronización Android ↔ Mac)
Syncthing sincroniza la carpeta src/content/ entre Android y Mac. No usa ningún servicio en la nube — es peer-to-peer (los archivos viajan directamente entre dispositivos).
¿Por qué Syncthing y no otra cosa?
- No requiere conexión a internet (funciona en red local)
- Cifrado de extremo a extremo (los archivos viajan cifrados)
- Sin servidores intermedios (nadie más tiene acceso a tus notas)
- Detección de cambios en tiempo real (no hay que esperar N minutos)
1.1 Configuración en Mac
La app Syncthing para Mac se descarga desde syncthing.net. Una vez instalada:
| Parámetro | Valor |
|---|---|
| Ruta compartida | /Users/ernestouriszar/markeup/src/content |
| Tipo de carpeta | Enviar y Recibir |
| Versionado | Simple (5 versiones) — guarda copias de archivos modificados |
| Intervalo de escaneo | 60 segundos — cada minuto revisa si hay cambios |
| Ignorar permisos | Activado — evita problemas de permisos entre Android y Mac |
| Patrones ignorados | .stversions/, *.sync-conflict*, .DS_Store |
Cómo añadir la carpeta:
- Abre Syncthing en Mac
- Haz clic en “Add Folder”
- Selecciona la ruta
/Users/ernestouriszar/markeup/src/content - Configura los parámetros como se indica arriba
- Acepta
1.2 Configuración en Android
La app Syncthing para Android se descarga desde F-Droid (recomendado) o Google Play.
| Parámetro | Valor |
|---|---|
| Ruta destino | /storage/emulated/0/Obsidian/MiVault |
| Tipo de carpeta | Enviar y Recibir |
| Ignorar permisos | Activado |
| Patrones ignorados | mismos que en Mac |
Cómo añadir la carpeta:
- Abre Syncthing en Android
- Menú (3 barras) → “Folders” → ”+” (añadir carpeta)
- Selecciona la ruta
/storage/emulated/0/Obsidian/MiVault - Configura los parámetros como se indica arriba
- Espera a que aparezca el Mac como dispositivo y vincúlalo
1.3 Tiempos de sincronización
- Archivos pequeños (.md): 1–3 segundos
- Imágenes (WebP): 2–5 segundos
- Detección inicial de nueva carpeta: hasta 60s (por el intervalo de escaneo)
Si un archivo tarda más de 10 segundos, algo está mal. Verifica que ambos dispositivos estén en la misma red y que Syncthing esté corriendo en ambos.
1.4 Operaciones comunes
Pausar/reanudar la sincronización desde Android (útil si estás editando y no quieres que se sincronice hasta que termines):
- Abrir Syncthing en Android
- Tocar el dispositivo Mac en la lista
- Alternar el interruptor de pausa
Forzar sincronización inmediata (si no quieres esperar los 60s de escaneo):
- Syncthing → menú (3 puntos) → “Rescan all folders”
1.5 Solución de problemas comunes de Syncthing
| Problema | Causa probable | Solución |
|---|---|---|
| Los dispositivos no se ven | Firewall o redes diferentes | Verificar que ambos estén en la misma WiFi. En Mac, ir a Preferencias del Sistema → Firewall → Permitir Syncthing |
| Sincronización muy lenta | Archivos grandes o red congestionada | Las imágenes WebP suelen pesar <100KB, deberían ser rápidas |
Archivos .sync-conflict apareciendo | El mismo archivo se editó en ambos lados casi simultáneamente | Eliminar los conflictos (el .gitignore ya los ignora). Revisar qué versión mantener |
| La carpeta no se sincroniza | ID de dispositivo incorrecto | Verificar que el ID del Mac esté añadido en Android y viceversa |
2. Obsidian y Templater
Obsidian es el editor de notas. Se usa tanto en Android (para escribir) como en Mac (para revisar). Templater es un plugin que permite crear plantillas con lógica programática (JavaScript).
2.1 Instalación de Templater
- Abre Obsidian
- Configuración → Community plugins → “Browse”
- Busca “Templater” e instálalo
- Actívalo en “Community plugins”
2.2 Estructura del vault en Android
/storage/emulated/0/Obsidian/MiVault/
├── posts/ ← Notas del blog (se crean aquí mediante la plantilla)
└── templates/ ← Plantillas de Templater (debe coincidir con la configuración)
2.3 Configuración de Templater
En Mac y Android (ambos deben tener Templater instalado y configurado igual):
| Parámetro | Valor | Por qué |
|---|---|---|
| Template folder location | templates/ | Aquí se guarda la plantilla template-sin-preguntas.md |
| Trigger Templater on new file creation | Activado | Para que al crear un archivo desde plantilla se procese automáticamente |
2.4 La plantilla: template-sin-preguntas.md
Solo existe una plantilla en el sistema. Su función es:
- Recibir el título que el usuario escribe
- Generar un slug válido (sin acentos, sin espacios, sin caracteres especiales)
- Crear la fecha actual en formato ISO
- Mover el archivo a la carpeta
posts/(esto es crítico) - Rellenar el frontmatter con valores por defecto
Archivo: src/content/templates/template-sin-preguntas.md
<%*
const title = tp.file.title;
const slug = title
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^\w\s-]/g, "")
.trim()
.replace(/\s+/g, "-");
const today = tp.date.now("YYYY-MM-DDTHH:mm:ss") + ".000Z";
// ─── MOVER A CARPETA posts ─────────────────────────────────────────────────
await tp.file.move(`/posts/${slug}`);
_%>
---
title: '<% title %>'
slug: <% slug %>
description: ''
type: post
lang: es-ES
draft: true
authors:
- Admin
- Ernesto Uriszar
pubDate: <% today %>
updatedDate: <% today %>
categories:
- sin-categoría
tags:
- tag-1
featured: false
allowComments: true
share: true
image: '/img/<% slug %>.webp'
imageAlt: ''
canonicalUrl: ''
aliases:
- '<% title %>'
- <% slug %>
noIndex: false
hideTOC: false
showTOC: false
hideCoverImage: false
---
## H2
Párrafo
## Referencias
### Bibliografía Fundamental
- Autor, A. (Año). _Título_. Editorial.
- Autor, A. (Año). _Título_. Editorial.
- Autor, A. (Año). _Título_. Editorial.
### Artículos y Fuentes Web
- [Título](URL) — Descripción breve
- [Título](URL) — Descripción breve
- [Título](URL) — Descripción breve
### Videos Recomendados
- [Título](URL) — Duración · Por qué verlo
- [Título](URL) — Duración · Por qué verlo
- [Título](URL) — Duración · Por qué verlo
### Personas Clave
- **Nombre** — [Wikipedia](URL) — Rol breve
- **Nombre** — [Wikipedia](URL) — Rol breve
- **Nombre** — [Wikipedia](URL) — Rol breve
### Organizaciones Relevantes
- **Nombre** — Qué hace
- **Nombre** — Qué hace
---
> _Nota final (30-50 palabras)._
2.5 La línea crítica: tp.file.move() explicada en detalle
await tp.file.move(`/posts/${slug}`);
¿Qué hace exactamente? Cuando creas un archivo desde Templater, el archivo se guarda inicialmente donde tú estés en Obsidian. Esta línea mueve el archivo a la subcarpeta posts/ y lo renombra con el slug.
¿Por qué es necesaria? Porque Astro espera que los posts estén dentro de src/content/posts/. Si creas un archivo en la raíz de src/content/ (como pasó con Prueba.md), Astro lanza el error:
Collection entries must live in the “posts” collection subdirectory
Sin tp.file.move(), cada nota que crees terminará en la raíz de src/content/ y romperá el build.
¿Qué pasa si olvido incluir esta línea? El archivo se crea en la carpeta actual de Obsidian, no en posts/. Cuando Syncthing lo sincronice, terminará en src/content/ (raíz) y el build fallará.
2.6 Cómo crear una nota nueva paso a paso
- Abre Obsidian en Android
- Abre la paleta de comandos:
Ctrl+P(en Android) oCmd+P(en Mac) - Selecciona: “Templater: Create new note from template”
- Elige la plantilla:
template-sin-preguntas.md - Escribe el título: Por ejemplo, “Mi nuevo artículo”
- Templater genera automáticamente:
- El slug:
mi-nuevo-articulo - La fecha actual
- El frontmatter básico
- Mueve el archivo a
posts/mi-nuevo-articulo.md
- El slug:
- Rellena el frontmatter:
description:Un resumen breve del artículo (aparece en las cards y en SEO)categories:Cambia desin-categoríaa la categoría correcta (ej:biodanza,tecnologia,literatura)tags:Añade tags relevantes (ej:meditacion,conciencia)image:Cambia la ruta si tienes una imagen específicadraft:Cambia afalsecuando el artículo esté listo para publicar
- Rellena el contenido siguiendo la estructura de la plantilla
- Guarda el archivo (normalmente Obsidian guarda automáticamente)
3. Automatización (watchexec + launchd + auto-commit.sh)
3.1 ¿Cómo funciona la automatización?
Cada vez que Syncthing recibe un archivo nuevo o modificado en src/content/:
- watchexec detecta el cambio (usa
fsevents, la API nativa de macOS para monitorizar archivos) - watchexec ejecuta
auto-commit.sh - auto-commit.sh añade los cambios a git, hace commit y push
- GitHub Actions recibe el push y ejecuta el build
3.2 Contenido exacto de auto-commit.sh
#!/bin/bash
cd /Users/ernestouriszar/markeup || exit 1
# Añadir TODO lo que haya cambiado en src/content
git add src/content/
# Si hay cambios, commit y push
if ! git diff --cached --quiet; then
git commit -m "📱 Auto-sync from Android $(date '+%Y-%m-%d %H:%M:%S')"
git push origin main
fi
Explicación línea por línea:
cd /Users/ernestouriszar/markeup || exit 1: Cambia al directorio del proyecto. Si falla, sale con error.git add src/content/: Añade todo el contenido desrc/content/al stage de git (archivos nuevos, modificados y eliminados).git diff --cached --quiet: Comprueba si hay cambios sin mostrar el diff.--quiethace que solo devuelva el código de salida (0 = no hay cambios, 1 = hay cambios).git commit -m "...": Crea un commit con un mensaje descriptivo que incluye la fecha.git push origin main: Envía los cambios a GitHub.
¿Por qué se añade src/content/ entero y no archivos específicos? Porque queremos capturar cualquier cambio: archivos nuevos, modificados, eliminados, renombrados, imágenes, etc. Es más simple y seguro que añadir archivos uno por uno.
3.3 Gestión de watchexec con launchd (servicio persistente)
Inicialmente, watchexec se ejecutaba con nohup en segundo plano. Pero esto tenía un problema: al reiniciar el Mac, el proceso moría. Para solucionarlo, se creó un servicio launchd que:
- Arranca watchexec automáticamente al iniciar sesión
- Lo reinicia si falla
- Persiste aunque se reinicie el Mac
- Permite consultar su estado fácilmente
3.3.1 El archivo de configuración launchd
Para que launchd gestione watchexec, necesitas un archivo .plist en ~/Library/LaunchAgents/.
Crear el archivo:
cat > ~/Library/LaunchAgents/com.user.watchexec.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.watchexec</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/watchexec</string>
<string>-w</string>
<string>/Users/ernestouriszar/markeup/src/content</string>
<string>--</string>
<string>/Users/ernestouriszar/markeup/auto-commit.sh</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/ernestouriszar/markeup</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/watchexec.log</string>
<key>StandardErrorPath</key>
<string>/tmp/watchexec.err</string>
</dict>
</plist>
PLIST
Explicación de las opciones:
Label: Nombre único del servicio (debe coincidir con el nombre del archivo).ProgramArguments: El comando a ejecutar y sus argumentos. Aquí:watchexec -w src/content -- ./auto-commit.sh.RunAtLoad:true→ arranca automáticamente al iniciar sesión.KeepAlive:true→ si el proceso muere, lo reinicia automáticamente.StandardOutPath/StandardErrorPath: Archivos de log para stdout y stderr.
3.3.2 Cargar el servicio
# Cargar el servicio (arranca watchexec ahora y en cada inicio de sesión)
launchctl load ~/Library/LaunchAgents/com.user.watchexec.plist
Explicación: launchctl load le dice a launchd que registre el servicio. La primera vez arranca watchexec inmediatamente. En adelante, arrancará automáticamente al iniciar sesión.
3.3.3 Verificar que el servicio está corriendo
launchctl list | grep watchexec
Explicación: launchctl list muestra todos los servicios gestionados por launchd. Filtrando por grep watchexec vemos solo nuestro servicio.
Salida esperada:
PID Status Label
1234 0 com.user.watchexec
PID: El ID del proceso (si es 0, es que no está corriendo pero sí registrado).Status:0significa que el último intento fue exitoso.Label: El nombre del servicio.
3.3.4 Ver los logs si algo falla
# Ver errores
cat /tmp/watchexec.err
# Ver salida estándar (actividad normal)
cat /tmp/watchexec.log
Explicación: Si watchexec falla por alguna razón, el error se escribe en /tmp/watchexec.err. Si todo funciona bien, los mensajes de watchexec van a /tmp/watchexec.log.
3.3.5 Reiniciar el servicio
launchctl stop com.user.watchexec
launchctl start com.user.watchexec
Explicación: stop detiene temporalmente el proceso (launchd lo recordará y lo reiniciará si KeepAlive es true). start lo arranca inmediatamente. Útil si necesitas recargar la configuración o después de cambiar el .plist.
3.3.6 Detener el servicio (si alguna vez quieres pausar la automatización)
# Descargar el servicio (lo detiene y elimina del registro de launchd)
launchctl unload ~/Library/LaunchAgents/com.user.watchexec.plist
Explicación: unload elimina el servicio del registro de launchd. No se ejecutará más hasta que se vuelva a cargar con launchctl load. Útil si estás haciendo mantenimiento y no quieres que watchexec interfiera.
3.3.7 Reactivar el servicio
launchctl load ~/Library/LaunchAgents/com.user.watchexec.plist
Explicación: Vuelve a registrar el servicio en launchd. watchexec arranca inmediatamente.
3.4 Comparativa: nohup vs launchd
| Aspecto | nohup (antiguo) | launchd (nuevo) |
|---|---|---|
| Persiste tras reinicio | ❌ No | ✅ Sí |
| Se reinicia si falla | ❌ No | ✅ Sí |
| Fácil de verificar | `ps aux | grep watchexec` |
| Logs | En nohup.out (poco manejable) | En /tmp/watchexec.log y .err |
| Instalación | Un comando | Crear .plist + launchctl load |
| Portabilidad | Funciona en cualquier UNIX | Solo macOS |
3.5 Qué eventos detecta watchexec
TODO cambio en src/content/:
- Archivos
.md(creación, modificación, borrado) - Imágenes (
.webp,.png, etc.) - Plantillas
- Renombrados
- Directorios nuevos
¿Qué NO detecta? Cambios fuera de src/content/. Si modificas package.json o astro.config.mjs, no se hará auto-commit (y es correcto, esos cambios deben hacerse manualmente).
4. Estructura de contenido
4.1 Lo que funciona (la estructura actual)
src/content/
├── posts/ ← ✅ DONDE DEBEN ESTAR las notas
│ ├── Biodanza/ ← 12 posts + 4 subposts (cuatro-elementos)
│ ├── Literatura/ ← 2 posts (Fernando Pessoa)
│ ├── documentacion/ ← 5 archivos de documentación del sistema
│ ├── img/ ← Imágenes originales (no referenciadas directamente)
│ ├── sample-folder-based-post/ ← Post demo con imágenes adjuntas
│ ├── *.md ← 7 posts de tecnología
│ ├── Prueba.md ← Post de prueba (borrador, draft: true)
│ └── Caca.md ← ⚠️ Post de prueba (draft: false — se publicó)
4.2 Lo que NO funciona y por qué
- Archivos
.mdensrc/content/(raíz): Si creas un archivo fuera desrc/content/posts/, Zod lanza un error y el build falla.- Solución: La plantilla de Templater usa
tp.file.move()para crear directamente enposts/.
- Solución: La plantilla de Templater usa
- Posts con
draft: falsey contenido basura:Caca.mdse publicó accidentalmente porque tienedraft: false.- Solución: Marcar como
draft: trueo eliminar.
- Solución: Marcar como
4.3 Categorías existentes
Las categorías actuales son:
biodanza,documentacion,literatura,sin-categoría,tecnologia
Si tu post no encaja en ninguna, usa sin-categoría temporalmente y luego crea una nueva categoría editando el frontmatter.
5. Flujo completo paso a paso (resumen)
Paso 1: Android — Obsidian guarda nota (con Templater)
↓ (1-5s) — Syncthing transfiere
Paso 2: Mac — Syncthing recibe archivo en src/content/posts/
↓ (inmediato) — watchexec (gestionado por launchd) detecta
Paso 3: Mac — auto-commit.sh: git add → commit → push
↓ (~5-15s según internet)
Paso 4: GitHub — Actions se dispara: npm run build
↓ (~30-60s) — Astro genera HTML, Pagefind indexa
Paso 5: Cloudflare — Recibe build y despliega en CDN
↓ (~30-60s)
🚀 SITIO ACTUALIZADO (total: 2-5 minutos)
6. Errores encontrados y soluciones
Error 1: Zod — frontmatter inválido
Síntoma: Al hacer npm run build, aparece ZodError: Invalid frontmatter.
Causa: Un post tiene frontmatter incompleto o mal formado (falta algún campo requerido).
Solución: Revisar el archivo que menciona el error y asegurarse de que tiene todos los campos requeridos: title, slug, description, pubDate, categories (mínimo 1), draft (opcional, default false).
Error 2: “must live in content collection”
Síntoma: Collection entries must live in the "posts" collection subdirectory.
Causa: Un archivo .md se creó en src/content/ (raíz) en lugar de src/content/posts/.
Solución: Verificar que la plantilla de Templater tenga tp.file.move(). Mover manualmente el archivo a posts/.
Error 3: Template parsing — ## const today
Síntoma: Templater no procesaba la plantilla correctamente y dejaba el código <%* ... %> visible.
Causa: La línea const today = ... estaba precedida de ## (formato markdown incorrecto dentro del bloque Templater).
Solución: La línea correcta es const today = tp.date.now("YYYY-MM-DDTHH:mm:ss") + ".000Z"; (sin ##).
Error 4: Archivos .stversions/ en git
Síntoma: Archivos de versiones de Syncthing apareciendo en git status.
Causa: Syncthing guarda versiones antiguas de archivos modificados en .stversions/.
Solución: Se añadieron al .gitignore:
.stfolder/
.stignore
.stversions/
.sync-conflict*