docs: reescribir README, documentacion tecnica y diagramas
El README describia un despliegue que ya no existe: rama production, Flask
API, OBSEI, tensorflow 2.17 y numpy 1.19. Reescrito con la instalacion real,
las cifras actuales y como levantarlo como servicio.
Dos diagramas nuevos en INFO/DOCS/diagramas/, con el script que los genera al
lado para poder reeditarlos:
flujos-de-informacion.svg como cuatro agencias abastecen a casi toda la
prensa, y el circuito corto de la independiente
arquitectura.svg de la fuente al grafo
Se retira documentacion obsoleta o peligrosa: notas de planificacion fechadas,
un volcado de consola con una credencial en claro, un fichero con comandos
sshpass contra un host identificable, y un LEEME que describia un proyecto
Django que ya no existe. El directorio DOCS/ era copia byte a byte de
INFO/DOCS/.
Del documento de rediseno se extrae la parte tecnica a
ARQUITECTURA_EMBEDDINGS.md y se descarta el resto.
SEGURIDAD.md pasa de lista de agujeros abiertos a registro de las mitigaciones
aplicadas: todas estan corregidas y verificadas en el codigo actual. Corrige
ademas una afirmacion que ya era falsa, que el backend escuchaba en 0.0.0.0.
El dominio pasa a upleaks.org y las rutas de despliegue de los documentos se
sustituyen por $FLUJOS.
This commit is contained in:
parent
e2b1f27699
commit
4265909b55
19 changed files with 927 additions and 371 deletions
|
|
@ -1,61 +0,0 @@
|
|||
┌───────────────────┐
|
||||
│ HTML + CSS UI │
|
||||
│ │
|
||||
│ - contenedor div │
|
||||
│ #globWarContainer│
|
||||
│ - form #paramForm │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ output_glob_war.js │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. Inicialización del gráfico │ │
|
||||
│ │ └─ ForceGraph3D()(elem) configuras estilos, forces │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 2. getData(paramsObj) │ │
|
||||
│ │ ├─ Construye URL: '/api/data?tema=guerra global…' │ │
|
||||
│ │ ├─ fetch(url) → JSON { nodes: [...], links: [...] }│ │
|
||||
│ │ ├─ Filtra enlaces inválidos (fuente/target existe)│ │
|
||||
│ │ ├─ Calcula grado de cada nodo │ │
|
||||
│ │ ├─ Filtra nodos con grado < MIN_DEGREE │ │
|
||||
│ │ └─ Devuelve { nodes, links } │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ ┌────────────────────────────────┐ │
|
||||
│ │ │ 3. graph.graphData(data) │ │
|
||||
│ │ │ Renderiza nodos + enlaces │ │
|
||||
│ │ └────────────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────────────────┐ ┌────────────────────────┐ │
|
||||
│ │ showNodeContent(content) │ │ centerGraph() │ │
|
||||
│ │ └─ despliega content │ │ └─ zoomToFit + padding │ │
|
||||
│ └──────────────────────────────┘ └────────────────────────┘ │
|
||||
│ ▲ ▲ │
|
||||
│ │ │ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 4. Event Listeners │ │
|
||||
│ │ - form submit → llama getData(paramsObj) │ │
|
||||
│ │ - window resize → llama centerGraph() │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FLUJOS_APP.js │
|
||||
│ ┌ getDataHandler(req, res) ┐ │
|
||||
│ │ - recibe paramsQuery │ │
|
||||
│ │ - consulta Mongo: │ │
|
||||
│ │ • filtra por tema │ │
|
||||
│ │ • aplica límites nodos/complejidad/fechas │
|
||||
│ │ - construye { nodes, links } JSON │
|
||||
│ └───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ HTTP GET /api/data → getDataHandler → responde JSON │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
┌──────────────────────────┐
|
||||
│ Cliente JS (output_*.js) │
|
||||
│ getData({tema, …}) │
|
||||
└────────────┬─────────────┘
|
||||
│ HTTP GET /api/data?tema=…&nodos=…&minSim=…
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ FLUJOS_APP.js – Express /api/data │
|
||||
│ │
|
||||
│ 1) Parsea req.query: tema, nodos, minSim│
|
||||
│ 2) nodesQuery = { tema, subtema?, … } │
|
||||
│ 3) .find(nodesQuery).limit(nodos) → │
|
||||
│ wikipediaNodes, noticiasNodes, │
|
||||
│ torrentsNodes │
|
||||
│ 4) Combina y formatea a `formattedNodes`│
|
||||
│ 5) nodeIds = formattedNodes.map(id) │
|
||||
│ 6) linksQuery = { │
|
||||
│ noticia1: { $in: nodeIds }, │
|
||||
│ noticia2: { $in: nodeIds }, │
|
||||
│ porcentaje_similitud: { $gte: min }│
|
||||
│ } │
|
||||
│ 7) comparacionesCollection.find(linksQuery) → rawLinks │
|
||||
│ 8) Formatea rawLinks a `{ source, target, value }` │
|
||||
│ 9) Responde `{ nodes: formattedNodes, links: formattedLinks }` │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────┐
|
||||
│ Cliente JS recibe JSON │
|
||||
│ Filtra nodos sin conexiones│
|
||||
│ Dibuja ForceGraph3D │
|
||||
└───────────────────────────┘
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
proyecto/
|
||||
|
|
||||
|-- .gitignore # Archivos y directorios ignorados por git
|
||||
|-- package-lock.json # Bloquea las versiones de las dependencias
|
||||
|-- FLUJOS_APP.js # Punto de entrada de tu aplicación
|
||||
|
|
||||
|-- DOCS/ # Documentación del proyecto
|
||||
|
|
||||
|-- BACK_BACK/ # Contiene elementos relacionados con el backend
|
||||
| |-- entorno/ # Configuraciones del entorno o archivos relacionados
|
||||
| |-- requirements/ # Dependencias y requerimientos del proyecto
|
||||
| |-- OBSEI/ # (No utilizado, considerar eliminar si no es necesario)
|
||||
|
|
||||
|-- VISUALIZACION/ # Código relacionado con la parte visual o frontend
|
||||
| |-- node_modules/ # Módulos de Node.js
|
||||
| |-- package.json # Dependencias del frontend y scripts
|
||||
| |-- package-lock.json # Bloquea las versiones de las dependencias del frontend
|
||||
| |-- public/ # Archivos estáticos (HTML, CSS, JS, imágenes)
|
||||
| | |-- images/ # Imágenes
|
||||
| | |-- ... # Otros archivos estáticos (html, css, js)
|
||||
| |-- controllers/ # Controladores para la lógica del negocio
|
||||
| |-- models/ # Modelos o esquemas de la base de datos
|
||||
| |-- graphql/ # Definiciones para GraphQL (si se usa)
|
||||
| |-- routes/ # Rutas de la aplicación
|
||||
|
|
||||
|-- .gitignore # Archivos y directorios ignorados por git
|
||||
`-- package-lock.json # Bloquea las versiones de las dependencias
|
||||
180
INFO/DOCS/CONTEXT/ARQUITECTURA_EMBEDDINGS.md
Normal file
180
INFO/DOCS/CONTEXT/ARQUITECTURA_EMBEDDINGS.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# Arquitectura de embeddings y correlación semántica
|
||||
|
||||
Cómo se representan los documentos como vectores y cómo se derivan de ahí las aristas
|
||||
noticia↔leak del grafo. Reemplaza el solape de palabras del pipeline original.
|
||||
|
||||
Implementación: `coconews/workers/backfill_embeddings.py` (generación) y
|
||||
`FLUJOS_DATOS/COMPARACIONES/motor_comparacion.py` (correlación).
|
||||
|
||||
---
|
||||
### 2.4 Esquema canónico unificado (aditivo, no destructivo)
|
||||
|
||||
Una sola forma para todos los nodos (`noticias`, `torrents`, `wikipedia`, `imagenes`, `imagenes_wiki`):
|
||||
|
||||
```
|
||||
{
|
||||
_id,
|
||||
archivo : string // UNIQUE. Clave de dedup y FK hacia comparaciones.
|
||||
source_type : string // 'rss' | 'leak' | 'wikipedia' | 'imagen' | 'legacy_news'
|
||||
tema, subtema : string
|
||||
texto : string // ver 2.5 sobre la realidad del texto de leaks
|
||||
fecha : ISODate|null
|
||||
// --- bloque embedding (idéntico en todas las colecciones) ---
|
||||
embedding : double[384] | null // SIEMPRE L2-normalizado en escritura (ver 4.x)
|
||||
embedding_model : 'paraphrase-multilingual-MiniLM-L12-v2'
|
||||
embedding_dim : 384
|
||||
procesado_embedding: bool
|
||||
apto_embedding : bool // NUEVO: gate de calidad (ver 4.x). false = no entra al índice
|
||||
// --- solo docs largos troceados (rss / leak / wiki) ---
|
||||
chunks : [ { idx, texto, embedding:double[384] } ]
|
||||
// --- solo rss ---
|
||||
url, titulo_original, lang, traducido, procesado_ner, entidades, fuente
|
||||
}
|
||||
```
|
||||
|
||||
**Nota de almacenamiento (corrección al borrador):** MongoDB BSON **no tiene tipo float de 32 bits** — todo número de array se guarda como `double` de 64 bits, escribas `np.float32` o no. Por tanto **guardar float32 NO ahorra disco en Mongo** (el ahorro es real solo en la RAM del índice FAISS al castear a `np.float32` antes de construirlo). El campo `embedding` ocupa 384·8 = **3 KB/doc en disco**; ver el cómputo de huella total en 2.7.
|
||||
|
||||
### 2.5 Realidad del texto de los leaks (bloqueante de calidad)
|
||||
|
||||
**El borrador asumía "texto completo sin limpiar"; eso es imposible para leaks.** El único texto de leak en disco es `txt/*_limpio.txt`, producido por `procesar_torrents.py::limpiar_texto`, que **pasa a minúsculas, elimina TODA la puntuación y quita stopwords ESPAÑOLAS sobre texto mayormente INGLÉS**. El directorio `files/` (texto crudo) está **vacío (0 ficheros)**. Embeber esa sopa de tokens des-puntuada y con acentos rotos (`proteccién`) da **peores** vectores que prosa limpia, porque los sentence-transformers dependen de puntuación y palabras función.
|
||||
|
||||
Decisión (requiere confirmación, ver §5-R1): **re-extraer el texto crudo desde los `.torrent`/ZIP originales** para `texto` (y rehacer chunking sobre prosa real), o **aceptar embeddings degradados** sobre `*_limpio.txt`. Recomendación: re-extraer, porque la calidad del matcher depende directamente de esto, pero acotar a un subconjunto curado primero (ver §5-R1).
|
||||
|
||||
### 2.6 Clave de dedup, namespaces y watermark
|
||||
|
||||
- **Dedup key = `archivo`** con índice único en **todas** las colecciones de nodo. Hoy falta el único en `torrents` (`pipeline_maestro.py::setup_indices`, líneas ~80-91, solo lo crea para wikipedia/noticias/imagenes/imagenes_wiki). **Extender `setup_indices` para incluir `torrents`.**
|
||||
- **Normalización de namespaces — con corrección de viabilidad:**
|
||||
- rss: el ingestor **hoy escribe `archivo = md5(link).hexdigest() + '.txt'`** (CON `.txt`, SIN prefijo `rss_`; el borrador lo decía mal). La migración debe **renombrar TODOS los `archivo` de `noticias` también** → `rss_<md5>` y reescribir sus edges. Esto **no es "aditivo y seguro"**: es un rename global de la clave de dedup en la colección canónica (ver 2.7, switch atómico).
|
||||
- leak: **el re-key a `leak_<sha1(ruta_original)>` es inviable** — `procesar_torrents.py` **nunca escribe en Mongo** (no hay `MongoClient`), los torrents los inserta `pipeline_completo.py::procesar_documentos` con `archivo = basename(<relpath_saneado>_limpio.txt)`, y `limpiar_nombre_archivo` mapea `[\/*?:"<>|]→_` de forma **lossy**: la ruta original **no es recuperable** del nombre guardado. Decisión: **conservar el `basename` actual como clave**, prefijándolo a **`leak_<basename_sin_extension>`** (rename puramente textual sobre lo que ya existe, sin necesidad de la ruta original). Las colisiones reales de `procesar_torrents.py` (el `txt/` plano donde `<name>_limpio.txt` se pisa) se arreglan **en la re-extracción** (§2.5), no en la migración de claves.
|
||||
- wiki: `wiki_<basename>`.
|
||||
- **Watermark:**
|
||||
- **Por documento:** `procesado_embedding=false` = pendiente de embeber (reanudable, idempotente, ya existe).
|
||||
- **Por corrida:** `pipeline_log` (`fase`, `estado`, `fin`). **Corrección sobre `--desde`:** `pipeline_maestro` ya lo pasa (líneas 341-346) y `pipeline_completo.py` lo parsea (línea 219) y **lo ignora**. Pero honrarlo **no es un fix barato**: la comparación actual enumera **directorios de disco** (`os.listdir` de `.../tokenized`), no tiene `fecha` de Mongo en tiempo de comparación. **`--desde` solo se puede honrar de verdad DESPUÉS de reescribir el motor para sacar candidatos de Mongo (F5)** — está contabilizado dentro de F5, no como tarea suelta.
|
||||
|
||||
### 2.7 Migración sin dejar el grafo a oscuras (switch atómico)
|
||||
|
||||
El invariante load-bearing es **`comparaciones.noticia1/noticia2 == nodo.archivo`** (`FLUJOS_APP.js` construye `nodeIds` desde `result.archivo.trim()` y casa links por `noticiaN.trim()`). Renombrar `archivo` (incluido el de coconews: `md5+.txt → rss_<md5>`) **mientras** el productor legacy aún escribe `basename` rompe el grafo en cualquier estado intermedio (cero links → grafo vacío). Reglas:
|
||||
|
||||
1. **El rename de `archivo` y la reescritura de `comparaciones.noticia1/2` son UN solo switch atómico** (script único, idealmente regenerando `comparaciones` desde cero en la regeneración con `motor_comparacion.py` en vez de reescribir 52M docs in-place — ver §5-R4).
|
||||
2. **Canonicalización de orden de pares ANTES del índice único:** almacenar siempre `noticia1 = min(a,b)`, `noticia2 = max(a,b)` (lexicográfico). Sin esto, el build del único `{noticia1,noticia2}` **falla** sobre los pares invertidos `(A,B)/(B,A)` que ya existen, y los re-runs duplican.
|
||||
3. **Plan de rollback / "el grafo sigue vivo":** la regeneración de `comparaciones` (la regeneración con `motor_comparacion.py`) se hace en una colección nueva `comparaciones_v2` y se promueve con `renameCollection` (atómico, sub-segundo) solo cuando está poblada y con índices; si algo falla se descarta `comparaciones_v2` y el front sigue sirviendo la vieja. Ver coste/ventana en §4.
|
||||
|
||||
### 2.8 Pipeline de sync idempotente
|
||||
|
||||
Todo con **upsert keyed-by-`archivo`** (`update_one(..., upsert=True)`), nunca `insert_one`:
|
||||
|
||||
```
|
||||
ingestor (rss) → upsert por archivo, swallow DuplicateKeyError [YA ASÍ]
|
||||
procesar_documentos → cambiar insert_one → update_one upsert (leak/wiki)
|
||||
embedder (generaliz.) → find({procesado_embedding:false, apto_embedding:true}) por colección → $set embedding
|
||||
```
|
||||
|
||||
`pipeline_mongolo.py` (`insert_one`, no idempotente) **se retira** y, crítico, **hay que encontrar y desactivar quien lo relanzó hoy a las 00:53** (cron/timer/parent vivo) — matar los hijos con SIGKILL no basta si el padre sigue respawneando.
|
||||
|
||||
|
||||
## 3. COMPARACIÓN con embeddings
|
||||
|
||||
### 3.1 Qué se reemplaza exactamente
|
||||
|
||||
`comparar_archivos`/`contar_palabras` (`pipeline_completo.py:76-99`): lee ficheros tokenizados de disco, `Counter()` de palabras, `porcentaje = sum(min(c1,c2) sobre comunes) / (len1+len2) * 100` sobre tokens **crudos incluyendo stopwords**, sin TF-IDF, sin poda → cruz completa O(n·m) (~41k noticias × ~36k torrents) → 52M docs en `comparaciones`, con docs idénticos topando al ~50%. Se reemplaza por **embedding por documento (una vez) + coseno top-k vía FAISS**.
|
||||
|
||||
### 3.2 Modelo, vectores y normalización
|
||||
|
||||
- **Modelo:** `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`, 384-dim, cacheado por Hugging Face en `~/.cache/huggingface` (~458 MB), CPU, multilingüe.
|
||||
- **Realidad cross-lingual (clave):** `translator.py:26` traduce **todas las noticias a ESPAÑOL** (`target='es'`); los **leaks NO se traducen**. La comparación real es **prosa-española-noticia vs sopa-de-keywords-inglesa-leak**. El coseno cross-lingual "mismo evento" de MiniLM suele caer en **0.35–0.5**, justo sobre el umbral propuesto. Por eso el umbral **se calibra con medición real, no por intuición** (§3.6) y se considera **umbral por par de idiomas** (mismo-idioma corre más alto que cross-idioma).
|
||||
- **L2-normalización explícita (no hand-waved):** `encode()` de sentence-transformers 5.4.1 tiene `normalize_embeddings=False` por defecto, y `embedder.py:40` guarda `emb.tolist()` **crudo**. Para que `porcentaje_similitud = coseno*100` sea coseno, **tanto los vectores del índice como los de query deben estar normalizados**. Regla: **normalizar en escritura** (`emb = emb / np.linalg.norm(emb)` antes del `$set`) **y** re-normalizar defensivamente al cargar en FAISS. Así el inner product de `IndexFlatIP` == coseno y el umbral tiene sentido.
|
||||
- **Chunking por tokens, no por chars (corrección):** hoy `texto[:512]` son **512 caracteres** (~80 palabras), no tokens — se pierde casi todo. Cambiar a **ventanas de ~256 tokens, solape ~32**, embeber cada chunk → `chunks[]`. **Cap de chunks content-aware, NO truncado duro:** el cap de "máx 20 chunks" del borrador descarta el 99.5% de un leak de 22 MB. En su lugar: si un doc excede el cap, **muestrear/representar todo el doc** (p.ej. embeber todos los chunks pero, para el índice, un cap por muestreo uniforme + el centroide), nunca quedarse con los primeros 20.
|
||||
- **Gate de calidad `apto_embedding` (antídoto de falsos positivos):** muchos "leaks" no son prosa comparable — firmware/keys de calculadora (`voyage 200 freeware flash application signing key n5322...`), blobs hex, y en el lado noticias hay basura tipo `$100_OFF___TUSHY_Discount_Codes`. Un modelo de 384-dim mapea docs cortos de solo-keywords/hex a una región degenerada donde los cosenos son espuriamente altos. **Antes de embeber:** filtrar por nº mínimo de tokens reales, detectar y saltar blobs hex/key/code, detectar idioma. `apto_embedding=false` → no entra al índice ni forma edges.
|
||||
|
||||
### 3.3 Motor ANN: **FAISS (faiss-cpu), `IndexFlatIP`** — decisión final
|
||||
|
||||
| Opción | Veredicto |
|
||||
|---|---|
|
||||
| `$vectorSearch` (Atlas) | ❌ Imposible: Atlas-only; aquí Community self-hosted. |
|
||||
| pgvector | ❌ Introduce Postgres, ausente del stack. Coste operativo injustificado. |
|
||||
| Qdrant | ⚠️ Sobredimensionado: servicio extra, REST. Y un daemon más que puede crash-loopear (justo lo que estamos apagando). |
|
||||
| hnswlib | ⚠️ Segunda opción: HNSW en un fichero, pip puro, persistente. Solo si un perfil real exige ANN aproximado. |
|
||||
| brute-force numpy | ⚠️ Correcto pero innecesario: `IndexFlatIP` hace lo mismo (coseno exacto) más rápido y limpio. |
|
||||
| **FAISS `IndexFlatIP`** | ✅ **Elegido.** |
|
||||
|
||||
**Justificación por volumen — corregida (el borrador omitía Wikipedia y subestimaba ~2×):**
|
||||
|
||||
| Corpus | Ficheros | Vectores (chunking, cap content-aware) |
|
||||
|---|---|---|
|
||||
| noticias (rss) | ~20k docs | ~20k+ (mayoría 1 chunk) |
|
||||
| torrents (leaks) | 36.183 ficheros (hoy ~500 en Mongo) | ~288k chunks |
|
||||
| wikipedia | 28.427 ficheros no vacíos | ~207k chunks |
|
||||
| **Total (techo realista)** | | **~515k vectores** |
|
||||
|
||||
- **RAM del índice:** 515k × 384 × 4 B (float32 en FAISS) ≈ **791 MB** → trivial en 62 GB.
|
||||
- **Coste de búsqueda:** ~20k queries (noticias) × ~495k corpus vía BLAS multinúcleo ≈ **1.3–6.3 min** por rebuild completo (medición teórica; confirmar con benchmark real en el box, §4-F0b).
|
||||
- **Coseno exacto** sobre vectores L2-normalizados → sin pérdida de recall.
|
||||
- **Cero daemons nuevos:** el índice se construye en-proceso dentro del job de comparación. Para crecimiento futuro a millones, `IndexFlatIP → IndexHNSWFlat` es un cambio de una línea.
|
||||
- **Persistencia/staleness (gap del borrador):** el flat index **no es incrementalmente append-and-search-only-new** sin bookkeeping. Decisión explícita: **rebuild completo cada corrida** (es sub-minuto a pocos minutos, barato). Para sync incremental real (`--desde`), el rebuild completo sigue siendo la vía; si en el futuro las noticias crecen mucho (eje temporal: 20k es un snapshot, no un techo — `coconews` ingesta indefinidamente; a 200k noticias el rebuild es ~1 h), se reevalúa HNSW persistente.
|
||||
|
||||
### 3.4 Generación de candidatos (mata el O(n·m)) — con énfasis corregido
|
||||
|
||||
**El verdadero lever de escalado es `top-k`, no el bloqueo por tema.** El borrador presentaba el bloqueo por `tema` como mecanismo principal, pero `asignar_tema_y_subtema` hace matching de keywords **en español** sobre leaks **en inglés** → casi todos caen en `('otros','general')`, así que el bloqueo **colapsa a un índice global** justo para el corpus que domina el conteo de vectores. Como el global es solo ~6 min, es viable. Plan:
|
||||
|
||||
1. **Índice global ANN** (no por tema) como base, dado que el bloqueo por tema no es fiable para leaks. Opcionalmente, **clasificar leaks por embedding** (vecino más cercano a centroides de tema) para recuperar el bloqueo como optimización, no como dependencia (§5-R3).
|
||||
2. **`top-k` por noticia**, con `k` derivado del cap real del front (ver 3.5).
|
||||
3. Solo esos vecinos se escriben como edges. **Adiós a los 52M de pares casi-cero.**
|
||||
|
||||
### 3.5 Reducción chunk→edge y presupuesto de `k` (gap central resuelto)
|
||||
|
||||
**Reducción chunk→doc (el borrador la dejaba ambigua, era un hueco de corrección):** el índice se construye con **vectores a nivel de chunk** (recall local: "¿algún fragmento del leak menciona este evento?"). La búsqueda devuelve top-k **chunks**; se **colapsan a nivel de doc** así:
|
||||
|
||||
- `score_doc(noticia, leak) = max sobre chunks` del coseno, **pero** con un **guard anti-inflación**: para leaks muy largos (muchos chunks) el `max` casi siempre encuentra un chunk espurio ≥ umbral. Mitigación: exigir **al menos `m≥2` chunks distintos del leak** por encima de un umbral secundario, **o** combinar `max` con el coseno del **centroide del doc** (`score = 0.5·max_chunk + 0.5·centroide`) para penalizar el "un solo chunk casual". Se calibra junto al umbral (§3.6).
|
||||
- Se escribe **un único edge por par `(noticia_archivo, leak_archivo)`** con el `score_doc` resultante → **no se reinfla `comparaciones`** ni colisionan múltiples chunk-hits sobre la clave `(noticia1,noticia2)`.
|
||||
|
||||
**Presupuesto de `k` respetando el cap real del front (corrección):** `MAX_LINKS_PER_NODE=8` en `FLUJOS_APP.js` **no** es "8 leaks por noticia": el front (líneas 228-241) **ordena TODOS los candidatos globalmente por `porcentaje_similitud` y hace asignación greedy capando el grado de AMBOS extremos a 8, cruzando TODOS los corpus** (noticias+wiki+torrents+imágenes). Implicaciones:
|
||||
|
||||
- Un leak popular que es top-8 de 50 noticias será **descartado** para 42 de ellas por el cap del lado-leak.
|
||||
- Una noticia que ya enlaza a wiki/imágenes ve sus edges de leak **desplazados**.
|
||||
|
||||
Por tanto, **generar top-8-por-noticia no produce 8 edges visibles**. Plan: **generar `k` algo mayor (p.ej. `k=15–20` por noticia) como sobre-aprovisionamiento**, dejar que la asignación greedy simétrica cross-corpus del front recorte, y **medir** cuántos news↔leak edges sobreviven realmente al render para ajustar `k`. El cap no es una excusa para under-generar.
|
||||
|
||||
### 3.6 Umbral, calibración y evaluación (requisito previo, NO opcional)
|
||||
|
||||
- **No se compromete 0.45 a ciegas.** Antes de regenerar el grafo: **etiquetar a mano ~50 pares news↔leak conocidos** (y específicamente un subconjunto **cross-lingual** español↔inglés), generar el **histograma de coseno** sobre una muestra real, y elegir el umbral donde precision/recall sea aceptable. Es esperable un **umbral por par de idiomas** (same-language más alto que cross-language) para no infra-enlazar justo los pares español-noticia↔inglés-leak que son el objetivo del proyecto.
|
||||
- **Mapeo al contrato:** `porcentaje_similitud = round(score_doc * 100, 2)`.
|
||||
- **Recalibración del slider `complejidad` es OBLIGATORIA, no opcional (corrección):** el default `20`, el rango `1-40` y el `IMG_UMBRAL>=3` se ajustaron al overlap viejo (que topaba al ~50% e incluía stopwords). La distribución de `coseno·100` es **completamente distinta** → reusar esos números es una **regresión visual garantizada**. Tras ver la distribución real hay que re-tunear defaults del front (cambio mínimo en `FLUJOS_APP.js`, sí toca front — único toque necesario).
|
||||
|
||||
### 3.7 Documento de edge
|
||||
|
||||
```jsonc
|
||||
{
|
||||
noticia1: "leak_9f3c...", // = min(archivo_a, archivo_b) (canonicalizado, §2.7)
|
||||
noticia2: "rss_ab12...", // = max(archivo_a, archivo_b)
|
||||
porcentaje_similitud: 57.3, // = score_doc*100
|
||||
// --- aditivos, ignorados por el front, útiles para auditoría ---
|
||||
metodo: "embedding",
|
||||
cosine: 0.573,
|
||||
agregacion: "max_chunk+centroide",
|
||||
modelo: "paraphrase-multilingual-MiniLM-L12-v2"
|
||||
}
|
||||
```
|
||||
|
||||
`FLUJOS_APP.js` lee solo `noticia1/noticia2/porcentaje_similitud` (+ rama `source1_type='imagen'`, intacta) → **cero cambios en el front salvo la recalibración del slider de §3.6**.
|
||||
|
||||
### 3.8 Throughput del backfill (el verdadero riesgo de escala — corregido a fondo)
|
||||
|
||||
> **Medido sobre texto real de leaks** (sentence-transformers 5.4.1, CPU de 24 cores con AVX2, sin GPU): **~59-66 chunks/s** (BATCH 20≈128: torch ya satura los cores por llamada). **Encode de 515k chunks ≈ 2.2 h**, NO 40-60 h. Las 40-60 h eran el wall-clock del loop ACTUAL (`sleep(2)`×~25k batches ≈14 h + 515k `update_one`). Quitando `sleep(2)` + `bulk_write` → backfill total **~2.5-3 h**. `normalize_embeddings=True` da vectores unitarios dim=384 (L2-norm = un flag). **GPU no es el cuello de botella.**
|
||||
|
||||
El borrador llamaba al backfill "un job nocturno, puede tardar horas". **Con la cadencia ACTUAL del embedder eso está mal por un orden de magnitud:** `BATCH=20` + `model.encode()` + **`time.sleep(2)` tras cada batch no vacío** + **`update_one` por documento** (no `bulk_write`). Backfillear ~515k chunks así = **40–60 HORAS** (solo el `sleep(2)` sobre ~25.757 batches son ~14 h de puro sleep; los 515k round-trips individuales añaden más). **La pregunta de GPU (§5-R7) es un distractor: el cuello de botella es la estructura del loop, no la falta de GPU.**
|
||||
|
||||
**Modo backfill explícito (separado del daemon trickle):**
|
||||
1. **`BATCH ≥ 128`** (o mayor según RAM/CPU).
|
||||
2. **Eliminar `sleep(2)`** en modo backfill (el sleep tiene sentido para el daemon incremental, no para una carga masiva).
|
||||
3. **`bulk_write(ordered=False)`** en vez de `update_one` por doc — la mejora individual de mayor palanca.
|
||||
4. **Medir chunks/s real en el box** (con/sin AVX-512) antes de comprometer un ETA — un único `model.encode` sobre un batch de chunks de 256 tokens convierte el ETA de adivinanza en número (§4-F0b).
|
||||
|
||||
### 3.9 Índices que faltan (bloqueante para que el front no haga full-scan)
|
||||
|
||||
`comparaciones` (52M docs) **no tiene índice en `noticia1`/`noticia2`** → la `linksQuery` con `$in` (`FLUJOS_APP.js:213-222`) es un **scan de colección**. Crear compuesto `{noticia1:1, noticia2:1}`. El **único** `{noticia1,noticia2}` solo **después** de canonicalizar el orden de pares (§2.7) — si no, el build falla sobre los invertidos.
|
||||
|
||||
### 3.10 Huella en disco y presión sobre WiredTiger (gap del borrador)
|
||||
|
||||
515k vectores × 384 × 8 B (double BSON) ≈ **1.6 GB** de arrays `embedding` añadidos a la base, **más** los `chunks[].embedding` inline duplicando vector por chunk dentro de cada doc (esto **infla el tamaño de cada doc** de `torrents`/`wikipedia` y por tanto el working-set). El caché WiredTiger por defecto es ~50% de 62 GB. **Acción:** monitorizar el crecimiento, y si los `chunks[]` inline pesan demasiado en el working-set, considerar una **colección separada `chunks_embeddings`** (un doc por chunk) en vez de array inline. Decisión a tomar con el número real tras F3.
|
||||
|
||||
---
|
||||
|
|
@ -218,6 +218,6 @@ La asignación la hace `pipeline_completo.py::asignar_tema_y_subtema()` buscando
|
|||
```
|
||||
MongoDB FLUJOS_DATOS
|
||||
└── imagenes_wiki / imagenes
|
||||
└── image_path → /var/www/theflows.net/flujos/FLUJOS_DATOS/IMAGENES/output/wiki_images/{tema_slug}/{archivo}
|
||||
servido como → https://theflows.net/wiki-images/{tema_slug}/{archivo}
|
||||
└── image_path → $FLUJOS/FLUJOS_DATOS/IMAGENES/output/wiki_images/{tema_slug}/{archivo}
|
||||
servido como → https://upleaks.org/wiki-images/{tema_slug}/{archivo}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Pipeline Maestro — Contexto técnico FLUJOS
|
||||
**Fecha:** 2026-04-21
|
||||
**Fecha:** 2026-04-24 (actualizado)
|
||||
**Archivo:** `FLUJOS_DATOS/pipeline_maestro.py`
|
||||
**Scheduler:** systemd timer (semanal, domingos 3:00 AM)
|
||||
**Scheduler:** systemd timer `flujos-pipeline.timer` — cada 12H (`OnUnitActiveSec=12H`)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -11,9 +11,13 @@ El pipeline maestro orquesta las tres fases del sistema en orden estricto:
|
|||
|
||||
```
|
||||
FASE 1 — SCRAPING
|
||||
├── Wikipedia (main.py) → colección wikipedia
|
||||
├── Noticias (main_noticias.py) → colección noticias
|
||||
└── Imágenes Wikipedia (wikipedia_image_scraper.py) → colección imagenes_wiki
|
||||
├── Wikipedia (main.py) → colección wikipedia
|
||||
├── Noticias (main_noticias.py) → colección noticias
|
||||
├── Imágenes Wikipedia (wikipedia_image_scraper.py) → colección imagenes_wiki
|
||||
└── Comparaciones imagen-texto (comparar_imagenes_wiki.py) → colección comparaciones
|
||||
[AÑADIDO 2026-04-24: TF-IDF entre imagenes_wiki y corpus texto,
|
||||
necesario porque Qwen (fase 2) tarda demasiado y imagenes_wiki
|
||||
queda sin enlaces en el grafo hasta que Qwen termine]
|
||||
|
||||
FASE 2 — ANÁLISIS
|
||||
├── Qwen3-VL imágenes (pipeline_imagenes.py --analizar) → colección imagenes
|
||||
|
|
@ -29,7 +33,7 @@ FASE 3 — COMPARACIÓN
|
|||
|
||||
```bash
|
||||
# Ejecución completa (las 3 fases)
|
||||
/var/www/theflows.net/flujos/FLUJOS_DATOS/myenv/bin/python3 pipeline_maestro.py
|
||||
$FLUJOS/FLUJOS_DATOS/myenv/bin/python3 pipeline_maestro.py
|
||||
|
||||
# Solo una fase
|
||||
python pipeline_maestro.py --solo-fase scraping
|
||||
|
|
@ -141,11 +145,11 @@ Requires=mongod.service
|
|||
[Service]
|
||||
Type=oneshot
|
||||
User=capitansito
|
||||
WorkingDirectory=/var/www/theflows.net/flujos/FLUJOS_DATOS
|
||||
WorkingDirectory=$FLUJOS/FLUJOS_DATOS
|
||||
Environment=MONGO_URL=mongodb://localhost:27017
|
||||
Environment=DB_NAME=FLUJOS_DATOS
|
||||
ExecStart=/var/www/theflows.net/flujos/FLUJOS_DATOS/myenv/bin/python3 \
|
||||
/var/www/theflows.net/flujos/FLUJOS_DATOS/pipeline_maestro.py
|
||||
ExecStart=$FLUJOS/FLUJOS_DATOS/myenv/bin/python3 \
|
||||
$FLUJOS/FLUJOS_DATOS/pipeline_maestro.py
|
||||
TimeoutStartSec=43200
|
||||
Nice=15
|
||||
CPUQuota=400%
|
||||
|
|
@ -175,7 +179,13 @@ Unit=flujos-pipeline.service
|
|||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
Cambiado de semanal (`OnCalendar=Sun`) a cada 2 días (`OnUnitActiveSec=2d`) el 2026-04-22. `Persistent=true` hace que si el servidor estaba apagado cuando tocaba, el timer se dispara 2 minutos después del siguiente arranque.
|
||||
Timer activo a 12H (`OnUnitActiveSec=12H`). `Persistent=true` hace que si el servidor estaba apagado cuando tocaba, el timer se dispara 2 minutos después del siguiente arranque.
|
||||
|
||||
**AVISO 2026-04-24:** El servicio falló por timeout (SIGTERM a las 12h) el 2026-04-22 porque la fase 2 (Qwen, 186 imágenes × ~3 min/imagen ≈ 9h) más la fase 1 (~2h) superaron `TimeoutStartSec=43200`. El timer quedó en estado `elapsed/n/a`. Para resetear manualmente:
|
||||
```bash
|
||||
sudo systemctl reset-failed flujos-pipeline.service
|
||||
sudo systemctl start flujos-pipeline.timer
|
||||
```
|
||||
|
||||
### Comandos de gestión
|
||||
|
||||
|
|
@ -237,7 +247,7 @@ journalctl -u flujos-pipeline.service --since "2026-04-20"
|
|||
|
||||
---
|
||||
|
||||
## Errores conocidos y fixes aplicados (2026-04-22)
|
||||
## Errores conocidos y fixes aplicados
|
||||
|
||||
| Script | Error | Fix |
|
||||
|---|---|---|
|
||||
|
|
@ -248,7 +258,7 @@ journalctl -u flujos-pipeline.service --since "2026-04-20"
|
|||
|
||||
Todos los módulos se instalan dentro del entorno virtual:
|
||||
```bash
|
||||
/var/www/theflows.net/flujos/FLUJOS_DATOS/myenv/bin/python3 -m pip install <modulo>
|
||||
$FLUJOS/FLUJOS_DATOS/myenv/bin/python3 -m pip install <modulo>
|
||||
```
|
||||
No usar `pip` directamente (el ejecutable `pip` del venv puede estar roto; usar `python3 -m pip`).
|
||||
|
||||
|
|
@ -278,7 +288,8 @@ pipeline_maestro.py
|
|||
├── [FASE 1] fase_scraping()
|
||||
│ ├── subprocess: WIKIPEDIA/main.py
|
||||
│ ├── subprocess: NOTICIAS/main_noticias.py
|
||||
│ └── subprocess: IMAGENES/wikipedia_image_scraper.py --flujos --max 20 --mongo
|
||||
│ ├── subprocess: IMAGENES/wikipedia_image_scraper.py --flujos --max 20 --mongo
|
||||
│ └── subprocess: IMAGENES/comparar_imagenes_wiki.py --umbral 3.0 ← AÑADIDO 2026-04-24
|
||||
│
|
||||
├── [FASE 2] fase_analisis()
|
||||
│ ├── subprocess: IMAGENES/pipeline_imagenes.py --analizar --carpeta ... --mongo
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
Internet
|
||||
│
|
||||
▼
|
||||
nginx (theflows.net:443, TLS Let's Encrypt)
|
||||
nginx (upleaks.org:443, TLS Let's Encrypt)
|
||||
├── / → FLUJOS/VISUALIZACION/public/ (estáticos)
|
||||
├── /api/ → proxy → Node.js :3000
|
||||
└── /wiki-images/ → (Node.js sirve estáticos de IMAGENES/output/wiki_images/)
|
||||
|
|
@ -63,7 +63,7 @@ PORT=3000
|
|||
## Rutas críticas del servidor
|
||||
|
||||
```
|
||||
/var/www/theflows.net/flujos/
|
||||
$FLUJOS/
|
||||
├── docs/ # esta documentación
|
||||
├── FLUJOS/
|
||||
│ ├── BACK_BACK/FLUJOS_APP.js # servidor Node.js
|
||||
|
|
|
|||
|
|
@ -35,20 +35,21 @@ Descarga imágenes de Wikipedia por tema usando la **Wikimedia REST API**. Para
|
|||
### Temas de FLUJOS que se scrapean (`TEMAS_FLUJOS`)
|
||||
|
||||
```python
|
||||
# ACTUALIZADO 2026-04-24: cambiados a los 5 temas del frontend
|
||||
# (antes eran 10 con nombres académicos que no coincidían con los temas del backend)
|
||||
TEMAS_FLUJOS = [
|
||||
"cambio climático",
|
||||
"geopolítica conflictos",
|
||||
"seguridad internacional espionaje",
|
||||
"libertad de prensa periodismo",
|
||||
"corporaciones poder económico",
|
||||
"populismo extremismo",
|
||||
"desinformación redes sociales",
|
||||
"privacidad vigilancia masiva",
|
||||
"biodiversidad medioambiente",
|
||||
"inteligencia artificial tecnología",
|
||||
"guerra global",
|
||||
"inteligencia y seguridad",
|
||||
"economía y corporaciones",
|
||||
"demografía y sociedad",
|
||||
]
|
||||
```
|
||||
|
||||
> **Por qué cambió:** Los 10 temas originales ("geopolítica conflictos", "seguridad internacional espionaje", etc.) no coincidían con los temas validados en `FLUJOS_APP.js` (`TEMAS_VALIDOS`), por lo que las imágenes se descargaban pero nunca aparecían en el grafo porque la query `{tema: "inteligencia y seguridad"}` no encontraba documentos con `tema: "seguridad internacional espionaje"`.
|
||||
|
||||
> Las imágenes scrapeadas con los temas antiguos siguen en disco (`output/wiki_images/geopolítica_conflictos/`, etc.) pero solo las de los 5 temas nuevos están accesibles desde el frontend.
|
||||
|
||||
### Filtros de calidad (imágenes que se descartan)
|
||||
|
||||
```python
|
||||
|
|
@ -103,7 +104,7 @@ python wikipedia_image_scraper.py --tema "climate change" --lang en --max 40
|
|||
```json
|
||||
{
|
||||
"archivo": "cambio_climático_003.jpg",
|
||||
"image_path": "/var/www/theflows.net/flujos/FLUJOS_DATOS/IMAGENES/output/wiki_images/cambio_climático/cambio_climático_003.jpg",
|
||||
"image_path": "$FLUJOS/FLUJOS_DATOS/IMAGENES/output/wiki_images/cambio_climático/cambio_climático_003.jpg",
|
||||
"image_url": "https://upload.wikimedia.org/wikipedia/commons/...",
|
||||
"tema": "cambio climático",
|
||||
"subtema": "derretimiento glaciar ártico",
|
||||
|
|
@ -251,7 +252,7 @@ python-dotenv
|
|||
|
||||
Instalación en el venv:
|
||||
```bash
|
||||
/var/www/theflows.net/flujos/FLUJOS_DATOS/myenv/bin/python3 -m pip install -r requirements_imagenes.txt
|
||||
$FLUJOS/FLUJOS_DATOS/myenv/bin/python3 -m pip install -r requirements_imagenes.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -263,9 +264,24 @@ Instalación en el venv:
|
|||
- Imágenes analizadas con Qwen en `imagenes` (MongoDB): proceso en curso / pendiente de primera ejecución completa
|
||||
- El modelo Qwen se descarga automáticamente la primera vez que se llama (~16 GB, puede tardar 30–60 min)
|
||||
|
||||
## Script auxiliar: comparar_imagenes_wiki.py (AÑADIDO 2026-04-24)
|
||||
|
||||
Script que genera comparaciones TF-IDF entre `imagenes_wiki` y el corpus de texto (wikipedia + noticias + torrents) sin necesidad de Qwen. Sirve como fallback mientras `imagenes` (Qwen) esté vacío.
|
||||
|
||||
```bash
|
||||
python comparar_imagenes_wiki.py # todos los temas, umbral 3%
|
||||
python comparar_imagenes_wiki.py --umbral 5.0 # más restrictivo
|
||||
python comparar_imagenes_wiki.py --tema "cambio climático"
|
||||
```
|
||||
|
||||
Los documentos generados en `comparaciones` tienen los campos extendidos:
|
||||
`source1_type: "imagen"`, `source2_type: "wikipedia"|"noticias"|"torrents"`, `tema`.
|
||||
|
||||
Cuando Qwen finalmente analice las imágenes, `pipeline_imagenes.py --analizar --mongo` generará comparaciones más ricas (basadas en keywords VLM) que COEXISTIRÁN con estas TF-IDF (misma colección, no se sobreescriben).
|
||||
|
||||
## Pendiente / mejoras futuras
|
||||
|
||||
- Conectar nodos imagen a la colección `comparaciones` (requiere embeddings de imagen o comparación texto-descripción)
|
||||
- Activar GPU cuando esté disponible (cambiar `device_map="cpu"` → `"cuda"`)
|
||||
- Aumentar `--max` a 50+ imágenes por tema una vez el análisis esté validado
|
||||
- Aumentar `--max` a 50+ imágenes por tema
|
||||
- Resolver el timeout de Qwen (186 imgs × 3min/img ≈ 9h): separarlo en un systemd service propio con `TimeoutStartSec=86400`
|
||||
- Añadir soporte para imágenes de noticias (no solo Wikipedia)
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ FLUJOS_DATOS/WIKIPEDIA/
|
|||
|
||||
`pipeline_maestro.py` Fase 1 llama:
|
||||
```bash
|
||||
/var/www/theflows.net/flujos/FLUJOS_DATOS/myenv/bin/python3 \
|
||||
$FLUJOS/FLUJOS_DATOS/myenv/bin/python3 \
|
||||
FLUJOS_DATOS/WIKIPEDIA/main.py
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,28 @@
|
|||
# Informe de Seguridad — FLUJOS
|
||||
**Fecha:** 2026-04-21
|
||||
**Auditor:** Análisis de código estático + pruebas manuales en local
|
||||
# Seguridad — FLUJOS
|
||||
|
||||
**Scope:** API REST (`FLUJOS_APP.js`), frontend JS, configuración nginx/systemd
|
||||
|
||||
## Estado
|
||||
|
||||
**Todas las vulnerabilidades de este documento están corregidas.** Lo que sigue es el
|
||||
registro de la auditoría y de las mitigaciones que se aplicaron, no una lista de agujeros
|
||||
abiertos. Se conserva porque explica por qué el código valida como valida.
|
||||
|
||||
Comprobable en `FLUJOS/BACK_BACK/FLUJOS_APP.js`:
|
||||
|
||||
| Mitigación | Dónde |
|
||||
|---|---|
|
||||
| Bind sólo a loopback | `app.listen(port, '127.0.0.1', ...)` |
|
||||
| Helmet + CSP propia | `app.use(helmet())` y `helmet.contentSecurityPolicy` |
|
||||
| Rechazo de operadores Mongo | `sanitizeParam()` descarta todo lo que no sea string |
|
||||
| Lista blanca de temas | `TEMAS_VALIDOS` |
|
||||
| Escape de metacaracteres | en `palabraClave`, antes de construir el `$regex` |
|
||||
| Rate limiting | en el buzón de envíos |
|
||||
| Panel admin fail-closed | `requireAdmin`, token por env o fichero fuera del webroot |
|
||||
|
||||
---
|
||||
|
||||
## Resumen ejecutivo
|
||||
|
||||
La aplicación despliega una API Express + MongoDB sin autenticación ni rate limiting. Se han identificado **2 vulnerabilidades críticas** explotables ahora mismo, 3 altas y 2 medias.
|
||||
## Auditoría original
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -196,21 +211,22 @@ Todos los ítems críticos y altos han sido corregidos en producción:
|
|||
|
||||
---
|
||||
|
||||
## Configuración nginx relevante (theflows.net)
|
||||
## Configuración nginx relevante
|
||||
|
||||
```
|
||||
/etc/nginx/sites-enabled/theflows.net
|
||||
/etc/nginx/sites-enabled/upleaks.org
|
||||
├── HTTP :80 → redirect 301 HTTPS
|
||||
├── HTTPS :443 ssl http2 (Let's Encrypt)
|
||||
│ ├── root: FLUJOS/VISUALIZACION/public (estáticos directos)
|
||||
│ └── /api/ → proxy_pass http://127.0.0.1:3000/api/
|
||||
```
|
||||
|
||||
**Problema:** el backend Node también escucha en `0.0.0.0:3000`, así que nginx solo es un proxy opcional, no obligatorio.
|
||||
El backend Node escucha únicamente en `127.0.0.1:3000`, así que nginx no es un proxy
|
||||
opcional: es la única vía de entrada.
|
||||
|
||||
---
|
||||
|
||||
## Orden de prioridad de fixes
|
||||
## Orden en que se aplicaron los fixes
|
||||
|
||||
| # | Vulnerabilidad | Impacto | Esfuerzo |
|
||||
|---|---|---|---|
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
## Arquitectura general
|
||||
|
||||
```
|
||||
theflows.net (nginx)
|
||||
upleaks.org (nginx)
|
||||
├── / → archivos estáticos desde VISUALIZACION/public/
|
||||
└── /api/ → proxy a Node.js :3000
|
||||
└── GET /api/data?tema=...&nodos=...&complejidad=...
|
||||
|
|
@ -67,7 +67,7 @@ Parámetros de query:
|
|||
}
|
||||
```
|
||||
|
||||
El backend limita imágenes a `floor(nodosLimit / 3)` para no saturar el grafo.
|
||||
El backend limita imágenes a `max(3, floor(nodosLimit / 3))` para escalar proporcionalmente con los nodos de texto.
|
||||
Prefiere `imagenes` (analizadas con Qwen) sobre `imagenes_wiki` (fallback scrapeado).
|
||||
|
||||
---
|
||||
|
|
@ -218,14 +218,14 @@ La CSP actual (Helmet) permite `unsafe-inline` en scripts — pendiente de corre
|
|||
## Imágenes wiki — ruta física a URL
|
||||
|
||||
```
|
||||
Disco: /var/www/theflows.net/flujos/FLUJOS_DATOS/IMAGENES/output/wiki_images/{tema_slug}/{archivo}
|
||||
Disco: $FLUJOS/FLUJOS_DATOS/IMAGENES/output/wiki_images/{tema_slug}/{archivo}
|
||||
Express: app.use('/wiki-images', express.static('.../wiki_images'))
|
||||
URL: https://theflows.net/wiki-images/{tema_slug}/{archivo}
|
||||
URL: https://upleaks.org/wiki-images/{tema_slug}/{archivo}
|
||||
```
|
||||
|
||||
El backend construye `image_url` en el API:
|
||||
```javascript
|
||||
const WIKI_IMAGES_BASE = '/var/www/theflows.net/flujos/FLUJOS_DATOS/IMAGENES/output/wiki_images/';
|
||||
const WIKI_IMAGES_BASE = '$FLUJOS/FLUJOS_DATOS/IMAGENES/output/wiki_images/';
|
||||
const relativePath = result.image_path.replace(WIKI_IMAGES_BASE, '');
|
||||
image_url = `/wiki-images/${relativePath}`;
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
=============================================================================
|
||||
DOCUMENTACIÓN DEL PROYECTO FLUJOS
|
||||
=============================================================================
|
||||
|
||||
INTRODUCCIÓN:
|
||||
-------------
|
||||
El proyecto FLUJOS es una aplicación web diseñada para unir noticias y eventos del pasado para entender la historia como una sucesion de eventos .
|
||||
Combatir la desinformacion es el principal objetivo del proeycto .
|
||||
|
||||
ESTRUCTURA DEL PROYECTO:
|
||||
-------------------------
|
||||
El proyecto FLUJOS está organizado de la siguiente manera:
|
||||
|
||||
- /flujos: Carpeta principal del proyecto.
|
||||
- /BACK: Contiene los archivos y scripts relacionados con la parte del servidor y la base de datos.
|
||||
- /FRONT: Contiene los archivos de la aplicación web del lado del cliente.
|
||||
- /TENSOR_FLOW: Carpeta que alberga los componentes relacionados con TensorFlow y la inteligencia artificial.
|
||||
- /DOCUMENTACIÓN: Documentación técnica y de usuario.
|
||||
|
||||
INSTALACIÓN:
|
||||
------------
|
||||
Para configurar y ejecutar el proyecto FLUJOS, sigue estos pasos:
|
||||
|
||||
1. Clona el repositorio desde GitLab:
|
||||
|
||||
git clone https://gitlab.com/tu-usuario/flujos.git
|
||||
|
||||
|
||||
|
||||
2. Crea y activa un entorno virtual:
|
||||
|
||||
python3 -m venv env
|
||||
source env/bin/activate
|
||||
|
||||
|
||||
|
||||
3. Instala las dependencias:
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
4. Configura la base de datos y realiza las migraciones:
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
|
||||
|
||||
5. Crea un superusuario para administrar la aplicación:
|
||||
|
||||
python manage.py createsuperuser
|
||||
|
||||
|
||||
|
||||
EJECUCIÓN:
|
||||
----------
|
||||
Para ejecutar el proyecto FLUJOS en un entorno de desarrollo local, utiliza el siguiente comando:
|
||||
|
||||
python manage.py runserver
|
||||
|
||||
|
||||
|
||||
El proyecto estará disponible en http://localhost:8000/.
|
||||
|
||||
ESTRUCTURA DEL PROYECTO (DETALLES):
|
||||
-------------------------------------
|
||||
- /BACK: Esta carpeta contiene la lógica del servidor Django y se encarga de la autenticación de usuarios y la gestión de la base de datos.
|
||||
|
||||
- /FRONT: Aquí se encuentran los archivos estáticos y la interfaz de usuario de la aplicación web.
|
||||
|
||||
- /TENSOR_FLOW: Carpeta dedicada a las implementaciones de TensorFlow y el procesamiento de datos relacionado con la inteligencia artificial.
|
||||
|
||||
AUTENTICACIÓN DE USUARIOS:
|
||||
--------------------------
|
||||
El sistema de autenticación de usuarios permite a los usuarios registrarse, iniciar sesión y gestionar sus cuentas de usuario. Los datos se almacenan en una base de datos Elasticsearch.
|
||||
|
||||
ENVÍO DE CORREOS ELECTRÓNICOS:
|
||||
-------------------------------
|
||||
El proyecto FLUJOS incluye un sistema de envío de correos electrónicos para funciones como verificación de correo electrónico y recuperación de contraseñas.
|
||||
|
||||
SEGURIDAD Y PRIVACIDAD:
|
||||
-----------------------
|
||||
El proyecto FLUJOS se preocupa por la seguridad y privacidad de los usuarios y emplea medidas de seguridad estándar.
|
||||
|
||||
IMPLEMENTACIÓN DE PGP:
|
||||
----------------------
|
||||
El proyecto utiliza PGP para garantizar la seguridad de las comunicaciones entre periodistas y la aplicación web.
|
||||
|
||||
PRUEBAS:
|
||||
--------
|
||||
Para ejecutar las pruebas unitarias y de integración, utiliza el siguiente comando:
|
||||
|
||||
python manage.py test
|
||||
|
||||
|
||||
|
||||
DESPLEGUE:
|
||||
----------
|
||||
El despliegue del proyecto FLUJOS en un entorno de producción requiere [Instrucciones y mejores prácticas para desplegar el proyecto en un entorno de producción real].
|
||||
|
||||
CONTRIBUCIONES:
|
||||
---------------
|
||||
¡Agradecemos las contribuciones! Si deseas contribuir al proyecto, sigue las pautas en [Enlace a las pautas de contribución en GitLab].
|
||||
|
||||
CONTACTO:
|
||||
---------
|
||||
Para obtener ayuda o más información, comunícate con [Información de contacto].
|
||||
|
||||
LICENCIA:
|
||||
---------
|
||||
Este proyecto se distribuye bajo la licencia [Nombre de la licencia]. Consulta el archivo LICENSE para más detalles.
|
||||
|
||||
=============================================================================
|
||||
81
INFO/DOCS/diagramas/arquitectura.svg
Normal file
81
INFO/DOCS/diagramas/arquitectura.svg
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 770" width="1500" height="770" font-family="DejaVu Sans Mono, Menlo, Consolas, monospace" role="img" aria-label="Arquitectura de UP-LEAKS: fuentes, workers, base de datos, correlacion, API y visualizacion">
|
||||
<defs>
|
||||
<marker id="a1" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#39ff14" opacity="0.85"/></marker>
|
||||
<marker id="a2" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#3ad6ff" opacity="0.85"/></marker>
|
||||
<marker id="a3" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#ffb020" opacity="0.85"/></marker>
|
||||
<marker id="a4" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#ff5ea8" opacity="0.85"/></marker>
|
||||
</defs>
|
||||
<rect width="1500" height="770" fill="#050505"/>
|
||||
<text x="46" y="54" fill="#39ff14" font-size="27" font-weight="bold" letter-spacing="3">ARQUITECTURA</text>
|
||||
<text x="46" y="80" fill="#8a938a" font-size="14">De la fuente al grafo. Cada bloque es un proceso independiente; se comunican solo por MongoDB.</text>
|
||||
<line x1="46" y1="96" x2="1454" y2="96" stroke="#39ff14" stroke-opacity="0.28"/>
|
||||
<rect x="46" y="130" width="300" height="174" rx="4" fill="#0d0f0d" stroke="#3ad6ff" stroke-width="1.5" stroke-opacity="0.85"/>
|
||||
<text x="62" y="156" fill="#3ad6ff" font-size="13" letter-spacing="1.5">FUENTES</text>
|
||||
<text x="62" y="190" fill="#8a938a" font-size="12.5">236 feeds RSS</text>
|
||||
<text x="330" y="190" fill="#5a625a" font-size="11.5" text-anchor="end">5 idiomas</text>
|
||||
<text x="62" y="220" fill="#8a938a" font-size="12.5">API de Wikipedia</text>
|
||||
<text x="330" y="220" fill="#5a625a" font-size="11.5" text-anchor="end">28.266</text>
|
||||
<text x="62" y="250" fill="#8a938a" font-size="12.5">WikiLeaks</text>
|
||||
<text x="330" y="250" fill="#5a625a" font-size="11.5" text-anchor="end">36.182</text>
|
||||
<text x="62" y="280" fill="#8a938a" font-size="12.5">buzón de envíos</text>
|
||||
<text x="330" y="280" fill="#5a625a" font-size="11.5" text-anchor="end">periodistas</text>
|
||||
<rect x="402" y="130" width="320" height="174" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.5" stroke-opacity="0.85"/>
|
||||
<text x="418" y="156" fill="#39ff14" font-size="13" letter-spacing="1.5">WORKERS · coconews/</text>
|
||||
<text x="418" y="190" fill="#8a938a" font-size="12.5">ingestor.py</text>
|
||||
<text x="706" y="190" fill="#5a625a" font-size="11.5" text-anchor="end">cada 15 min</text>
|
||||
<text x="418" y="220" fill="#8a938a" font-size="12.5">translator.py</text>
|
||||
<text x="706" y="220" fill="#5a625a" font-size="11.5" text-anchor="end">→ español</text>
|
||||
<text x="418" y="250" fill="#8a938a" font-size="12.5">ner.py</text>
|
||||
<text x="706" y="250" fill="#5a625a" font-size="11.5" text-anchor="end">spaCy</text>
|
||||
<text x="418" y="280" fill="#8a938a" font-size="12.5">embedder.py</text>
|
||||
<text x="706" y="280" fill="#5a625a" font-size="11.5" text-anchor="end">MiniLM 384d</text>
|
||||
<rect x="778" y="130" width="340" height="234" rx="4" fill="#0d0f0d" stroke="#ffb020" stroke-width="1.5" stroke-opacity="0.85"/>
|
||||
<text x="794" y="156" fill="#ffb020" font-size="13" letter-spacing="1.5">MONGODB · FLUJOS_DATOS</text>
|
||||
<text x="794" y="190" fill="#8a938a" font-size="12.5">noticias</text>
|
||||
<text x="1102" y="190" fill="#5a625a" font-size="11.5" text-anchor="end">98.110</text>
|
||||
<text x="794" y="220" fill="#8a938a" font-size="12.5">wikipedia</text>
|
||||
<text x="1102" y="220" fill="#5a625a" font-size="11.5" text-anchor="end">28.266</text>
|
||||
<text x="794" y="250" fill="#8a938a" font-size="12.5">torrents</text>
|
||||
<text x="1102" y="250" fill="#5a625a" font-size="11.5" text-anchor="end">36.182</text>
|
||||
<text x="794" y="280" fill="#8a938a" font-size="12.5">embeddings_chunks</text>
|
||||
<text x="1102" y="280" fill="#5a625a" font-size="11.5" text-anchor="end">531.306</text>
|
||||
<text x="794" y="310" fill="#8a938a" font-size="12.5">coconews_entidades</text>
|
||||
<text x="1102" y="310" fill="#5a625a" font-size="11.5" text-anchor="end">76.857</text>
|
||||
<text x="794" y="340" fill="#8a938a" font-size="12.5">comparaciones</text>
|
||||
<text x="1102" y="340" fill="#5a625a" font-size="11.5" text-anchor="end">115M</text>
|
||||
<rect x="1154" y="130" width="300" height="204" rx="4" fill="#0d0f0d" stroke="#ff5ea8" stroke-width="1.5" stroke-opacity="0.85"/>
|
||||
<text x="1170" y="156" fill="#ff5ea8" font-size="13" letter-spacing="1.5">APIs</text>
|
||||
<text x="1170" y="190" fill="#8a938a" font-size="12.5">FLUJOS_APP.js</text>
|
||||
<text x="1438" y="190" fill="#5a625a" font-size="11.5" text-anchor="end">:3000</text>
|
||||
<text x="1170" y="220" fill="#8a938a" font-size="12.5"> /api/data</text>
|
||||
<text x="1438" y="220" fill="#5a625a" font-size="11.5" text-anchor="end">grafo</text>
|
||||
<text x="1170" y="250" fill="#8a938a" font-size="12.5"> /api/stats</text>
|
||||
<text x="1438" y="250" fill="#5a625a" font-size="11.5" text-anchor="end">métricas</text>
|
||||
<text x="1170" y="280" fill="#8a938a" font-size="12.5"> /api/submit</text>
|
||||
<text x="1438" y="280" fill="#5a625a" font-size="11.5" text-anchor="end">buzón</text>
|
||||
<text x="1170" y="310" fill="#8a938a" font-size="12.5">coconews server.js</text>
|
||||
<text x="1438" y="310" fill="#5a625a" font-size="11.5" text-anchor="end">:3100</text>
|
||||
<line x1="352" y1="200" x2="392" y2="200" stroke="#3ad6ff" stroke-width="1.6" stroke-opacity="0.65" marker-end="url(#a2)"/>
|
||||
<line x1="728" y1="200" x2="768" y2="200" stroke="#39ff14" stroke-width="1.6" stroke-opacity="0.65" marker-end="url(#a1)"/>
|
||||
<line x1="1124" y1="200" x2="1144" y2="200" stroke="#ffb020" stroke-width="1.6" stroke-opacity="0.65" marker-end="url(#a3)"/>
|
||||
<rect x="46" y="420" width="1072" height="150" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.5"/>
|
||||
<text x="64" y="448" fill="#39ff14" font-size="13" letter-spacing="1.5">CORRELACIÓN SEMÁNTICA · motor_comparacion.py</text>
|
||||
<text x="64" y="476" fill="#7cff5a" font-size="12">1. vectorizar</text>
|
||||
<text x="196" y="476" fill="#8a938a" font-size="12">cada documento y cada fragmento → 384 dimensiones</text>
|
||||
<text x="64" y="500" fill="#7cff5a" font-size="12">2. coseno</text>
|
||||
<text x="196" y="500" fill="#8a938a" font-size="12">noticia contra todo el corpus de leaks, reducido por máximo de fragmento</text>
|
||||
<text x="64" y="524" fill="#7cff5a" font-size="12">3. anti-hub</text>
|
||||
<text x="196" y="524" fill="#8a938a" font-size="12">score = cos − λ·ln(1+grado); descarta los documentos catch-all</text>
|
||||
<text x="64" y="548" fill="#7cff5a" font-size="12">4. arista</text>
|
||||
<text x="196" y="548" fill="#8a938a" font-size="12">si pasa el umbral, se escribe el par en `comparaciones`</text>
|
||||
<rect x="1154" y="420" width="300" height="150" rx="4" fill="#0d0f0d" stroke="#ff5ea8" stroke-width="1.5"/>
|
||||
<text x="1170" y="448" fill="#ff5ea8" font-size="13" letter-spacing="1.5">NAVEGADOR</text>
|
||||
<text x="1170" y="476" fill="#8a938a" font-size="12.5">nginx · upleaks.org</text>
|
||||
<text x="1170" y="500" fill="#8a938a" font-size="12.5">3d-force-graph + Three.js</text>
|
||||
<text x="1170" y="524" fill="#8a938a" font-size="12.5">5 vistas temáticas</text>
|
||||
<text x="1170" y="548" fill="#8a938a" font-size="12.5">semanal / todas</text>
|
||||
<line x1="1124" y1="495" x2="1144" y2="495" stroke="#ff5ea8" stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#a4)"/>
|
||||
<text x="46" y="650" fill="#5a625a" font-size="12">Los workers no se llaman entre sí: cada uno marca su trabajo en el documento (`traducido`, `procesado_ner`, `procesado_embedding`) y el siguiente recoge lo pendiente.</text>
|
||||
<text x="46" y="672" fill="#5a625a" font-size="12">Si uno cae, los demás siguen; al volver, retoma la cola donde estaba. No hay orquestador ni cola de mensajes: el estado vive en la base.</text>
|
||||
<text x="46" y="750" fill="#5a625a" font-size="11">upleaks.org — software libre — gitea.laenre.net/hacklab/FLUJOS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
134
INFO/DOCS/diagramas/flujos-de-informacion.svg
Normal file
134
INFO/DOCS/diagramas/flujos-de-informacion.svg
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 940" width="1500" height="940" font-family="DejaVu Sans Mono, Menlo, Consolas, monospace" role="img" aria-label="Diagrama de los flujos de informacion: de las agencias de noticias a la prensa generalista y la prensa independiente">
|
||||
<defs>
|
||||
<linearGradient id="cuello" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#39ff14" stop-opacity="0.55"/><stop offset="100%" stop-color="#39ff14" stop-opacity="0.06"/></linearGradient>
|
||||
<linearGradient id="indg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#3ad6ff" stop-opacity="0.45"/><stop offset="100%" stop-color="#3ad6ff" stop-opacity="0.06"/></linearGradient>
|
||||
<marker id="fl" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#39ff14" opacity="0.75"/></marker>
|
||||
<marker id="flc" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#3ad6ff" opacity="0.8"/></marker>
|
||||
</defs>
|
||||
<rect width="1500" height="940" fill="#050505"/>
|
||||
<text x="46" y="58" fill="#39ff14" font-size="30" font-weight="bold" letter-spacing="3">DE DÓNDE VIENE LA NOTICIA QUE ACABAS DE LEER</text>
|
||||
<text x="46" y="86" fill="#8a938a" font-size="15">Cuatro agencias abastecen a casi toda la prensa del mundo. Lo que parece pluralidad suele ser el mismo teletipo repetido.</text>
|
||||
<line x1="46" y1="102" x2="1454" y2="102" stroke="#39ff14" stroke-opacity="0.28"/>
|
||||
<text x="46" y="120" fill="#7cff5a" font-size="13" letter-spacing="2">ORIGEN — 4 AGENCIAS</text>
|
||||
<rect x="114" y="132" width="300" height="78" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.6"/>
|
||||
<text x="264" y="166" fill="#39ff14" font-size="23" font-weight="bold" text-anchor="middle">REUTERS</text>
|
||||
<text x="264" y="190" fill="#8a938a" font-size="11.5" text-anchor="middle">Thomson Reuters — Reino Unido/Canadá</text>
|
||||
<rect x="438" y="132" width="300" height="78" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.6"/>
|
||||
<text x="588" y="166" fill="#39ff14" font-size="23" font-weight="bold" text-anchor="middle">AFP</text>
|
||||
<text x="588" y="190" fill="#8a938a" font-size="11.5" text-anchor="middle">Agence France-Presse — Francia</text>
|
||||
<rect x="762" y="132" width="300" height="78" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.6"/>
|
||||
<text x="912" y="166" fill="#39ff14" font-size="23" font-weight="bold" text-anchor="middle">AP</text>
|
||||
<text x="912" y="190" fill="#8a938a" font-size="11.5" text-anchor="middle">Associated Press — EE. UU.</text>
|
||||
<rect x="1086" y="132" width="300" height="78" rx="4" fill="#0d0f0d" stroke="#39ff14" stroke-width="1.6"/>
|
||||
<text x="1236" y="166" fill="#39ff14" font-size="23" font-weight="bold" text-anchor="middle">EFE</text>
|
||||
<text x="1236" y="190" fill="#8a938a" font-size="11.5" text-anchor="middle">Agencia EFE — España</text>
|
||||
<path d="M108,210 L1392,210 L868,302 L632,302 Z" fill="url(#cuello)"/>
|
||||
<line x1="264" y1="210" x2="750" y2="296" stroke="#39ff14" stroke-opacity="0.5" stroke-width="1.2" marker-end="url(#fl)"/>
|
||||
<line x1="588" y1="210" x2="750" y2="296" stroke="#39ff14" stroke-opacity="0.5" stroke-width="1.2" marker-end="url(#fl)"/>
|
||||
<line x1="912" y1="210" x2="750" y2="296" stroke="#39ff14" stroke-opacity="0.5" stroke-width="1.2" marker-end="url(#fl)"/>
|
||||
<line x1="1236" y1="210" x2="750" y2="296" stroke="#39ff14" stroke-opacity="0.5" stroke-width="1.2" marker-end="url(#fl)"/>
|
||||
<text x="750" y="328" fill="#39ff14" font-size="15" font-weight="bold" text-anchor="middle" letter-spacing="1">EL MISMO TELETIPO</text>
|
||||
<text x="750" y="348" fill="#8a938a" font-size="12" text-anchor="middle">un despacho, un ángulo, un titular</text>
|
||||
<rect x="51" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="110" y="441" fill="#8a938a" font-size="12" text-anchor="middle">BBC</text>
|
||||
<line x1="750" y1="358" x2="110" y2="413" stroke="#39ff14" stroke-opacity="0.22" stroke-width="0.9"/>
|
||||
<rect x="179" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="238" y="441" fill="#8a938a" font-size="12" text-anchor="middle">The Guardian</text>
|
||||
<rect x="307" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="366" y="441" fill="#8a938a" font-size="12" text-anchor="middle">CNN</text>
|
||||
<rect x="435" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="494" y="441" fill="#8a938a" font-size="12" text-anchor="middle">El País</text>
|
||||
<line x1="750" y1="358" x2="494" y2="413" stroke="#39ff14" stroke-opacity="0.22" stroke-width="0.9"/>
|
||||
<rect x="563" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="622" y="441" fill="#8a938a" font-size="12" text-anchor="middle">Le Monde</text>
|
||||
<rect x="691" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="750" y="441" fill="#8a938a" font-size="12" text-anchor="middle">Der Spiegel</text>
|
||||
<rect x="819" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="878" y="441" fill="#8a938a" font-size="12" text-anchor="middle">La Vanguardia</text>
|
||||
<rect x="947" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1006" y="441" fill="#8a938a" font-size="12" text-anchor="middle">The Times</text>
|
||||
<line x1="750" y1="358" x2="1006" y2="413" stroke="#39ff14" stroke-opacity="0.22" stroke-width="0.9"/>
|
||||
<rect x="1075" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1134" y="441" fill="#8a938a" font-size="12" text-anchor="middle">ABC</text>
|
||||
<rect x="1203" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1262" y="441" fill="#8a938a" font-size="12" text-anchor="middle">Corriere</text>
|
||||
<rect x="1331" y="416" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1390" y="441" fill="#8a938a" font-size="12" text-anchor="middle">El Mundo</text>
|
||||
<line x1="750" y1="358" x2="1390" y2="413" stroke="#39ff14" stroke-opacity="0.22" stroke-width="0.9"/>
|
||||
<rect x="51" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="110" y="491" fill="#8a938a" font-size="12" text-anchor="middle">FAZ</text>
|
||||
<rect x="179" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="238" y="491" fill="#8a938a" font-size="12" text-anchor="middle">NYT</text>
|
||||
<rect x="307" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="366" y="491" fill="#8a938a" font-size="12" text-anchor="middle">WaPo</text>
|
||||
<rect x="435" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="494" y="491" fill="#8a938a" font-size="12" text-anchor="middle">Sky News</text>
|
||||
<rect x="563" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="622" y="491" fill="#8a938a" font-size="12" text-anchor="middle">RTVE</text>
|
||||
<rect x="691" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="750" y="491" fill="#8a938a" font-size="12" text-anchor="middle">20minutos</text>
|
||||
<rect x="819" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="878" y="491" fill="#8a938a" font-size="12" text-anchor="middle">Antena 3</text>
|
||||
<rect x="947" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1006" y="491" fill="#8a938a" font-size="12" text-anchor="middle">La Sexta</text>
|
||||
<rect x="1075" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1134" y="491" fill="#8a938a" font-size="12" text-anchor="middle">Yahoo News</text>
|
||||
<rect x="1203" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1262" y="491" fill="#8a938a" font-size="12" text-anchor="middle">MSN</text>
|
||||
<rect x="1331" y="466" width="118" height="40" rx="3" fill="#101310" stroke="#5a625a" stroke-width="1"/>
|
||||
<text x="1390" y="491" fill="#8a938a" font-size="12" text-anchor="middle">Google News</text>
|
||||
<rect x="40" y="376" width="439" height="26" fill="#050505"/>
|
||||
<text x="46" y="395" fill="#7cff5a" font-size="13" letter-spacing="2">REPLICACIÓN — CIENTOS DE CABECERAS, UNA SOLA FUENTE</text>
|
||||
<text x="750" y="538" fill="#5a625a" font-size="12" text-anchor="middle">… y varios miles más. Cambia el logotipo, no el contenido.</text>
|
||||
<line x1="46" y1="562" x2="1454" y2="562" stroke="#39ff14" stroke-opacity="0.22" stroke-dasharray="5 6"/>
|
||||
<text x="46" y="596" fill="#3ad6ff" font-size="13" letter-spacing="2">EL OTRO CIRCUITO — PRENSA INDEPENDIENTE</text>
|
||||
<text x="46" y="616" fill="#8a938a" font-size="12.5">Menos cabeceras, pero cada una con fuente propia. No replican: producen.</text>
|
||||
<rect x="46" y="638" width="290" height="148" rx="4" fill="#0d0f0d" stroke="#3ad6ff" stroke-width="1.3" stroke-opacity="0.75"/>
|
||||
<text x="62" y="660" fill="#3ad6ff" font-size="12" letter-spacing="1">FUENTES PROPIAS</text>
|
||||
<text x="62" y="682" fill="#8a938a" font-size="12">› documentos filtrados</text>
|
||||
<text x="62" y="706" fill="#8a938a" font-size="12">› peticiones FOIA</text>
|
||||
<text x="62" y="730" fill="#8a938a" font-size="12">› reporteo sobre el terreno</text>
|
||||
<text x="62" y="754" fill="#8a938a" font-size="12">› archivos judiciales</text>
|
||||
<text x="62" y="778" fill="#8a938a" font-size="12">› OSINT y satélite</text>
|
||||
<rect x="376" y="638" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="390" y="660" fill="#3ad6ff" font-size="13.5">Bellingcat</text>
|
||||
<text x="390" y="677" fill="#5a625a" font-size="11">OSINT</text>
|
||||
<line x1="336" y1="696" x2="372" y2="664" stroke="#3ad6ff" stroke-opacity="0.45" stroke-width="1.2" marker-end="url(#flc)"/>
|
||||
<rect x="620" y="638" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="634" y="660" fill="#3ad6ff" font-size="13.5">The Intercept</text>
|
||||
<text x="634" y="677" fill="#5a625a" font-size="11">filtraciones</text>
|
||||
<rect x="864" y="638" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="878" y="660" fill="#3ad6ff" font-size="13.5">Declassified UK</text>
|
||||
<text x="878" y="677" fill="#5a625a" font-size="11">FOIA</text>
|
||||
<rect x="1108" y="638" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="1122" y="660" fill="#3ad6ff" font-size="13.5">Meduza</text>
|
||||
<text x="1122" y="677" fill="#5a625a" font-size="11">exilio</text>
|
||||
<rect x="376" y="702" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="390" y="724" fill="#3ad6ff" font-size="13.5">Amu TV / 8am</text>
|
||||
<text x="390" y="741" fill="#5a625a" font-size="11">exilio afgano</text>
|
||||
<line x1="336" y1="696" x2="372" y2="728" stroke="#3ad6ff" stroke-opacity="0.45" stroke-width="1.2" marker-end="url(#flc)"/>
|
||||
<rect x="620" y="702" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="634" y="724" fill="#3ad6ff" font-size="13.5">Dabanga</text>
|
||||
<text x="634" y="741" fill="#5a625a" font-size="11">Sudán</text>
|
||||
<rect x="864" y="702" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="878" y="724" fill="#3ad6ff" font-size="13.5">elDiario.es</text>
|
||||
<text x="878" y="741" fill="#5a625a" font-size="11">socios</text>
|
||||
<rect x="1108" y="702" width="232" height="52" rx="3" fill="#0a1114" stroke="#3ad6ff" stroke-width="1.1" stroke-opacity="0.6"/>
|
||||
<text x="1122" y="724" fill="#3ad6ff" font-size="13.5">CTXT</text>
|
||||
<text x="1122" y="741" fill="#5a625a" font-size="11">lectores</text>
|
||||
<rect x="46" y="800" width="1408" height="108" rx="5" fill="#0d0f0d" stroke="#39ff14" stroke-width="2"/>
|
||||
<text x="72" y="838" fill="#39ff14" font-size="25" font-weight="bold" letter-spacing="3">UP-LEAKS</text>
|
||||
<text x="72" y="862" fill="#8a938a" font-size="12.5">Ingiere los dos circuitos y los cruza contra el archivo de WikiLeaks.</text>
|
||||
<text x="72" y="882" fill="#8a938a" font-size="12.5">Donde una noticia y un documento filtrado hablan de lo mismo, aparece una arista.</text>
|
||||
<text x="940" y="846" fill="#39ff14" font-size="26" font-weight="bold">236</text>
|
||||
<text x="940" y="868" fill="#5a625a" font-size="11.5" letter-spacing="1">feeds</text>
|
||||
<text x="1112" y="846" fill="#39ff14" font-size="26" font-weight="bold">36.182</text>
|
||||
<text x="1112" y="868" fill="#5a625a" font-size="11.5" letter-spacing="1">documentos</text>
|
||||
<text x="1284" y="846" fill="#39ff14" font-size="26" font-weight="bold">115M</text>
|
||||
<text x="1284" y="868" fill="#5a625a" font-size="11.5" letter-spacing="1">aristas</text>
|
||||
<path d="M1400,546 L1400,786" fill="none" stroke="#39ff14" stroke-opacity="0.45" stroke-width="1.6" stroke-dasharray="4 4" marker-end="url(#fl)"/>
|
||||
<text x="1408" y="658" fill="#5a625a" font-size="11">generalista</text>
|
||||
<path d="M496,770 L496,786" fill="none" stroke="#3ad6ff" stroke-opacity="0.6" stroke-width="1.6" marker-end="url(#flc)"/>
|
||||
<text x="508" y="780" fill="#5a625a" font-size="11">independiente</text>
|
||||
<text x="46" y="920" fill="#5a625a" font-size="11">upleaks.org — software libre — gitea.laenre.net/hacklab/FLUJOS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
129
INFO/DOCS/diagramas/gen_arquitectura_svg.py
Normal file
129
INFO/DOCS/diagramas/gen_arquitectura_svg.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env python3
|
||||
# Genera el diagrama de arquitectura de UP-LEAKS / FLUJOS.
|
||||
import io, sys
|
||||
|
||||
W, H = 1500, 770
|
||||
NEON, NEON2 = "#39ff14", "#7cff5a"
|
||||
BG, PANEL = "#050505", "#0d0f0d"
|
||||
GRIS, GRIS2 = "#8a938a", "#5a625a"
|
||||
CIAN, AMBAR, MAGENTA = "#3ad6ff", "#ffb020", "#ff5ea8"
|
||||
|
||||
o = []
|
||||
a = o.append
|
||||
|
||||
a(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}" '
|
||||
f'font-family="DejaVu Sans Mono, Menlo, Consolas, monospace" role="img" '
|
||||
f'aria-label="Arquitectura de UP-LEAKS: fuentes, workers, base de datos, correlacion, API y visualizacion">')
|
||||
a('<defs>')
|
||||
for nom, col in (("a1", NEON), ("a2", CIAN), ("a3", AMBAR), ("a4", MAGENTA)):
|
||||
a(f'<marker id="{nom}" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">'
|
||||
f'<path d="M0,0 L7,3 L0,6 Z" fill="{col}" opacity="0.85"/></marker>')
|
||||
a('</defs>')
|
||||
a(f'<rect width="{W}" height="{H}" fill="{BG}"/>')
|
||||
|
||||
a(f'<text x="46" y="54" fill="{NEON}" font-size="27" font-weight="bold" letter-spacing="3">ARQUITECTURA</text>')
|
||||
a(f'<text x="46" y="80" fill="{GRIS}" font-size="14">'
|
||||
f'De la fuente al grafo. Cada bloque es un proceso independiente; se comunican solo por MongoDB.</text>')
|
||||
a(f'<line x1="46" y1="96" x2="{W-46}" y2="96" stroke="{NEON}" stroke-opacity="0.28"/>')
|
||||
|
||||
|
||||
def columna(x, y, w, titulo, color, items, alto_item=30, cab=40):
|
||||
h = cab + len(items) * alto_item + 14
|
||||
a(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="4" fill="{PANEL}" '
|
||||
f'stroke="{color}" stroke-width="1.5" stroke-opacity="0.85"/>')
|
||||
a(f'<text x="{x+16}" y="{y+26}" fill="{color}" font-size="13" letter-spacing="1.5">{titulo}</text>')
|
||||
for i, it in enumerate(items):
|
||||
yy = y + cab + 20 + i * alto_item
|
||||
if isinstance(it, tuple):
|
||||
a(f'<text x="{x+16}" y="{yy}" fill="{GRIS}" font-size="12.5">{it[0]}</text>')
|
||||
a(f'<text x="{x+w-16}" y="{yy}" fill="{GRIS2}" font-size="11.5" text-anchor="end">{it[1]}</text>')
|
||||
else:
|
||||
a(f'<text x="{x+16}" y="{yy}" fill="{GRIS}" font-size="12.5">{it}</text>')
|
||||
return h
|
||||
|
||||
|
||||
Y = 130
|
||||
X1, W1 = 46, 300
|
||||
X2, W2 = 402, 320
|
||||
X3, W3 = 778, 340
|
||||
X4, W4 = 1154, 300
|
||||
|
||||
h1 = columna(X1, Y, W1, "FUENTES", CIAN, [
|
||||
("236 feeds RSS", "5 idiomas"),
|
||||
("API de Wikipedia", "28.266"),
|
||||
("WikiLeaks", "36.182"),
|
||||
("buzón de envíos", "periodistas"),
|
||||
])
|
||||
|
||||
h2 = columna(X2, Y, W2, "WORKERS · coconews/", NEON, [
|
||||
("ingestor.py", "cada 15 min"),
|
||||
("translator.py", "→ español"),
|
||||
("ner.py", "spaCy"),
|
||||
("embedder.py", "MiniLM 384d"),
|
||||
])
|
||||
|
||||
h3 = columna(X3, Y, W3, "MONGODB · FLUJOS_DATOS", AMBAR, [
|
||||
("noticias", "98.110"),
|
||||
("wikipedia", "28.266"),
|
||||
("torrents", "36.182"),
|
||||
("embeddings_chunks", "531.306"),
|
||||
("coconews_entidades", "76.857"),
|
||||
("comparaciones", "115M"),
|
||||
])
|
||||
|
||||
h4 = columna(X4, Y, W4, "APIs", MAGENTA, [
|
||||
("FLUJOS_APP.js", ":3000"),
|
||||
(" /api/data", "grafo"),
|
||||
(" /api/stats", "métricas"),
|
||||
(" /api/submit", "buzón"),
|
||||
("coconews server.js", ":3100"),
|
||||
])
|
||||
|
||||
ymid = Y + 70
|
||||
for (xa, wa, xb, col, mk) in ((X1, W1, X2, CIAN, "a2"), (X2, W2, X3, NEON, "a1"), (X3, W3, X4, AMBAR, "a3")):
|
||||
a(f'<line x1="{xa+wa+6}" y1="{ymid}" x2="{xb-10}" y2="{ymid}" stroke="{col}" '
|
||||
f'stroke-width="1.6" stroke-opacity="0.65" marker-end="url(#{mk})"/>')
|
||||
|
||||
# ── correlacion ───────────────────────────────────────────────────────────
|
||||
Y2 = Y + max(h1, h2, h3, h4) + 56
|
||||
a(f'<rect x="{X1}" y="{Y2}" width="{X3+W3-X1}" height="150" rx="4" fill="{PANEL}" '
|
||||
f'stroke="{NEON}" stroke-width="1.5"/>')
|
||||
a(f'<text x="{X1+18}" y="{Y2+28}" fill="{NEON}" font-size="13" letter-spacing="1.5">'
|
||||
f'CORRELACIÓN SEMÁNTICA · motor_comparacion.py</text>')
|
||||
|
||||
pasos = [
|
||||
("1. vectorizar", "cada documento y cada fragmento → 384 dimensiones"),
|
||||
("2. coseno", "noticia contra todo el corpus de leaks, reducido por máximo de fragmento"),
|
||||
("3. anti-hub", "score = cos − λ·ln(1+grado); descarta los documentos catch-all"),
|
||||
("4. arista", "si pasa el umbral, se escribe el par en `comparaciones`"),
|
||||
]
|
||||
for i, (t, d) in enumerate(pasos):
|
||||
yy = Y2 + 56 + i * 24
|
||||
a(f'<text x="{X1+18}" y="{yy}" fill="{NEON2}" font-size="12">{t}</text>')
|
||||
a(f'<text x="{X1+150}" y="{yy}" fill="{GRIS}" font-size="12">{d}</text>')
|
||||
|
||||
# ── navegador ─────────────────────────────────────────────────────────────
|
||||
a(f'<rect x="{X4}" y="{Y2}" width="{W4}" height="150" rx="4" fill="{PANEL}" '
|
||||
f'stroke="{MAGENTA}" stroke-width="1.5"/>')
|
||||
a(f'<text x="{X4+16}" y="{Y2+28}" fill="{MAGENTA}" font-size="13" letter-spacing="1.5">NAVEGADOR</text>')
|
||||
for i, t in enumerate(["nginx · upleaks.org", "3d-force-graph + Three.js",
|
||||
"5 vistas temáticas", "semanal / todas"]):
|
||||
a(f'<text x="{X4+16}" y="{Y2+56+i*24}" fill="{GRIS}" font-size="12.5">{t}</text>')
|
||||
|
||||
a(f'<line x1="{X3+W3+6}" y1="{Y2+75}" x2="{X4-10}" y2="{Y2+75}" stroke="{MAGENTA}" '
|
||||
f'stroke-width="1.6" stroke-opacity="0.6" marker-end="url(#a4)"/>')
|
||||
|
||||
# ── nota al pie ───────────────────────────────────────────────────────────
|
||||
Y3 = Y2 + 150 + 62
|
||||
a(f'<text x="{X1}" y="{Y3+18}" fill="{GRIS2}" font-size="12">'
|
||||
f'Los workers no se llaman entre sí: cada uno marca su trabajo en el documento '
|
||||
f'(`traducido`, `procesado_ner`, `procesado_embedding`) y el siguiente recoge lo pendiente.</text>')
|
||||
a(f'<text x="{X1}" y="{Y3+40}" fill="{GRIS2}" font-size="12">'
|
||||
f'Si uno cae, los demás siguen; al volver, retoma la cola donde estaba. No hay orquestador '
|
||||
f'ni cola de mensajes: el estado vive en la base.</text>')
|
||||
a(f'<text x="46" y="{H-20}" fill="{GRIS2}" font-size="11">'
|
||||
f'upleaks.org — software libre — gitea.laenre.net/hacklab/FLUJOS</text>')
|
||||
a('</svg>')
|
||||
|
||||
io.open(sys.argv[1], 'w', encoding='utf-8').write('\n'.join(o))
|
||||
print("escrito", sys.argv[1], "| alto util:", Y3 + 40)
|
||||
196
INFO/DOCS/diagramas/gen_flujos_svg.py
Normal file
196
INFO/DOCS/diagramas/gen_flujos_svg.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/env python3
|
||||
# Genera el diagrama grande de flujos de informacion (UP-LEAKS).
|
||||
import io, math, sys
|
||||
|
||||
W, H = 1500, 940
|
||||
NEON, NEON2 = "#39ff14", "#7cff5a"
|
||||
BG, PANEL = "#050505", "#0d0f0d"
|
||||
GRIS, GRIS2 = "#8a938a", "#5a625a"
|
||||
AMBAR = "#ffb020"
|
||||
CIAN = "#3ad6ff"
|
||||
|
||||
AGENCIAS = [
|
||||
("REUTERS", "Thomson Reuters — Reino Unido/Canadá"),
|
||||
("AFP", "Agence France-Presse — Francia"),
|
||||
("AP", "Associated Press — EE. UU."),
|
||||
("EFE", "Agencia EFE — España"),
|
||||
]
|
||||
|
||||
# Cabeceras generalistas: todas beben de los mismos teletipos
|
||||
CABECERAS = [
|
||||
"BBC", "The Guardian", "CNN", "El País", "Le Monde", "Der Spiegel",
|
||||
"La Vanguardia", "The Times", "ABC", "Corriere", "El Mundo", "FAZ",
|
||||
"NYT", "WaPo", "Sky News", "RTVE", "20minutos", "Antena 3",
|
||||
"La Sexta", "Yahoo News", "MSN", "Google News",
|
||||
]
|
||||
|
||||
# Prensa independiente: circuito corto, fuentes propias
|
||||
INDEPENDIENTES = [
|
||||
("Bellingcat", "OSINT"),
|
||||
("The Intercept", "filtraciones"),
|
||||
("Declassified UK", "FOIA"),
|
||||
("Meduza", "exilio"),
|
||||
("Amu TV / 8am", "exilio afgano"),
|
||||
("Dabanga", "Sudán"),
|
||||
("elDiario.es", "socios"),
|
||||
("CTXT", "lectores"),
|
||||
]
|
||||
|
||||
FUENTES_PROPIAS = [
|
||||
"documentos filtrados", "peticiones FOIA", "reporteo sobre el terreno",
|
||||
"archivos judiciales", "OSINT y satélite",
|
||||
]
|
||||
|
||||
o = []
|
||||
a = o.append
|
||||
|
||||
a(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}" '
|
||||
f'font-family="DejaVu Sans Mono, Menlo, Consolas, monospace" role="img" '
|
||||
f'aria-label="Diagrama de los flujos de informacion: de las agencias de noticias a la prensa generalista y la prensa independiente">')
|
||||
|
||||
a('<defs>')
|
||||
a(f'<linearGradient id="cuello" x1="0" y1="0" x2="0" y2="1">'
|
||||
f'<stop offset="0%" stop-color="{NEON}" stop-opacity="0.55"/>'
|
||||
f'<stop offset="100%" stop-color="{NEON}" stop-opacity="0.06"/></linearGradient>')
|
||||
a(f'<linearGradient id="indg" x1="0" y1="0" x2="0" y2="1">'
|
||||
f'<stop offset="0%" stop-color="{CIAN}" stop-opacity="0.45"/>'
|
||||
f'<stop offset="100%" stop-color="{CIAN}" stop-opacity="0.06"/></linearGradient>')
|
||||
a(f'<marker id="fl" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">'
|
||||
f'<path d="M0,0 L7,3 L0,6 Z" fill="{NEON}" opacity="0.75"/></marker>')
|
||||
a(f'<marker id="flc" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">'
|
||||
f'<path d="M0,0 L7,3 L0,6 Z" fill="{CIAN}" opacity="0.8"/></marker>')
|
||||
a('</defs>')
|
||||
|
||||
a(f'<rect width="{W}" height="{H}" fill="{BG}"/>')
|
||||
|
||||
# ── titulo ────────────────────────────────────────────────────────────────
|
||||
a(f'<text x="46" y="58" fill="{NEON}" font-size="30" font-weight="bold" letter-spacing="3">'
|
||||
f'DE DÓNDE VIENE LA NOTICIA QUE ACABAS DE LEER</text>')
|
||||
a(f'<text x="46" y="86" fill="{GRIS}" font-size="15">'
|
||||
f'Cuatro agencias abastecen a casi toda la prensa del mundo. Lo que parece pluralidad '
|
||||
f'suele ser el mismo teletipo repetido.</text>')
|
||||
a(f'<line x1="46" y1="102" x2="{W-46}" y2="102" stroke="{NEON}" stroke-opacity="0.28"/>')
|
||||
|
||||
# ── capa 1: agencias ──────────────────────────────────────────────────────
|
||||
y_ag, h_ag = 132, 78
|
||||
ancho = 300
|
||||
x0 = (W - (len(AGENCIAS) * ancho + (len(AGENCIAS) - 1) * 24)) / 2
|
||||
a(f'<text x="46" y="{y_ag-12}" fill="{NEON2}" font-size="13" letter-spacing="2">'
|
||||
f'ORIGEN — 4 AGENCIAS</text>')
|
||||
centros_ag = []
|
||||
for i, (nom, sub) in enumerate(AGENCIAS):
|
||||
x = x0 + i * (ancho + 24)
|
||||
a(f'<rect x="{x:.0f}" y="{y_ag}" width="{ancho}" height="{h_ag}" rx="4" '
|
||||
f'fill="{PANEL}" stroke="{NEON}" stroke-width="1.6"/>')
|
||||
a(f'<text x="{x+ancho/2:.0f}" y="{y_ag+34}" fill="{NEON}" font-size="23" font-weight="bold" '
|
||||
f'text-anchor="middle">{nom}</text>')
|
||||
a(f'<text x="{x+ancho/2:.0f}" y="{y_ag+58}" fill="{GRIS}" font-size="11.5" '
|
||||
f'text-anchor="middle">{sub}</text>')
|
||||
centros_ag.append(x + ancho / 2)
|
||||
|
||||
# ── cuello de botella ─────────────────────────────────────────────────────
|
||||
y_c1, y_c2 = y_ag + h_ag, y_ag + h_ag + 92
|
||||
cx = W / 2
|
||||
a(f'<path d="M{x0-6:.0f},{y_c1} L{W-x0+6:.0f},{y_c1} L{cx+118:.0f},{y_c2} L{cx-118:.0f},{y_c2} Z" '
|
||||
f'fill="url(#cuello)"/>')
|
||||
for c in centros_ag:
|
||||
a(f'<line x1="{c:.0f}" y1="{y_c1}" x2="{cx:.0f}" y2="{y_c2-6}" stroke="{NEON}" '
|
||||
f'stroke-opacity="0.5" stroke-width="1.2" marker-end="url(#fl)"/>')
|
||||
a(f'<text x="{cx:.0f}" y="{y_c2+26}" fill="{NEON}" font-size="15" font-weight="bold" '
|
||||
f'text-anchor="middle" letter-spacing="1">EL MISMO TELETIPO</text>')
|
||||
a(f'<text x="{cx:.0f}" y="{y_c2+46}" fill="{GRIS}" font-size="12" text-anchor="middle">'
|
||||
f'un despacho, un ángulo, un titular</text>')
|
||||
|
||||
# ── capa 2: cabeceras generalistas ────────────────────────────────────────
|
||||
y_ab = y_c2 + 104
|
||||
_etq = 'REPLICACIÓN — CIENTOS DE CABECERAS, UNA SOLA FUENTE'
|
||||
_lbl = [] # se pinta DESPUES del abanico, con fondo, para que no lo crucen lineas
|
||||
cols, cw, ch, gap = 11, 118, 40, 10
|
||||
gw = cols * cw + (cols - 1) * gap
|
||||
gx = (W - gw) / 2
|
||||
for i, nom in enumerate(CABECERAS):
|
||||
r, c = divmod(i, cols)
|
||||
x = gx + c * (cw + gap)
|
||||
y = y_ab + 10 + r * (ch + gap)
|
||||
a(f'<rect x="{x:.0f}" y="{y:.0f}" width="{cw}" height="{ch}" rx="3" fill="#101310" '
|
||||
f'stroke="{GRIS2}" stroke-width="1"/>')
|
||||
a(f'<text x="{x+cw/2:.0f}" y="{y+25:.0f}" fill="{GRIS}" font-size="12" '
|
||||
f'text-anchor="middle">{nom}</text>')
|
||||
if r == 0 and c in (0, 3, 7, 10):
|
||||
a(f'<line x1="{cx:.0f}" y1="{y_c2+56}" x2="{x+cw/2:.0f}" y2="{y-3:.0f}" '
|
||||
f'stroke="{NEON}" stroke-opacity="0.22" stroke-width="0.9"/>')
|
||||
a(f'<rect x="40" y="{y_ab-30}" width="{len(_etq)*8.6:.0f}" height="26" fill="{BG}"/>')
|
||||
a(f'<text x="46" y="{y_ab-11}" fill="{NEON2}" font-size="13" letter-spacing="2">{_etq}</text>')
|
||||
filas = math.ceil(len(CABECERAS) / cols)
|
||||
y_ab_fin = y_ab + 10 + filas * (ch + gap)
|
||||
a(f'<text x="{cx:.0f}" y="{y_ab_fin+22:.0f}" fill="{GRIS2}" font-size="12" text-anchor="middle">'
|
||||
f'… y varios miles más. Cambia el logotipo, no el contenido.</text>')
|
||||
|
||||
# ── separador ─────────────────────────────────────────────────────────────
|
||||
y_sep = y_ab_fin + 46
|
||||
a(f'<line x1="46" y1="{y_sep}" x2="{W-46}" y2="{y_sep}" stroke="{NEON}" '
|
||||
f'stroke-opacity="0.22" stroke-dasharray="5 6"/>')
|
||||
|
||||
# ── capa 3: prensa independiente ──────────────────────────────────────────
|
||||
y_in = y_sep + 34
|
||||
a(f'<text x="46" y="{y_in}" fill="{CIAN}" font-size="13" letter-spacing="2">'
|
||||
f'EL OTRO CIRCUITO — PRENSA INDEPENDIENTE</text>')
|
||||
a(f'<text x="46" y="{y_in+20}" fill="{GRIS}" font-size="12.5">'
|
||||
f'Menos cabeceras, pero cada una con fuente propia. No replican: producen.</text>')
|
||||
|
||||
fx, fy = 46, y_in + 42
|
||||
a(f'<rect x="{fx}" y="{fy}" width="290" height="{len(FUENTES_PROPIAS)*24+28}" rx="4" '
|
||||
f'fill="{PANEL}" stroke="{CIAN}" stroke-width="1.3" stroke-opacity="0.75"/>')
|
||||
a(f'<text x="{fx+16}" y="{fy+22}" fill="{CIAN}" font-size="12" letter-spacing="1">FUENTES PROPIAS</text>')
|
||||
for i, f in enumerate(FUENTES_PROPIAS):
|
||||
a(f'<text x="{fx+16}" y="{fy+44+i*24}" fill="{GRIS}" font-size="12">› {f}</text>')
|
||||
|
||||
icw, ich, igap = 232, 52, 12
|
||||
igx = fx + 330
|
||||
for i, (nom, sub) in enumerate(INDEPENDIENTES):
|
||||
r, c = divmod(i, 4)
|
||||
x = igx + c * (icw + igap)
|
||||
y = fy + r * (ich + igap)
|
||||
a(f'<rect x="{x:.0f}" y="{y:.0f}" width="{icw}" height="{ich}" rx="3" fill="#0a1114" '
|
||||
f'stroke="{CIAN}" stroke-width="1.1" stroke-opacity="0.6"/>')
|
||||
a(f'<text x="{x+14:.0f}" y="{y+22:.0f}" fill="{CIAN}" font-size="13.5">{nom}</text>')
|
||||
a(f'<text x="{x+14:.0f}" y="{y+39:.0f}" fill="{GRIS2}" font-size="11">{sub}</text>')
|
||||
if c == 0:
|
||||
a(f'<line x1="{fx+290}" y1="{fy+58:.0f}" x2="{x-4:.0f}" y2="{y+ich/2:.0f}" '
|
||||
f'stroke="{CIAN}" stroke-opacity="0.45" stroke-width="1.2" marker-end="url(#flc)"/>')
|
||||
|
||||
y_in_fin = fy + 2 * (ich + igap)
|
||||
|
||||
# ── capa 4: up-leaks ──────────────────────────────────────────────────────
|
||||
y_up = y_in_fin + 34
|
||||
bw, bh = W - 92, 108
|
||||
a(f'<rect x="46" y="{y_up}" width="{bw}" height="{bh}" rx="5" fill="{PANEL}" '
|
||||
f'stroke="{NEON}" stroke-width="2"/>')
|
||||
a(f'<text x="72" y="{y_up+38}" fill="{NEON}" font-size="25" font-weight="bold" '
|
||||
f'letter-spacing="3">UP-LEAKS</text>')
|
||||
a(f'<text x="72" y="{y_up+62}" fill="{GRIS}" font-size="12.5">'
|
||||
f'Ingiere los dos circuitos y los cruza contra el archivo de WikiLeaks.</text>')
|
||||
a(f'<text x="72" y="{y_up+82}" fill="{GRIS}" font-size="12.5">'
|
||||
f'Donde una noticia y un documento filtrado hablan de lo mismo, aparece una arista.</text>')
|
||||
|
||||
for i, (etq, val) in enumerate([("236", "feeds"), ("36.182", "documentos"), ("115M", "aristas")]):
|
||||
x = 940 + i * 172
|
||||
a(f'<text x="{x}" y="{y_up+46}" fill="{NEON}" font-size="26" font-weight="bold">{etq}</text>')
|
||||
a(f'<text x="{x}" y="{y_up+68}" fill="{GRIS2}" font-size="11.5" letter-spacing="1">{val}</text>')
|
||||
|
||||
# flechas de entrada
|
||||
# generalista: baja por la derecha bordeando el bloque independiente
|
||||
a(f'<path d="M{W-100},{y_ab_fin+30:.0f} L{W-100},{y_up-14}" fill="none" stroke="{NEON}" '
|
||||
f'stroke-opacity="0.45" stroke-width="1.6" stroke-dasharray="4 4" marker-end="url(#fl)"/>')
|
||||
a(f'<text x="{W-92}" y="{(y_ab_fin+y_up)/2:.0f}" fill="{GRIS2}" font-size="11">generalista</text>')
|
||||
# independiente: sube desde el bloque cian
|
||||
a(f'<path d="M{igx+120:.0f},{y_in_fin+4:.0f} L{igx+120:.0f},{y_up-14}" fill="none" stroke="{CIAN}" '
|
||||
f'stroke-opacity="0.6" stroke-width="1.6" marker-end="url(#flc)"/>')
|
||||
a(f'<text x="{igx+132:.0f}" y="{y_up-20:.0f}" fill="{GRIS2}" font-size="11">independiente</text>')
|
||||
|
||||
a(f'<text x="46" y="{H-20}" fill="{GRIS2}" font-size="11">'
|
||||
f'upleaks.org — software libre — gitea.laenre.net/hacklab/FLUJOS</text>')
|
||||
a('</svg>')
|
||||
|
||||
io.open(sys.argv[1], 'w', encoding='utf-8').write('\n'.join(o))
|
||||
print("escrito", sys.argv[1], "| alto util:", y_up + bh)
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
|
||||
--- Ver variable de entorno y configuración MongoDB ---
|
||||
|
||||
cat /var/www/theflows.net/flujos/.env
|
||||
cat $FLUJOS/.env
|
||||
|
||||
|
||||
--- Listar colecciones en MongoDB ---
|
||||
|
|
@ -129,33 +129,33 @@
|
|||
--- Contar archivos en disco por sección ---
|
||||
|
||||
# NOTICIAS artículos
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/NOTICIAS/articulos/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/NOTICIAS/articulos/ -type f | wc -l
|
||||
|
||||
# NOTICIAS archivos raw
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/NOTICIAS/archivos/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/NOTICIAS/archivos/ -type f | wc -l
|
||||
|
||||
# WIKIPEDIA artículos
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/WIKIPEDIA/articulos_wikipedia/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/WIKIPEDIA/articulos_wikipedia/ -type f | wc -l
|
||||
|
||||
# WIKIPEDIA tokenizados
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/WIKIPEDIA/articulos_tokenizados/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/WIKIPEDIA/articulos_tokenizados/ -type f | wc -l
|
||||
|
||||
# TORRENTS total
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/TORRENTS/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/TORRENTS/ -type f | wc -l
|
||||
|
||||
# WikiLeaks txt
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/TORRENTS/TORRENTS_WIKILEAKS_COMPLETO/txt/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/TORRENTS/TORRENTS_WIKILEAKS_COMPLETO/txt/ -type f | wc -l
|
||||
|
||||
# WikiLeaks tokenizados
|
||||
find /var/www/theflows.net/flujos/FLUJOS_DATOS/TORRENTS/TORRENTS_WIKILEAKS_COMPLETO/tokenized/ -type f | wc -l
|
||||
find $FLUJOS/FLUJOS_DATOS/TORRENTS/TORRENTS_WIKILEAKS_COMPLETO/tokenized/ -type f | wc -l
|
||||
|
||||
|
||||
--- Tamaño en disco por sección ---
|
||||
|
||||
du -sh /var/www/theflows.net/flujos/FLUJOS_DATOS/NOTICIAS/
|
||||
du -sh /var/www/theflows.net/flujos/FLUJOS_DATOS/WIKIPEDIA/
|
||||
du -sh /var/www/theflows.net/flujos/FLUJOS_DATOS/TORRENTS/
|
||||
du -sh /var/www/theflows.net/flujos/FLUJOS_DATOS/MONGO/
|
||||
du -sh $FLUJOS/FLUJOS_DATOS/NOTICIAS/
|
||||
du -sh $FLUJOS/FLUJOS_DATOS/WIKIPEDIA/
|
||||
du -sh $FLUJOS/FLUJOS_DATOS/TORRENTS/
|
||||
du -sh $FLUJOS/FLUJOS_DATOS/MONGO/
|
||||
|
||||
|
||||
--- Estimación de artículos únicos comparados (cálculo matemático) ---
|
||||
|
|
|
|||
187
README.md
187
README.md
|
|
@ -1,116 +1,141 @@
|
|||
# FLUJOS
|
||||
# FLUJOS · UP-LEAKS
|
||||
|
||||
Software libre para analizar y visualizar flujos de información. Conecta noticias, documentos y eventos históricos para entender la historia como una sucesión de eventos y combatir la desinformación.
|
||||
Cruza lo que publica la prensa con lo que dicen los documentos filtrados, y lo dibuja
|
||||
como un grafo que se puede recorrer.
|
||||
|
||||
---
|
||||
En marcha: **[upleaks.org](https://upleaks.org)**
|
||||
|
||||

|
||||
|
||||
## La idea
|
||||
|
||||
Cuatro agencias —Reuters, AFP, AP, EFE— abastecen a casi toda la prensa del planeta.
|
||||
Abres diez periódicos distintos y lees el mismo despacho diez veces. Esa repetición
|
||||
parece pluralidad y no lo es.
|
||||
|
||||
FLUJOS ingiere los dos circuitos a la vez: el de las cabeceras que replican teletipos y
|
||||
el de la prensa independiente que produce fuente propia. Los cruza contra el archivo de
|
||||
WikiLeaks y levanta una arista cada vez que una noticia y un documento filtrado hablan de
|
||||
lo mismo.
|
||||
|
||||
El resultado no es un buscador. Es un mapa donde se ve qué se cuenta, quién lo cuenta y
|
||||
qué había escrito sobre eso antes de que se contara.
|
||||
|
||||
## Qué hay dentro ahora mismo
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Noticias | 98.110 de 236 feeds en 5 idiomas |
|
||||
| Artículos de Wikipedia | 28.266 |
|
||||
| Documentos de WikiLeaks | 36.182 |
|
||||
| Fragmentos vectorizados | 531.306 |
|
||||
| Aristas del grafo | 115.035.059 |
|
||||
|
||||
## Arquitectura
|
||||
|
||||

|
||||
|
||||
Cinco procesos independientes que no se hablan entre sí. Cada uno marca su trabajo en el
|
||||
documento (`traducido`, `procesado_ner`, `procesado_embedding`) y el siguiente recoge lo
|
||||
que quedó pendiente. Si uno se cae, los demás siguen; cuando vuelve, retoma la cola donde
|
||||
estaba. El estado vive en MongoDB, no en un orquestador.
|
||||
|
||||
- `coconews/workers/ingestor.py` — lee los 236 feeds cada 15 minutos
|
||||
- `coconews/workers/translator.py` — traduce al español
|
||||
- `coconews/workers/ner.py` — extrae entidades con spaCy
|
||||
- `coconews/workers/embedder.py` — vectoriza con MiniLM multilingüe (384 dimensiones)
|
||||
- `FLUJOS_DATOS/COMPARACIONES/motor_comparacion.py` — correlación semántica noticia↔leak
|
||||
|
||||
La documentación técnica de cada pieza está en [`INFO/DOCS/CONTEXT/`](INFO/DOCS/CONTEXT/),
|
||||
y la [wiki](https://gitea.laenre.net/hacklab/FLUJOS/wiki) cuenta el conjunto.
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Node.js >= 18.x
|
||||
- Node.js >= 18
|
||||
- Python >= 3.11
|
||||
- MongoDB >= 6.x
|
||||
|
||||
### Instalar MongoDB (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
sudo apt install -y gnupg curl
|
||||
curl -fsSL https://pgp.mongodb.com/server-6.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg --dearmor
|
||||
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
|
||||
sudo apt update && sudo apt install -y mongodb-org
|
||||
sudo systemctl enable --now mongod
|
||||
```
|
||||
|
||||
---
|
||||
- MongoDB >= 6
|
||||
|
||||
## Instalación
|
||||
|
||||
```bash
|
||||
git clone https://gitea.laenre.net/hacklab/FLUJOS.git
|
||||
cd FLUJOS
|
||||
git checkout production
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### Variables de entorno
|
||||
Todas las rutas se deducen de dónde esté el repo. Las variables de `.env` solo hacen
|
||||
falta si quieres cambiar algo: puertos, la URL de MongoDB o dónde viven los datos.
|
||||
|
||||
```bash
|
||||
cp .env.example FLUJOS/BACK_BACK/.env
|
||||
# API de visualización
|
||||
cd FLUJOS/BACK_BACK && npm install && npm start # :3000
|
||||
|
||||
# API de noticias
|
||||
cd coconews/api && npm install && node server.js # :3100
|
||||
|
||||
# Workers
|
||||
python3 -m venv .venv && . .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python3 -m spacy download es_core_news_sm
|
||||
```
|
||||
|
||||
Edita `.env` con tus valores:
|
||||
Con eso, la interfaz está en `http://localhost:3000`. La base arranca vacía: hay que
|
||||
poblarla.
|
||||
|
||||
| Variable | Por defecto | Descripción |
|
||||
|-------------|-------------------------------|------------------------------|
|
||||
| `MONGO_URL` | `mongodb://localhost:27017` | URL de conexión a MongoDB |
|
||||
| `DB_NAME` | `FLUJOS_DATOS` | Nombre de la base de datos |
|
||||
| `PORT` | `3000` | Puerto del servidor |
|
||||
|
||||
### Dependencias Node.js
|
||||
## Poblar la base
|
||||
|
||||
```bash
|
||||
cd FLUJOS/BACK_BACK && npm install
|
||||
cd ../VISUALIZACION && npm install
|
||||
# Noticias: arranca el ciclo de ingesta y no para
|
||||
python3 coconews/workers/import_feeds.py # carga el catálogo de feeds
|
||||
python3 coconews/workers/ingestor.py
|
||||
|
||||
# Wikipedia y WikiLeaks
|
||||
python3 FLUJOS_DATOS/WIKIPEDIA/main.py
|
||||
python3 FLUJOS_DATOS/TORRENTS/procesar_torrents.py
|
||||
|
||||
# Vectorizar y correlacionar
|
||||
python3 coconews/workers/backfill_embeddings.py
|
||||
python3 FLUJOS_DATOS/COMPARACIONES/motor_comparacion.py --salida comparaciones_v2
|
||||
```
|
||||
|
||||
### Dependencias Python
|
||||
El motor de correlación acepta `--dry-run` para calibrar umbrales antes de escribir nada.
|
||||
|
||||
## Como servicio
|
||||
|
||||
Las unidades de `coconews/systemd/` son plantillas. El script las rellena con tu ruta,
|
||||
tu usuario y tu binario de node:
|
||||
|
||||
```bash
|
||||
cd ../../FLUJOS_DATOS
|
||||
python3 -m venv myenv
|
||||
source myenv/bin/activate
|
||||
pip install transformers==4.10.0 tensorflow==2.17.0 scikit-learn==0.24.2 \
|
||||
pandas==1.3.2 numpy==1.19.5 nltk==3.6.2 django obsei[all]
|
||||
python FLUJOS_DATOS/manage.py migrate
|
||||
sudo coconews/systemd/instalar.sh
|
||||
sudo systemctl enable --now coconews-ingestor coconews-translator \
|
||||
coconews-ner coconews-embedder coconews-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arrancar
|
||||
|
||||
```bash
|
||||
# Servidor principal
|
||||
cd FLUJOS/BACK_BACK && npm start
|
||||
|
||||
# API Python (en otra terminal, con el venv activo)
|
||||
cd FLUJOS/BACK_BACK && python flask_api/app.py
|
||||
```
|
||||
|
||||
Disponible en `http://localhost:3000`
|
||||
|
||||
---
|
||||
|
||||
## Base de datos
|
||||
|
||||
La app lee de MongoDB (`FLUJOS_DATOS`). Las colecciones se pueblan con el pipeline de datos:
|
||||
|
||||
```bash
|
||||
# 1. Scraping
|
||||
python FLUJOS_DATOS/NOTICIAS/main_noticias.py
|
||||
python FLUJOS_DATOS/WIKIPEDIA/main.py
|
||||
python FLUJOS_DATOS/TORRENTS/procesar_torrents.py
|
||||
|
||||
# 2. Comparaciones
|
||||
python FLUJOS_DATOS/COMPARACIONES/pipeline_completo.py
|
||||
```
|
||||
|
||||
Consulta `FLUJOS_DATOS/DOCS/` para más detalle.
|
||||
|
||||
---
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
flujos/
|
||||
FLUJOS/
|
||||
├── FLUJOS/
|
||||
│ ├── BACK_BACK/ # Servidor Express + Flask API + OBSEI
|
||||
│ └── VISUALIZACION/ # Frontend (Three.js, force-graph)
|
||||
└── FLUJOS_DATOS/
|
||||
├── NOTICIAS/ # Scraper de noticias
|
||||
├── WIKIPEDIA/ # Extracción Wikipedia
|
||||
├── TORRENTS/ # Documentos WikiLeaks
|
||||
├── COMPARACIONES/ # Similitud entre documentos
|
||||
└── DOCS/ # Documentación técnica
|
||||
│ ├── BACK_BACK/ API Express que sirve el grafo (:3000)
|
||||
│ └── VISUALIZACION/ frontend, Three.js + 3d-force-graph
|
||||
├── coconews/ ingesta de noticias: workers, API y unidades systemd
|
||||
├── FLUJOS_DATOS/
|
||||
│ ├── NOTICIAS/ scraper histórico de medios
|
||||
│ ├── WIKIPEDIA/ extracción de artículos
|
||||
│ ├── TORRENTS/ documentos de WikiLeaks
|
||||
│ ├── COMPARACIONES/ correlación semántica
|
||||
│ └── IMAGENES/ scraper y análisis de imágenes
|
||||
└── INFO/DOCS/ documentación técnica y diagramas
|
||||
```
|
||||
|
||||
## Envíos
|
||||
|
||||
Hay un buzón para periodistas y filtradores en `/journalist`. Los envíos se guardan fuera
|
||||
del webroot, nunca se sirven como estáticos y no se registra la IP de quien envía.
|
||||
|
||||
---
|
||||
|
||||
*"Los gigantes vistos en perspectiva parecen marionetas."*
|
||||
> No basta con hablar de cambio.
|
||||
> Muéstrame el código.
|
||||
|
||||
Software libre. Ver [`VOICES.md`](VOICES.md).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue