Arquitectura visual del grafo de conocimiento que conecta cartera, contactos, documentos, conversaciones y análisis financieros del fondo. Pensado para entender de un vistazo cómo fluye la data y qué se puede hacer con ella.
Un Knowledge Graph es una base de datos donde la información se modela como nodos (cosas: empresas, personas, documentos, reuniones) y aristas (relaciones entre cosas: "esta persona trabaja en", "este deck menciona", "esta reunión fue con"). Lo que esto desbloquea para un fondo PE/VC es conexión entre todas las piezas de información que hoy viven aisladas en distintos sistemas.
30 min → 5 segundos. Lo que antes requería abrir Drive + Gmail + Notion + Excel + Granola para armar un dossier de Citibox, ahora es una query.
✓ Hoy
"3 de mis founders vienen de Glovo" → detectado automático cruzando decks de cartera con LLM extraction. Insight que SQL plano no descubre.
✓ Hoy
El agente recuerda lo que dijiste sobre Citibox la semana pasada. Cada mensaje se extrae a Mentions estructuradas con quote + timestamp.
⚠ Parcial — backfill 2858 msgs pendiente
1 persona = 1 nodo, aunque esté en el CRM de varios miembros del fondo.
El linker consolida por email canónico y conserva el contexto per-user en la arista
IN_CRM_OF (cargo, fuente, fecha).
✓ Modelo B-completo implementado 2026-05-15
Cada recomendación rastreable hasta su fuente. Cuando el agente sugiere algo, sabés exactamente qué quarterlies, meetings y emails leyó para llegar ahí.
⚠ F7 Recommendation pendiente
De analyst que busca a sistema que alerta. Patterns nightly: "3 participadas con cambio CEO + drop headcount" → notif por WA antes que vos lo notes.
⚠ F6 pendiente — foundation lista
13 tipos de nodos + 7 tipos de aristas representan la realidad operativa del fondo. Cada
pregunta del negocio se traduce a un path en el grafo: (Directa)-[:HOLDS]->(Company)
<-[:ABOUT]-(Document)-[:HAS_MENTION]->(:Mention)-[:OF]->(Competitor).
Schema canónico en docs/kg/kg-schema.md.
Cada texto que entra al sistema (deck, email, meeting summary, mensaje WA) pasa por un
LLM Haiku que detecta entidades mencionadas y las atan al nodo canónico via
:Mention. No depende de búsqueda exacta — el LLM entiende contexto
y abreviaciones ("Glovo", "Glovo Apps", "@glovo").
~10s en background, ~$0.0001 por texto.
Cuando aparece una mención nueva, un linker decide a qué nodo canónico atarla con 4 estrategias en cascada: identifier exacto (email/linkedin/tax_id) → name exact → fuzzy match → LLM disambiguate si hay ambigüedad real.
Evita duplicación de identidades. Detalle en kg_entity_linker.py.
La magia se ve cuando una pregunta necesita saltar entre cosas distintas — muchas veces. Acá la comparación visual:
¿Quién es el CEO de Citibox?
¿Qué founders de mi cartera vienen de Glovo?
| Tipo de pregunta | SQL clásico | RAG vectorial | Knowledge Graph |
|---|---|---|---|
| "¿CEO de X?" | ✓ | ⚠ aprox | ✓ |
| "¿Quién menciona a X?" | ⚠ lento | ✓ | ✓ |
| "¿Conexión entre X e Y?" | ✗ inviable | ✗ se pierde | ✓ |
| "¿Patrones cross-empresa?" (cartera ex-Glovo, etc.) | ✗ | ✗ | ✓ único |
La diferencia clave: SQL piensa en tablas (bueno para "qué tengo"). Vectores piensan en similitud (bueno para "qué se parece"). Los grafos piensan en conexiones (único para "cómo se relaciona").
| Tipo | Qué representa | De dónde viene | Nodos |
|---|---|---|---|
| :Document | Pitch decks, balances, contratos, NDAs, meeting notes Granola | Drive watcher + email pipeline + sync ETL | 318 |
| :Mention | Cada vez que el LLM detecta una entidad en un texto, con quote | Extracción LLM (Haiku) post-OCR | 248 |
| :Company | Empresas (cartera + CRM + externas mencionadas) | Postgres companies + extracción LLM | 212 |
| :Person | Contactos del CRM y personas extraídas, 1 nodo canónico por email (ya no se duplica por user) | Postgres kb_personas + LLM, agrupado | 111 |
| :DailyMemory | Resúmenes diarios de actividad por usuario | omzg_memory_svc nightly | 76 |
| :Directa | Cada inversión directa del fondo (1 ticker = 1 directa) | Postgres directas | 67 |
| Emails procesados por Kabuto | email_pipeline → kb_interacciones | 51 | |
| :Meeting | Reuniones registradas (Granola) | granola_ingest → kb_interacciones | 48 |
| :QuarterlyUpdate | Updates financieros trimestrales de cartera | Postgres quarterly_updates | 40 |
| :FinancialAnalysis | Análisis financieros estructurados (extracted_data, key_metrics) | finanzas_service tras OCR | 21 |
| :Conversation | Hilos de WhatsApp/Telegram | omzg_orchestrator | 18 |
| :Fondo | Fondos de inversión que el grupo gestiona | Postgres fondos | 12 |
| :User | Miembros del fondo (1 por Invitation con phone) | Postgres invitations · phone normalizado | 5 (14 prod) |
| :Organization | Orgs operadoras del fondo (Mazos, Umbratech, Greelow, Inspirit) | Auto-inferida por email domain de cada User | 3 (5 prod) |
| :Fund | Raíz explícita del fondo gestor (Mazos) | Hardcoded — único fondo gestor por ahora | 1 |
| Arista | Origen → Destino | Significado | Cantidad |
|---|---|---|---|
ABOUT | Document/Email/Meeting/FinAnalysis → Company | "Este documento es sobre esta empresa" | 400 |
HAS_MENTION | Document/Email/Meeting/Conv → Mention | "Este texto contiene esta mención específica" | 248 |
OF | Mention → Company/Person | "Esta mención se refiere a esta entidad" | 189 |
WITH | Meeting → Person | "Esta reunión tuvo como attendee a esta persona" | 94 |
OPERATES | Fund → Directa/Fondo | "Este fondo opera esta inversión" — cierra la jerarquía institucional | 79 |
HOLDS_DIRECTA | Directa → Company | "Esta inversión es sobre esta empresa" | 67 |
WORKS_AT | Person → Company | "Esta persona trabaja en esta empresa" | 62 |
DELIVERED_BY | Document → Person | "Este doc lo trajo esta persona (sender)" | 43 |
IN_CRM_OF | Person → User | "Esta persona está en el CRM de este miembro del fondo" — preserva cargo/fuente/fecha per-user, una persona puede tener N aristas | 39 |
MEMBER_OF | User → Organization | "Este miembro pertenece a esta org operadora" — inferida por email domain | 4 |
El siguiente diagrama muestra los tipos de nodo y cómo se relacionan entre sí. Cada flecha es una arista típica del grafo.
Encima del diagrama clásico (Cartera · Sources · Identidad · Memoria) se agrega una capa institucional:
:Fund {id:'mazos'} → OPERATES →
:Directa/:Fondo;
:User → MEMBER_OF →
:Organization;
:Person → IN_CRM_OF →
:User (con cargo/fuente/fecha por arista).
Cómo leerlo — 4 capas claras: arriba está la cartera (Directa, Company, Fondo, Quarterly), al medio están las fuentes que capturan información del día a día (Document, Email, Meeting, FinAnalysis, Memory, Conversation), abajo está el extractor LLM que produce Mentions, y a la derecha viven las personas. Cualquier query atraviesa estas 4 capas en pocas aristas.
El grafo no inventa nada — toda la información viene de fuentes reales del sistema. Hay 8 entradas activas:
Las tablas companies, directas, fondos, transferencias, quarterly_updates, kb_personas son la fuente de verdad. Se sincronizan al grafo via backfill (one-shot) + reconcile nightly.
Modificable manualmente vía dashboard o API.
Cuando un user manda un mensaje al bot (omzg_orchestrator), se persiste en omzg_messages y se dispara kg_ingest_omzg: extracción LLM + linking → Mentions en el grafo.
En tiempo real. Hook fire-and-forget.
Cada PDF/DOCX/XLSX nuevo en el Drive del fondo se descarga, OCR-ea, clasifica con LLM, persiste en kb_documentos y dispara kg_ingest_drive.
Cada 8 minutos via cron Kabuto.
Granola envía summaries de cada Google Meet/Zoom transcripto. Entran a kb_interacciones tipo "Reunión" con full_content.
Sync nightly. Crea :Meeting nodes.
El pipeline de Kabuto procesa emails entrantes, extrae attachments, classifica via LLM y persiste en kb_interacciones tipo "Mail" + kb_documentos.
Cada 5 minutos.
Cuando se conecte: facturas y treasury van a holded_invoices. Pipeline preparado para crear :Invoice nodes con tax_id como identidad legal.
0 filas hoy.
El agente Claude usa el MCP HTTP oficial de Cala para enriquecer empresas con data del universo VC global (CEOs, founders, funding rounds).
On-demand desde el agente, no batch.
Sobre el texto de cualquier source (doc, email, meeting summary, mensaje WA), un LLM Haiku detecta entidades mencionadas y crea :Mention nodes con quote y contexto.
Async via ThreadPoolExecutor (max 4 workers). ~10s por source con texto.
Cualquier cambio en Postgres (nuevo contacto, nuevo doc, nueva interacción) se refleja en el grafo en menos de 30 segundos — automáticamente. Si algo falla, hay 2 capas más de respaldo.
El agente Claude que responde por WhatsApp / Telegram tiene acceso al grafo a través del
MCP oficial de Neo4j Labs (mcp-neo4j-cypher), montado en modo
read-only. El agente puede leer cualquier query Cypher pero NO puede escribir
(los writes van solo por los hooks live del backend).
Pensada para que Claude descubra dinámicamente qué labels y relaciones existen. Hoy el agente la salta porque mcp-neo4j-cypher@0.6.0 tiene un bug en apoc.meta.schema. Workaround: usar queries de introspección directas via read_neo4j_cypher.
CALL db.labels()
CALL db.relationshipTypes()
Ejecuta cualquier query Cypher de lectura. Read-timeout 2 minutos para queries pesadas. Sin token-limit (respuestas completas).
read_neo4j_cypher(
query: "MATCH (d:Directa)-[:HOLDS_DIRECTA]->(c)
WHERE c.name = $name RETURN d",
params: {name: "Citibox"}
)
MATCH (d:Directa)-[:HOLDS_DIRECTA]->(c:Company {name:'Citibox'})
OPTIONAL MATCH (qu:QuarterlyUpdate)-[:ABOUT]->(c)
OPTIONAL MATCH (doc:Document)-[:ABOUT]->(c)
OPTIONAL MATCH (m:Meeting)-[:ABOUT]->(c)
OPTIONAL MATCH (f:FinancialAnalysis)-[:ABOUT]->(c)
RETURN d.ticker, d.cost_eur, d.tipo, d.status,
count(DISTINCT qu) AS quarterlies,
count(DISTINCT doc) AS docs,
count(DISTINCT m) AS meetings,
count(DISTINCT f) AS analyses
MATCH (d:Document {doc_type:'pitch_deck'})
-[:HAS_MENTION]->(:Mention)-[:OF]->(c:Company)
WHERE toLower(c.name) CONTAINS 'glovo'
OPTIONAL MATCH (d)-[:ABOUT]->(deckOf:Company)
RETURN d.filename, deckOf.name, c.name
MATCH (cart:Company)<-[:ABOUT]-(doc:Document)
-[:HAS_MENTION]->(:Mention)-[:OF]->(comp:Company)
MATCH (:Directa)-[:HOLDS_DIRECTA]->(cart)
WHERE comp <> cart AND comp.verified = false
WITH comp, count(DISTINCT cart) AS shared,
collect(DISTINCT cart.name) AS participadas
WHERE shared >= 2
RETURN comp.name, shared, participadas
MATCH (p:Person {verified:true})<-[:WITH]-(m:Meeting)
RETURN p.name, p.cargo, count(m) AS meetings
ORDER BY meetings DESC LIMIT 5
MATCH (f:FinancialAnalysis)-[:ABOUT]->(c:Company)
RETURN c.name, f.doc_type, f.status,
substring(coalesce(f.summary,''), 0, 100) AS preview
ORDER BY c.name LIMIT 5
MATCH (d:Document {doc_type:'pitch_deck'})
-[:HAS_MENTION]->(mn:Mention)
-[:OF]->(p:Person {verified:false})
OPTIONAL MATCH (d)-[:ABOUT]->(c:Company)
RETURN p.name, mn.role_hint, c.name AS empresa
MATCH (p:Person)-[r:IN_CRM_OF]->(u:User)
WITH p, count(DISTINCT u) AS n_users,
collect({user: u.name, cargo: r.cargo, fuente: r.fuente}) AS detalle
WHERE n_users >= 2
RETURN p.id AS persona_canonica,
p.postgres_ids AS pg_ids_consolidados,
n_users AS miembros_que_lo_tienen,
detalle
ORDER BY n_users DESC LIMIT 10
postgres_ids de los rows originales agrupados. Antes esto era N nodos duplicados sin relación entre sí.MATCH (f:Fund {id:'mazos'})
OPTIONAL MATCH opPath=(f)-[:OPERATES]->(d:Directa)
WITH f, collect(opPath)[0..10] AS opPaths
MATCH (u:User)
OPTIONAL MATCH memPath=(u)-[:MEMBER_OF]->(o:Organization)
OPTIONAL MATCH crmPath=(p:Person)-[:IN_CRM_OF]->(u)
WITH f, opPaths, u, memPath, collect(crmPath)[0..5] AS crmPaths
RETURN f, opPaths, u, memPath, crmPaths
// Cuando el LLM extrae un nombre suelto de un meeting cuyo source ABOUT apunta
// a una Company conocida, el linker filtra candidatos por WORKS_AT a esa misma Company.
// Si solo 1 candidato cumple, hace el match sin LLM (confidence high).
MATCH (mt:Meeting)-[:ABOUT]->(c:Company),
(mt)-[:HAS_MENTION]->(m:Mention)-[:OF]->(p:Person)
WHERE m.match_tier = 'name_contextual'
RETURN m.name_extracted AS nombre, c.name AS empresa_del_source,
p.id AS resolved_to, m.confidence
tier=name_contextual, confidence=high, persona resuelta al nodo canónico correcto. Sin Tier 1.6 esto hubiera quedado como un placeholder auto-* esperando revisión manual.Hasta hace poco el grafo era plano: todo el universo Mazos se modelaba como "datos del fondo" sin distinguir los 14 miembros del sistema (de 5 organizaciones distintas: Mazos, Umbratech, Greelow, Inspirit, Mazingerventures). El Modelo B-completo agrega esa capa de identidad institucional y resuelve la duplicación de personas que tenían varios miembros del fondo en sus CRMs personales.
auto-*Funcionalmente correcto, arquitecturalmente incompleto.
+)postgres_ids: [array] de los rows originales agrupadosWORKS_AT a la empresa del sourceRefleja la realidad multi-empresa del fondo. Detalle en docs/kg/modelo-multiempresa.md.
| Tarea | Esfuerzo | Por qué |
|---|---|---|
| 3.5h | Implementado 2026-05-15 — pendiente aplicar contra prod via SSH tunnel. | |
| Aplicar Modelo B-completo contra prod (SSH tunnel + sync_all + migración) | 30min | Genera los 14 :User reales + 5 :Organization + consolidación de las personas duplicadas reales del CRM. |
Workaround bug get_neo4j_schema del MCP oficial |
15min | upstream mcp-neo4j-cypher@0.6.0 ejecuta apoc.meta.schema({sample: None}) con None literal. Hoy el agente sólo puede usar db.labels() / db.relationshipTypes(). |
| Deploy Neo4j a prod (instalar uv + agregar a docker-compose + .env) | 1h | Hoy el KG vive solo en dev. Hasta que esté en prod, el agente prod no usa el grafo. |
| Backfill 2858 mensajes históricos omzg | 30min + ~$0.30 LLM | Densidad temporal — Mentions sobre conversaciones antiguas. |
| Adaptar el script de 3 años de Didac para que descubra nodos + relaciones y los inyecte al KG | 1-2d | El script ya recorre 3 años de actividad histórica (emails, docs, meetings). Hoy genera output plano — falta engancharlo al pipeline de extracción + linker para que cada entidad detectada caiga al grafo con su :Mention + arista al source correspondiente. Bootstrappea el grafo con contexto temporal profundo. |
| Endpoint REST /api/kg/dossier + widget Next.js | 1.5d | Visualización del grafo en el dashboard del cliente. |
| F6 Discovery proactivo (patterns + alerts WA) | 2-3d | El agente avisa por WA cuando se cumplen señales compuestas. |
| F7 Audit trail (Recommendation nodes con JUSTIFIED_BY) | 1.5d | Reconstruir el reasoning de cualquier recomendación del agente. |
Versión visual auto-generada de la arquitectura del KG · Mazinger OS · 2026-05-15 · v2 (Modelo B-completo)
Docs relacionados: docs/kg/sync-sostenible.md, docs/kg/modelo-multiempresa.md,
docs/kg/plan-cierre-gaps-2026-05-14.md, docs/kg/kg-schema.md