Aplicación web para gestionar enlaces (bookmarks) organizada por categorías, con panel de salud de servicios y listado de MCPs. El proyecto incluye un backend en Node.js/Express con base de datos SQLite y un frontend en React que puede ser servido por el propio backend.
- Gestión de enlaces por categorías (CRUD completo sobre SQLite).
- Barra lateral fija con las categorías y filtro por texto en toda la colección.
- Importación inicial automática de enlaces desde
json/bookmarks.jsonsi la base de datos no existe. - Exportación de enlaces a JSON vía API.
- Sistema de módulos del backend con autocarga y protección por contraseña para rutas de configuración.
- Módulo de salud de servidores (Health):
- Alta/edición/baja de servidores a monitorizar.
- Comprobación autónoma cada 60 segundos aunque la web esté cerrada.
- Incidencias confirmadas tras dos fallos consecutivos, con recordatorios cada 15 minutos y aviso de recuperación.
- Notificaciones en un canal de Microsoft Teams mediante Teams Workflows.
- Vista de salud con estado del monitor, última/próxima ronda, incidencias y detalle por componente.
- Listado y exploración de MCPs (Model Context Protocol) con API para leer readme y descargar ficheros.
- El backend compila y sirve el frontend automáticamente si no existe el build.
- Despliegue sencillo con PM2 mediante
ecosystem.config.js.
- Backend:
backend/server.js(Express, SQLite, carga de módulos, API REST para enlaces, health y MCPs). - Frontend: React (
frontend/src), con pestañas para Bookmarks, Server Health y MCPs. - Datos iniciales:
json/bookmarks.jsonyjson/servers.json. - Módulos backend:
backend/modules/*(p.ej. módulohealth). - MCPs:
mcp-list/con definiciones y documentación por cada MCP.
gestorenlaces/
├── backend/
│ ├── server.js
│ ├── modules/
│ │ └── health/
│ │ └── index.js
│ └── package.json
├── frontend/
│ ├── package.json
│ ├── public/
│ └── src/
│ ├── App.js
│ ├── components/
│ │ ├── CategoryNav.js
│ │ ├── BookmarkList.js
│ │ └── ServerHealth.js
│ └── api.js
├── json/
│ ├── bookmarks.json
│ └── servers.json
├── mcp-list/
│ ├── data.json
│ ├── README.md
│ ├── service/
│ │ └── readme.md
├── ecosystem.config.js
└── README.md
Crear un fichero .env en la raíz del proyecto o en backend/ (el servidor carga .env desde la raíz superior) con, al menos:
PORT(opcional): Puerto del backend. Por defecto5000.ADMIN_KEY(requerido para gestionar datos desde la web): clave estática que protege las rutas de escritura (alta/edición/borrado de enlaces y servidores). Si no está configurada, las escrituras quedan deshabilitadas ("fail closed") y devuelven503; las rutas de lectura siguen siendo públicas.HEALTH_CHECK_INTERVAL_SECONDS(opcional, default60): frecuencia de comprobación.HEALTH_FAILURE_THRESHOLD(opcional, default2): fallos consecutivos necesarios para abrir una incidencia.HEALTH_REMINDER_MINUTES(opcional, default15): frecuencia máxima de recordatorios mientras continúa el fallo.HEALTH_REQUEST_TIMEOUT_MS(opcional, default5000): timeout por endpoint de salud.TEAMS_HEALTH_WEBHOOK_URL: URL secreta generada por Teams Workflows para publicar avisos en el canal.
Ejemplo:
PORT=5000
ADMIN_KEY=tu-clave-secreta
HEALTH_CHECK_INTERVAL_SECONDS=60
HEALTH_FAILURE_THRESHOLD=2
HEALTH_REMINDER_MINUTES=15
HEALTH_REQUEST_TIMEOUT_MS=5000
TEAMS_HEALTH_WEBHOOK_URL=https://prod-xx.westeurope.logic.azure.com/...
La URL de Teams es un secreto: debe vivir solo en .env, que ya está excluido
de Git.
La gestión de datos desde la web (crear/editar/borrar enlaces y servidores) está protegida por una clave estática definida en la variable de entorno ADMIN_KEY del fichero .env.
- Las rutas de lectura (
GET) son siempre públicas. - Las rutas de escritura (
POST/PUT/DELETEde bookmarks y servidores) requieren la cabecerax-admin-keycon el valor deADMIN_KEY.- Sin clave configurada en el servidor: las escrituras devuelven
503(gestión deshabilitada). - Clave ausente o incorrecta en la petición:
401.
- Sin clave configurada en el servidor: las escrituras devuelven
- Endpoint de verificación:
POST /api/auth/verifyvalida la clave (vía cabecerax-admin-key) sin realizar ninguna acción; responde200con{ "ok": true }si es válida. - En el frontend, un botón con icono de candado en la cabecera permite "desbloquear" el modo edición introduciendo la clave; mientras esté desbloqueado, la clave se adjunta automáticamente a las peticiones (se guarda en
sessionStorage, se limpia al cerrar la pestaña). Al bloquear, los controles de edición desaparecen.
Backend
- Ir a la carpeta del backend:
cd backend - Instalar dependencias:
npm install - Ejecutar en modo desarrollo:
El backend quedará en
npm run devhttp://localhost:5000.
Frontend
- En otra terminal:
cd frontend - Instalar dependencias:
npm install - Ejecutar la aplicación React:
El frontend quedará en
npm starthttp://localhost:3000.
- Desde
backend/instale dependencias y construya el frontend:Nota:cd backend npm install npm run buildserver.jsdispara un build automático del frontend si no encuentrafrontend/build/index.html. - Inicie el servidor:
La app se servirá en
npm starthttp://localhost:5000.
Hay un archivo ecosystem.config.js en la raíz. Ejemplos:
pm2 start ecosystem.config.js
pm2 status
pm2 logs gestor
GET /api/bookmarks— Lista todos los enlaces.GET /api/bookmarks/category/:category— Lista los enlaces de una categoría.GET /api/categories— Lista de categorías únicas.POST /api/bookmarks— Crea un nuevo enlace. Body JSON:{ "category": "Dev", "short_description": "Google", "long_description": "Buscador", "link": "https://google.com", "icon": "" }PUT /api/bookmarks/:id— Actualiza campos del enlace indicado.DELETE /api/bookmarks/:id— Elimina el enlace.GET /api/export— Exporta los enlaces actuales ajson/bookmarks.json.
Modelo de datos de un bookmark:
category(string, requerido)short_description(string, requerido)long_description(string, opcional)link(string, requerido)icon(string, opcional)
Inicialización: la primera vez, si la tabla está vacía, se importan datos desde json/bookmarks.json.
El servidor carga automáticamente módulos desde backend/modules/* si existe la carpeta.
Rutas relacionadas:
GET /api/modules— Lista módulos cargados y sus rutas.
Rutas del módulo health:
GET /api/health/servers— Lista los servidores configurados.POST /api/health/servers— Crea un servidor a monitorizar. Body JSON:{ name, url, description }.PUT /api/health/servers/:id— Actualiza un servidor.DELETE /api/health/servers/:id— Elimina un servidor.GET /api/health/status— Devuelve la última instantánea y el estado del monitor (active,degradedostopped) sin ejecutar comprobaciones.GET /api/health/incidents?limit=20— Lista solo incidencias confirmadas; no contiene chequeos correctos.GET /api/health/check— Alias compatible y sin efectos secundarios de/api/health/status.POST /api/health/check— Fuerza una ronda inmediata; requierex-admin-key.
Inicialización: si la tabla servers está vacía, se importan datos desde json/servers.json.
Notas:
- El backend controla la vigilancia. Cerrar la pestaña o pulsar “Actualizar vista” no detiene ni ejecuta el monitor.
- Un HTTP
200también se considera fallo si faltastatus: "ok"en la raíz o si cualquier componente es distinto deok. - Un fallo aislado se descarta. El segundo fallo consecutivo abre una incidencia y publica en Teams.
- Mientras continúe, se recuerda cada 15 minutos; la primera comprobación correcta publica la recuperación.
- SQLite guarda una fila por incidencia, actualizada durante el fallo. No almacena el histórico de resultados correctos.
- Si Teams no responde, la entrega se reintenta sin detener las comprobaciones.
- En Microsoft Teams, abre Workflows y crea un flujo con el disparador “When a Teams webhook request is received”.
- Añade la acción que publica el mensaje o tarjeta en un canal y selecciona el equipo y canal de destino.
- Guarda el flujo y copia la URL HTTP generada.
- Pega la URL en
TEAMS_HEALTH_WEBHOOK_URLdentro de.env. - Reinicia el backend; con PM2:
pm2 restart gestor --update-env.
Usa Teams Workflows en lugar de crear un conector clásico “Incoming Webhook”. Para continuidad operativa, asigna al menos un copropietario al Workflow.
El backend expone un pequeño catálogo de MCPs definido en mcp-list/data.json y permite consultar documentación/archivos de cada MCP:
GET /api/mcps— Devuelve la lista de MCPs (leemcp-list/data.json).GET /api/mcps/:folder/readme— Devuelve elreadme.mddel MCP.GET /api/mcps/:folder/files— Lista de ficheros del MCP (excluyereadme.md).GET /api/mcps/:folder/file/:filename— Descarga un fichero concreto del MCP.
En el frontend existe una pestaña “MCPs” que consume estas APIs para explorar la documentación.
La UI usa react-router-dom, por lo que cada vista tiene una URL canónica enlazable:
/— Bookmarks: listado filtrable por texto con categorías en la barra lateral./health— Server Health: estado del monitor autónomo, incidencias y detalle por componente./mcps— MCPs: exploración de los MCPs disponibles y lectura de su documentación./mcps/:folder— Deep-link al detalle de un MCP concreto (p.ej./mcps/sqlserver); abre directamente su documentación. Si elfolderno existe, se muestra "MCP no encontrado".
json/bookmarks.json: datos de ejemplo de enlaces. Se importan automáticamente si la base está vacía.json/servers.json: lista inicial de servidores a monitorizar para el módulohealth.
En backend/package.json:
npm run dev— Ejecuta el servidor con recarga (nodemon).npm start— Ejecuta el servidor en modo producción.
En frontend/package.json:
npm start— Dev server de React.npm run build— Construye el frontend para producción.
- Node.js 18+ recomendado.
- Acceso saliente HTTPS desde el backend hacia la URL del Workflow de Teams.
Si.