Files
fn_registry/dev/issues/0172-osint-web-graph-explorer.md
T
egutierrez eb8dbf66a1 feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 00:16:46 +02:00

10 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0172 App web OSINT: grafo sigma.js + tablas por tipo + fichas con imágenes sobre el vault osint pendiente app
osint
frontend
app-scoped media
0171
2026-06-10 2026-06-10
osint
web
sigma
graph
mantine
obsidian
vault
dashboard

0172 — App web OSINT: grafo sigma.js + tablas por tipo + fichas con imágenes

APP Metadata

Campo Valor
ID 0172
Estado pendiente (solo plan — se construye cuando el vault tenga más datos)
Prioridad media
Tipo app — nueva app web en projects/osint/apps/osint_web
Project osint (projects/osint/)

Contexto

El project osint guarda sus investigaciones en el vault de Obsidian /home/enmanuel/Obsidian/osint (sub-repo dataforge/osint). Hoy ese vault tiene:

  • ~82 nodos repartidos en carpetas tipadas: personas/ (45), organizaciones/ (25), lugares/ (10), dominios/ (1), casos/ (1).
  • Datos tabulares en el frontmatter YAML de cada ficha: tipo, nombre, sexo, fecha_nacimiento, dni, direccion, pais, aliases, tags, etc.
  • Aristas implícitas: los wikilinks [[...]] en las secciones Relaciones, Lugares y Documentos conectan unas fichas con otras (y con sus attachments).
  • ~240 attachments: fotos, DNIs, certificados y PDFs en attachments/<tipo>/<slug>/, embebidos en las notas con ![[...]].

Obsidian es bueno para escribir la investigación, pero malo para explorarla de un vistazo: no da un grafo navegable de todos los objetivos, ni una tabla filtrable, ni una ficha-resumen con la galería de imágenes de cada persona. Metabase/Grafana no encajan: leen BD SQL (no .md), y no muestran ni grafo de nodos ni imágenes inline.

Decisión del usuario (10/06/2026): construir una app web propia que lea el vault y ofrezca tres vistas — grafo explorable con sigma.js, tablas filtradas por tipo, y fichas con imágenes. Este issue es solo el plan: la recopilación de datos en Obsidian continúa primero; la app se implementa cuando haya suficiente material que justifique la inversión.

Objetivo

Una app web local que, leyendo directamente los .md del vault osint (sin BD intermedia obligatoria en v1), permita:

  1. Explorar el grafo de nodos (personas, organizaciones, lugares, dominios, casos) y sus conexiones por wikilinks, con sigma.js: zoom, pan, click en nodo → ficha, colores por tipo, filtro de tipos visibles, búsqueda de nodo.
  2. Ver tablas filtradas por tipo: una tabla por categoría (personas, organizaciones, ...) con las columnas del frontmatter, ordenable y filtrable (por dni, lugar, fecha, tag).
  3. Abrir la ficha de cualquier nodo: frontmatter renderizado + cuerpo Markdown + galería de sus attachments (fotos, DNIs, PDFs) servidos por el backend.

Arquitectura propuesta

projects/osint/apps/osint_web/        (sub-repo Gitea dataforge/osint_web)
  app.md                              frontmatter de registro (framework: react-vite-mantine)
  server/                             backend Python (lee el vault, sirve JSON + attachments)
    main.py                           FastAPI o stdlib http
  frontend/                           React + Vite + Mantine + sigma.js
    src/
      views/GraphView.tsx             sigma.js + graphology
      views/TablesView.tsx            Mantine DataTable filtrable por tipo
      views/NodeCard.tsx              ficha + galería de attachments

Backend (Python — máximo reuso del grupo obsidian)

Python porque el grupo de capacidad obsidian (11 funciones, dominio obsidian) ya cubre casi todo el parseo del vault. Registry-first: el backend orquesta estas funciones, no reimplementa el parseo.

Funciones del registry a reutilizar:

Función Uso en la app
list_obsidian_notes_py_obsidian enumerar nodos por carpeta/tipo
read_obsidian_note_py_obsidian leer ficha: {frontmatter, body, wikilinks, tags}
parse_obsidian_frontmatter_py_obsidian datos tabulares de cada nodo
extract_obsidian_wikilinks_py_obsidian aristas del grafo
extract_obsidian_embeds_py_obsidian attachments embebidos en cada nota
resolve_obsidian_embed_py_obsidian resolver ![[foto.jpg]] → path real en disco para servir la imagen
slugify_obsidian_name_py_obsidian normalizar nombre de wikilink → id de nodo
search_obsidian_notes_py_obsidian búsqueda global en el grafo

Funciones nuevas a delegar a fn-constructor (no escribir inline en la app):

  • build_obsidian_graph_py_obsidian (impure) — dado vault_dir, devuelve {"nodes": [{id, tipo, label, frontmatter}], "edges": [{source, target, kind}]}. Resuelve cada wikilink a un nodo existente (vía slug / nombre de archivo); los wikilinks que no resuelven a un .md del vault se marcan como aristas "dangling" o se descartan según flag. Tag de grupo: obsidian. Es la pieza que el grupo declara como frontera no cubierta ("No indexa el grafo agregado") — esta función la cierra.

