Añade alta, edición y borrado de contactos (personas y organizaciones) a la
app osint_web. La fuente de verdad es la ficha .md del vault Obsidian
(CONVENTIONS.md §3b/§6); Xandikos es el retransmisor al móvil.
Backend (server/main.py):
- POST /api/contact: genera slug, escribe la ficha .md con el frontmatter
canónico + PUT del vCard a Xandikos. 409 si el slug ya existe.
- PUT /api/contact/{slug}: merge del frontmatter (preserva campos heredados)
+ re-PUT del vCard. 404 si no existe.
- DELETE /api/contact/{slug}: borra la ficha .md + DELETE del vCard. 404 si
no existe.
Cada escritura invalida la caché DAV para que el cambio se vea ya en la app.
Registry-first: orquesta create/update/delete_obsidian_note del grupo obsidian
y carddav_put_vcard/dav_delete_resource del grupo dav (sin reimplementar
parseo ni HTTP). Mapea los campos OSINT a propiedades X-OSINT-* del vCard.
Frontend (ContactsView.tsx + api.ts + format.ts):
- Botón "Nuevo contacto" → modal con formulario Mantine (TextInput,
TagsInput aliases, Select contexto, Textarea notas).
- Detalle: botones "Editar" (formulario precargado) y "Borrar" (con
confirmación). Tras guardar refresca la lista.
- Helper slugify (replica slugify_obsidian_name) para resolver la ficha.
Tests: 6 nuevos casos (ciclo crear→editar→borrar con .md real + reflejo vCard
mockeado, organización, 404s, tipo inválido, preserva campos heredados). Suite
27 passed. Ciclo e2e real verificado contra Xandikos + vault (vCard creado,
editado y borrado; slug zz-test-crud limpiado). pnpm build verde (React 19 +
Mantine v9).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frontend de osint_web
Frontend web local del explorador OSINT. Lee el backend FastAPI (../server/main.py,
escucha solo en 127.0.0.1:8470) y ofrece cinco vistas de lectura sobre el vault
de Obsidian osint + la agenda/calendario del servidor Xandikos.
Stack
- React 19 + Vite 6 + TypeScript + Mantine v9. Mantine v9 exige React 19 (con
React 18 compila pero no monta — error "s is not a function"); por eso el
package.jsonpina React 19. Iconos de@tabler/icons-react. Theming concreateTheme()(src/theme.ts), sin Tailwind ni CSS variables custom (reglafrontend_theming.md). - Grafo:
sigma(v3) +graphology+graphology-layout-forceatlas2(las únicas deps fuera de Mantine; KISS). Layout force-directed en un web worker (no bloquea la UI con 1199 nodos), pausable. - Markdown de las fichas con
react-markdown. Calendario con@mantine/dates(que usadayjs). - Vite proxya
/api→http://127.0.0.1:8470en dev (sobrescribible conVITE_API_BASE).
Vistas
| Vista | Archivo | Endpoint(s) | Qué muestra |
|---|---|---|---|
| Grafo | src/views/GraphView.tsx |
GET /api/graph, GET /api/search |
sigma.js force-directed; color por tipo, tamaño por grado; panel lateral con toggles de tipos + dangling + buscador (centra el nodo). Click en nodo → ficha. Layout pausable + reset de cámara. |
| Tablas | src/views/TablesView.tsx |
GET /api/graph, GET /api/nodes?tipo= |
una pestaña por tipo real; Table Mantine con columnas deducidas del frontmatter, ordenable y filtrable. Click en fila → ficha. |
| Ficha | src/views/NodeCard.tsx |
GET /api/node/<slug>, GET /api/attachment |
modal: frontmatter clave-valor (fechas europeas DD/MM/AAAA), cuerpo Markdown, galería de imágenes con lightbox, documentos/PDFs como enlace, wikilinks navegables. |
| Contactos | src/views/ContactsView.tsx |
GET /api/contacts |
agenda: lista + buscador (nombre/alias/tel/email); detalle con teléfonos, correos, bloque osint (dni/país/sexo…) y nota. |
| Calendario | src/views/CalendarView.tsx |
GET /api/calendar |
mini-calendario @mantine/dates con punto en días con eventos + lista de eventos del mes/día agrupados por fecha (hora local, lugar, descripción). |
Botón global Refrescar (header) → POST /api/refresh + recarga de la vista activa.
Arrancar (dev)
Necesitas backend + frontend a la vez:
# Terminal 1 — backend (escucha solo en 127.0.0.1)
cd projects/osint/apps/osint_web
.venv/bin/python server/main.py --vault /home/enmanuel/Obsidian/osint --port 8470
# Terminal 2 — frontend
cd projects/osint/apps/osint_web/frontend
pnpm install # primera vez
pnpm dev # http://127.0.0.1:5173
Abrir http://127.0.0.1:5173. El proxy de Vite reenvía /api al backend, así
que no hay que tocar CORS. Solo localhost (datos sensibles del vault: DNIs, fotos).
Build
cd projects/osint/apps/osint_web/frontend
pnpm install
pnpm build # tsc -b && vite build → dist/
Gotcha pnpm 10/11 (esbuild)
pnpm bloquea por seguridad los scripts de build de dependencias. esbuild (el
bundler nativo de Vite) necesita su postinstall. El pnpm-workspace.yaml lo
permite con allowBuilds: { esbuild: true }. Si pnpm build falla con
"esbuild ... was not found", ejecuta pnpm rebuild esbuild.
Notas
- Grafo sin WebGL: si el navegador no expone WebGL (headless sin GPU), la vista Grafo muestra un aviso en vez de crashear; el resto de la app sigue funcionando.
- Contactos/Calendario dependen del servidor Xandikos: si no responde, esas dos vistas muestran un aviso naranja y el grafo/tablas siguen operativos (offline).
- Las fechas se presentan en europeo (
src/format.ts): ISO de Obsidian2026-06-07→07/06/2026; iCal20220829T133000Z→ hora local15:30.