Analytics propio sin servicios externos. Registra visitas desde cualquier proyecto y visualízalas en un dashboard local.
Tres partes independientes:
tracker/— módulo PHP portable. Se copia en el servidor de cada proyecto a trackear.viewer/— Vue SPA + proxy PHP. Corre en local con un solo comando; desplegable a servidor sin cambios de código.app/— wrapper Electron opcional. Empaqueta el viewer como app de escritorio.
[Proyecto A] [Proyecto B]
Vue SPA → POST /tracker/log.php Vue SPA → POST /tracker/log.php
↓ ↓
visits.log (JSONL) visits.log (JSONL)
↑ Bearer token ↑ Bearer token
GET /tracker/read.php GET /tracker/read.php
↑ ↑
┌─────────────────────────────────────────────┐
│ viewer/api/fetch.php (secrets server-side) │
│ viewer Vue SPA → GET /api/fetch.php │
└─────────────────────────────────────────────┘
LOCAL: npm run dev (Vite intercepta /api/*)
PROD: dist/ + api/fetch.php en servidor
¿Primera vez con migas? Estos son los pasos esenciales:
- Copia
tracker/al servidor del proyecto que quieres monitorizar (por FTP, SCP o el gestor de archivos de tu hosting). - Crea
tracker/tracker.config.jsoncon un token secreto, los dominios permitidos y la ruta donde se guardará el log. - Crea el archivo de log vacío con los permisos correctos para que PHP pueda escribir en él.
- Añade las reglas nginx para proteger los archivos sensibles y activar PHP en los endpoints del tracker.
- Añade el snippet de tracking a las páginas que quieras medir (2 líneas de JavaScript).
- Arranca el viewer:
cd viewer && npm install && npm run dev, configura tus proyectos enviewer.config.jsony abrehttp://localhost:5173.
Los pasos 1–5 se explican con detalle a continuación en la sección tracker/. El viewer se describe en viewer/.
-
Copia la carpeta
tracker/al servidor del proyecto (por ejemplo en/var/www/mi-proyecto/tracker/). -
Crea
tracker/tracker.config.jsona partir del ejemplo:
{
"read_secret": "TOKEN_ALEATORIO_LARGO",
"allowed_origins": ["https://mi-proyecto.com", "http://localhost:5173"],
"log_path": "/var/www/mi-proyecto/visits.log"
}read_secret— token para protegerread.php. Genera uno conopenssl rand -hex 32.allowed_origins— dominios autorizados a enviar visitas alog.php. Incluyehttp://localhost:5173durante el desarrollo del viewer.log_path— ruta absoluta donde se escribirávisits.log. Puede estar fuera del webroot.
- Crea el archivo de log vacío con los permisos correctos:
sudo touch /var/www/mi-proyecto/visits.log
sudo chown www-data:tu-usuario /var/www/mi-proyecto/visits.log
sudo chmod 664 /var/www/mi-proyecto/visits.logEl proceso PHP (www-data en la mayoría de servidores) necesita permiso de escritura. Si log_path apunta fuera del webroot, crea también el directorio con los mismos permisos.
- Añade estas reglas a nginx para proteger los archivos sensibles:
location ~* \.log$ { deny all; }
location = /tracker/tracker.config.json { deny all; }
location ~ ^/tracker/(log|read|flush)\.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}- Añade el snippet de tracking al proyecto (Vue, vanilla JS, o donde sea):
navigator.sendBeacon('/tracker/log.php', JSON.stringify({
path: location.pathname,
referrer: document.referrer,
screen: `${screen.width}x${screen.height}`,
lang: navigator.language,
}))O como llamada fetch si prefieres controlar el timing:
fetch('/tracker/log.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
path: location.pathname,
referrer: document.referrer,
screen: `${screen.width}x${screen.height}`,
lang: navigator.language,
}),
})# Registrar una visita
curl -X POST https://mi-proyecto.com/tracker/log.php \
-H "Content-Type: application/json" \
-H "Origin: https://mi-proyecto.com" \
-d '{"path":"/","referrer":"","screen":"1920x1080","lang":"es"}'
# → HTTP 204
# Leer el log
curl https://mi-proyecto.com/tracker/read.php \
-H "Authorization: Bearer TOKEN_ALEATORIO_LARGO"
# → JSONL con las visitas| Endpoint | Método | Descripción |
|---|---|---|
log.php |
POST |
Recibe { path, referrer, screen, lang }, añade ts y ua, escribe en visits.log |
read.php |
GET |
Devuelve visits.log en JSONL. Requiere Authorization: Bearer <read_secret> |
flush.php |
POST |
Vacía visits.log. Requiere Authorization: Bearer <read_secret> |
Permite truncar el log de visitas desde el viewer sin acceso directo al servidor. El viewer descarga primero los datos como backup local antes de llamar a este endpoint.
# Vaciar el log de un proyecto
curl -X POST https://mi-proyecto.com/tracker/flush.php \
-H "Authorization: Bearer TOKEN_ALEATORIO_LARGO"
# → HTTP 204Reutiliza el mismo read_secret que read.php. No hay token separado.
Dashboard Vue que lee los logs de todos los proyectos trackeados.
- Node.js 18+
- Bedrock — sistema de diseño CSS/Vue
cd viewer
npm installCrea viewer/viewer.config.json a partir del ejemplo:
cp viewer.config.example.json viewer.config.jsonEdita el archivo con tus proyectos:
{
"viewer_token": "",
"projects": [
{
"name": "mi-proyecto",
"read_url": "https://mi-proyecto.com/tracker/read.php",
"read_secret": "TOKEN_ALEATORIO_LARGO"
}
]
}viewer_token— vacío en local (sin auth). Rellena con un token largo si despliegas el viewer a un servidor.projects— lista de proyectos a monitorizar. Cada uno necesita la URL deread.phpy el token correspondiente.hidden: true— oculta el proyecto del selector. Útil para proyectos de prueba. Sigue siendo accesible directamente vía?project=nombre.
cd viewer
npm run dev
# → http://localhost:5173El servidor de Vite intercepta las peticiones a /api/fetch.php en Node.js, leyendo viewer.config.json y llamando a cada read_url. No hace falta PHP en local.
cd viewer
npm run build
# → dist/Despliega dist/ y api/ juntos en el mismo servidor. El PHP proxy (api/fetch.php) mantiene los secrets server-side y valida viewer_token si está configurado.
/var/www/migas/
├── index.html ← dist/
├── assets/ ← dist/assets/
├── api/
│ └── fetch.php
└── viewer.config.json
Cuando el viewer esté en un servidor, activa el viewer_token en viewer.config.json:
{
"viewer_token": "OTRO_TOKEN_LARGO",
...
}El SPA deberá enviar el header X-Viewer-Token: OTRO_TOKEN_LARGO en cada petición. Añade una pantalla de login o protege el acceso con la autenticación que prefieras.
app/ es un wrapper de Electron que empaqueta el viewer como una aplicación de escritorio. No modifica ningún archivo de viewer/ — consume su output compilado.
- Levanta un servidor HTTP local en un puerto aleatorio
- Sirve
viewer/dist/como archivos estáticos - Registra los mismos handlers de
/api/fetch.phpy/api/flush.phpque en el dev server de Vite - Abre una
BrowserWindowapuntando ahttp://127.0.0.1:{puerto} - Lee
viewer/viewer.config.jsondesde su ubicación original — sin duplicar el archivo
- Node.js 18+
- Electron (instalado en
app/node_modules/)
cd app
npm installCompila el viewer si no está compilado y lanza la app directamente con Electron:
cd viewer && npm run build
cd ../app && npm startLa ventana abre sin terminal visible. viewer/viewer.config.json se lee en caliente — cualquier cambio en el config se aplica en la siguiente acción sin reiniciar.
# 1. Compilar el viewer
cd viewer && npm run build
# 2. Empaquetar la app
cd ../app && npm run build
# → app/dist/win-unpacked/migas.exeEl exe incluye viewer/dist/ bundleado en resources/viewer-dist/. El viewer.config.json no se bundlea — se lee desde su ruta original en el repositorio.
Estructura del output:
app/dist/win-unpacked/
├── migas.exe
└── resources/
└── viewer-dist/ ← copia de viewer/dist/ en el momento del build
Si añades proyectos a
viewer.config.jsonno hace falta recompilar. Si cambia el código del viewer hay que repetir los dos pasos.
migas/
├── tracker/
│ ├── log.php # Recibe visitas (POST)
│ ├── read.php # Devuelve logs (GET, protegido)
│ ├── flush.php # Vacía el log (POST, protegido)
│ └── tracker.config.example.json # Template de configuración
│
├── viewer/
│ ├── src/
│ │ ├── main.js
│ │ ├── App.vue
│ │ ├── router.js # Vue Router (hash history)
│ │ ├── views/
│ │ │ ├── OverviewView.vue # Vista principal — resumen de todos los proyectos
│ │ │ └── ProjectView.vue # Vista de detalle — visitas + stats de un proyecto
│ │ ├── composables/
│ │ │ └── useProjectData.js # Estado compartido (proyectos, visitas, caché)
│ │ └── components/
│ │ ├── app-shell/ # Cabecera y layout principal
│ │ ├── c-project-card/ # Tarjeta resumen por proyecto
│ │ ├── c-project-stats/ # Contadores y rankings (paths, referrers, etc.)
│ │ ├── c-visit-table/ # Tabla paginada de visitas
│ │ ├── c-filter-bar/ # Filtros por ruta y fecha
│ │ └── c-confirm-modal/ # Modal de confirmación (usado en flush)
│ ├── api/
│ │ ├── fetch.php # Proxy PHP — leer logs (solo en producción)
│ │ └── flush.php # Proxy PHP — vaciar log (solo en producción)
│ ├── viewer.config.example.json
│ ├── package.json
│ └── vite.config.js # Middlewares dev para /api/fetch.php y /api/flush.php
│
├── app/
│ ├── electron/
│ │ └── main.js # Proceso principal: servidor HTTP + BrowserWindow
│ └── package.json # electron + electron-builder
│
├── dev/
│ └── visits.log # Log de prueba con datos ficticios
│
└── .gitignore # Excluye *.config.json, node_modules/, dist/
| Activo | Protección |
|---|---|
visits.log |
Nunca servido directamente — nginx lo bloquea |
tracker/read.php |
Authorization: Bearer <read_secret> |
tracker.config.json |
Gitignored + bloqueado por nginx |
viewer/api/fetch.php |
X-Viewer-Token — desactivado en local, activable en producción |
viewer.config.json |
Gitignored, nunca llega al bundle JS |
Los read_secret de cada proyecto viven únicamente en viewer.config.json (server-side). El SPA nunca los ve.
El script deploy.ps1 en la raíz del proyecto gestiona el versionado y la publicación en GitHub. No requiere Lanzadera ni acceso a ningún servidor.
.\deploy.ps1 release # propone versión, crea tag y publica en GitHub
.\deploy.ps1 github # push + tags sin crear versión nuevaEl flujo habitual es:
- Completar tareas y actualizar el CHANGELOG con la nueva versión
- Hacer commit de los cambios
- Ejecutar
.\deploy.ps1 release
El script lee la última versión del CHANGELOG para proponerla automáticamente. Si ya tiene tag en git, propone el siguiente patch con opción de elegir minor o major.
migas no recopila ni almacena la dirección IP de los visitantes. Los únicos datos que escribe log.php en visits.log son:
| Campo | Origen | Ejemplo |
|---|---|---|
path |
Enviado por el SPA | /mix/001 |
referrer |
Enviado por el SPA | https://google.es |
screen |
Enviado por el SPA | 1920x1080 |
lang |
Enviado por el SPA | es |
ts |
Generado en el servidor | 2026-05-28T10:12:34+02:00 |
ua |
$_SERVER['HTTP_USER_AGENT'] |
cadena del navegador |
El User-Agent puede considerarse un dato personal en algunas jurisdicciones — trátalo con las mismas precauciones que cualquier otro dato de analítica.
Lanzadera es una herramienta de deploy para proyectos Vue que puede automatizar la instalación del tracker. No es necesaria para usar migas — la instalación manual descrita arriba es el path principal.
Si ya usas Lanzadera en el proyecto destino, añade la sección tracker a tu project.config.json:
{
"tracker": {
"enabled": true,
"read_secret": "TOKEN_ALEATORIO_LARGO",
"log_path": "/var/log/migas/mi-proyecto/visits.log"
}
}Y en machine.config.json, indica dónde está el código fuente del tracker en tu máquina:
{
"trackerSourcePath": "C:/Users/tu-usuario/WORK/migas/tracker"
}Con esta configuración, .\deploy.ps1 tracker automatiza lo que los pasos 1–4 de la instalación manual describen:
- Copia
log.php,read.phpyflush.phpal servidor - Genera
tracker.config.jsoncon los valores del project config - Crea el archivo de log con
chown www-dataychmod 664
Las reglas nginx se añaden al ejecutar .\deploy.ps1 nginx o .\deploy.ps1 setup.