Endpoints HTTP (JSON salvo el de attachments):

Método Ruta Devuelve
GET /api/graph grafo completo {nodes, edges} para sigma.js
GET /api/nodes?tipo=persona filas de la tabla de ese tipo (frontmatter aplanado)
GET /api/node/{slug} ficha: frontmatter + body (HTML/markdown) + lista de attachments
GET /api/attachment?path=... sirve el binario del attachment (image/pdf), con allowlist al vault
GET /api/search?q=... nodos que matchean

Seguridad: el backend solo sirve archivos dentro del vault osint (path traversal bloqueado). El vault contiene datos personales sensibles (DNIs) → la app escucha solo en 127.0.0.1, sin exponer a red. No es un service desplegable a VPS.

Frontend (React + Vite + Mantine + sigma.js)

  • Sistema del registry: React + Vite + Mantine v9 + @fn_library (grupo mantine, 63 funciones). Componentes propios de @fn_library antes que HTML nativo (regla frontend_theming.md).
  • Grafo: sigma.js + graphology. Color por tipo, tamaño por grado, layout force-directed (graphology-layout-forceatlas2). Click en nodo → abre NodeCard. Panel lateral con toggles de tipos visibles y caja de búsqueda.
  • Tablas: una pestaña por tipo, Mantine Table/DataTable con columnas del frontmatter, orden y filtro por columna (dni, lugar, fecha_nacimiento, tags).
  • Fichas: NodeCard con frontmatter en formato clave-valor (fechas en formato europeo DD/MM/AAAA — memoria formato-fecha-europeo), cuerpo Markdown, y galería de attachments (imágenes con lightbox; PDFs como enlace/embed).

sigma.js y graphology son dependencias nuevas del frontend (no en @fn_library). KISS: añadir solo esas dos; el resto (tabla, layout, modales) sale de Mantine/@fn_library.

Decisiones abiertas

  1. ¿BD intermedia o lectura directa del vault? v1 lee el vault en cada arranque (cachea el grafo en memoria). Si el vault crece mucho o se quiere histórico/diff, evaluar un operations.db con entities/relations (encaja con el bucle reactivo). Recomendado: empezar sin BD (KISS), añadirla solo si el rendimiento o un caso de uso lo exige.
  2. Backend FastAPI vs stdlib http: FastAPI da validación y OpenAPI gratis; stdlib evita una dependencia. Como el backend es fino (orquesta funciones del registry), decidir al construir.
  3. Live-reload del vault: ¿re-escanear bajo demanda (botón "refrescar") o watcher de filesystem? v1: botón refrescar (simple). Watcher si molesta.
  4. Aristas dangling: wikilinks a notas que aún no existen — ¿mostrarlos como nodos fantasma (útil para ver "objetivos pendientes de fichar") o esconderlos? Propuesta: nodo fantasma con estilo atenuado, toggle para ocultar.

Definition of Done

Escenario Tipo Comando / evidencia Resultado esperado
Golden: grafo carga el vault e2e GET /api/graph con el vault osint real nodes ≥ nº de .md, edges con los wikilinks resueltos; sigma.js los pinta
Golden: ficha con imágenes e2e GET /api/node/<persona con fotos> + abrir NodeCard frontmatter + cuerpo + galería con las imágenes de attachments/personas/<slug>/
Edge: tabla filtrada por tipo e2e GET /api/nodes?tipo=organizacion solo nodos de ese tipo, columnas del frontmatter
Edge: wikilink dangling unit nota con [[Persona-Inexistente]] arista marcada dangling / nodo fantasma, sin crash
Edge: nombre con mayúsculas/acentos unit wikilink [[María del Mar]] → slug resuelve a maria-del-mar-...md vía slugify_obsidian_name
Error: path traversal en attachment e2e GET /api/attachment?path=../../etc/passwd 403/404, jamás sirve fuera del vault
Error: vault inexistente e2e arrancar con --vault /no/existe error claro al arrancar, no 500 silencioso
Cobertura audit uses_functions del app.md declara todas las funciones del grupo obsidian consumidas

Vida útil (cuando se construya): usar la app de verdad sobre el vault osint durante ≥7 días en investigaciones reales; medir que el grafo sigue cargando sin romperse al crecer el vault.

Notas

Estado actual: solo plan. No construir todavía — la recopilación de datos en Obsidian continúa; cuando el vault tenga masa crítica de objetivos/relaciones, se arranca con /new-cpp-app no aplica (es web): se hace git init del sub-repo dataforge/osint_web dentro de projects/osint/apps/osint_web/ antes de limpiar cualquier worktree (regla apps_subrepo.md), scaffolding de frontend con el stack Mantine del registry, y backend Python orquestando el grupo obsidian.

Onboarding (para cuando exista): arrancar backend python server/main.py --vault /home/enmanuel/Obsidian/osint --port 8470 y pnpm dev en frontend/; abrir http://127.0.0.1:5173. Pestañas: Grafo / Tablas / (ficha al click). Solo localhost por los datos sensibles del vault.

Relación con #0171 (manifest de sub-repos): cuando esta app exista será un hijo del project osint y debe entrar en su subrepos.yaml para re-clonarse en otros PCs.