eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
185 lines
10 KiB
Markdown
185 lines
10 KiB
Markdown
---
|
|
id: "0172"
|
|
title: "App web OSINT: grafo sigma.js + tablas por tipo + fichas con imágenes sobre el vault osint"
|
|
status: pendiente
|
|
type: app
|
|
domain:
|
|
- osint
|
|
- frontend
|
|
scope: app-scoped
|
|
priority: media
|
|
depends: []
|
|
blocks: []
|
|
related: ["0171"]
|
|
created: 2026-06-10
|
|
updated: 2026-06-10
|
|
tags: [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.
|