Módulo 4: El modelo de datos del sistema
7. Ingesta RAG idempotente
Descripción
Al terminar esta lección vas a poder aplicar el patrón de deduplicación de este módulo a un terreno nuevo: la ingesta de documentos para RAG. Vas a entender por qué re-embeber un documento que ya procesaste es un problema —cuesta cómputo y ensucia el almacén de vectores con copias— y cómo evitarlo con la misma tienda de dedup que ya conoces, cambiando solo la clave: en vez de un order_id, un hash del contenido del archivo. Vas a ver qué es un embedding y un vector store en términos simples, cómo el Starter Kit te da Qdrant y Ollama para hacer todo esto local y a costo cero, y por qué el hash de contenido (no el nombre del archivo) es la clave correcta para decidir si un documento cambió o es el mismo de siempre.
Esto importa por dos razones. La práctica: embeber documentos cuesta —tiempo de cómputo, y en muchos montajes, dinero por llamada a un modelo—, y hacerlo de nuevo sobre algo que no cambió es puro desperdicio; peor aún, si insertas los mismos fragmentos dos veces en el vector store, tus búsquedas empiezan a devolver duplicados y la calidad de las respuestas cae. La conceptual, que es la más valiosa: esta lección demuestra que lo que aprendiste no es un truco para cobros. Es un patrón general —consulta antes de actuar sobre un efecto costoso— que sirve donde sea que repetir haga daño. El cobro y el embedding son la misma forma con distinta piel.
Conexión con el módulo: esta lección toma la tienda de dedup de la lección 5 y le cambia el dominio. La restricción de unicidad, el ON CONFLICT DO NOTHING, el "actúa solo si insertaste": todo es idéntico. Lo único que cambia es qué va en la columna clave —un hash de contenido en vez de un identificador de pedido— y qué es el "efecto" que proteges —embeber e insertar en Qdrant en vez de crear un cobro—. Usa Qdrant y Ollama, los dos servicios del Starter Kit que conociste en la lección 6. Y cierra el módulo demostrando la generalidad del patrón, justo antes de que la lección 8 lo consolide en un proyecto.
Volver a fotocopiar lo que ya está archivado
Empecemos por la analogía, porque hace evidente el desperdicio.
Imagina que tu trabajo es alimentar un archivero con documentos. Cada documento que llega, lo lees, le sacas una ficha resumen —para poder encontrarlo después por tema— y la guardas en el archivero. Es un trabajo útil: gracias a esas fichas, cualquiera puede buscar "políticas de devolución" y encontrar el documento correcto en segundos.
Ahora imagina que, cada mañana, alguien te entrega la misma pila de documentos de ayer, más un par de nuevos. Si procesas la pila entera sin fijarte, vuelves a leer, resumir y archivar documentos que ya estaban archivados. Dos problemas nacen de ahí. Uno: gastaste horas sacando fichas que ya existían —trabajo tirado a la basura—. Dos, y peor: ahora el archivero tiene dos fichas del mismo documento, y cuando alguien busque "políticas de devolución", le van a salir duplicadas, confundiéndolo sobre cuál es la buena.
La ingesta de RAG es exactamente ese archivero. Cada documento —una hoja de producto de Cumbre, un catálogo de un proveedor, una política de envíos— se procesa para que un agente de soporte pueda buscar en él por significado. Y si reprocesas documentos que ya estaban, gastas cómputo de más y llenas el vector store de fragmentos duplicados que degradan las búsquedas. El trabajo de esta lección es ponerle al archivero un portero que diga: "este documento ya está archivado, sáltatelo; este es nuevo, pásalo". Ese portero es la tienda de dedup del módulo, con una clave distinta.
Qué es embeber y qué es un vector store, en simple
Antes de deduplicar, vale la pena entender qué es el "efecto costoso" que estamos protegiendo, porque probablemente sea nuevo. Vamos con dos definiciones con manzanas.
Un embedding es la traducción del significado de un texto a una lista de números. Un modelo lee un fragmento —"nuestra política permite devoluciones dentro de 30 días"— y produce una lista larga de números (un "vector") que captura de qué trata ese texto. La magia es que textos con significado parecido producen vectores cercanos entre sí, aunque usen palabras distintas: "se puede devolver un producto en el primer mes" caería cerca del anterior, aunque no comparta casi ninguna palabra. Piénsalo como asignarle a cada texto una coordenada en un mapa de significados: los temas parecidos quedan en el mismo barrio, y buscar "cómo devuelvo algo" te lleva al barrio correcto sin necesidad de que coincidan las palabras exactas.
Un vector store es el archivero donde guardas esos vectores para poder buscar por cercanía. Qdrant —uno de los servicios del Starter Kit— es un vector store: almacena los vectores de todos tus fragmentos y, cuando le das el vector de una pregunta, encuentra rápido los fragmentos más cercanos en el mapa de significados. Es lo que permite el RAG: recuperar los pedazos de documento relevantes a una pregunta para dárselos a un modelo de lenguaje que redacta la respuesta.
Quién produce los embeddings, en local, es Ollama. El otro servicio del Starter Kit corre modelos en tu máquina, sin API de pago. Para embeddings hay modelos dedicados; al momento de escribir esta guía —julio de 2026—, dos muy usados en la librería de Ollama son nomic-embed-text (768 dimensiones, buen manejo de textos largos) y mxbai-embed-large (1024 dimensiones). La lista de modelos cambia con el tiempo y hay nuevos con frecuencia, así que verifica la librería de Ollama vigente cuando montes esto; el patrón de dedup que enseñamos no depende de qué modelo elijas.
Con eso claro, el "efecto costoso" que queremos no repetir es: tomar un documento, partirlo en fragmentos, pedirle a Ollama el embedding de cada fragmento, y guardarlos en Qdrant. Repetirlo sobre un documento que no cambió gasta cómputo y duplica fragmentos. La idempotencia es no volver a hacerlo si ya se hizo.
¿Por qué "costoso", si Ollama es local y gratis? Porque "gratis" no es "instantáneo": embeber decenas o cientos de fragmentos toma tiempo de procesador —y si en tu montaje real usaras un modelo de embeddings de pago por API en lugar de Ollama, cada fragmento reprocesado sería, además, dinero—. El patrón que aprendes aquí te protege en los dos escenarios: local, te ahorra tiempo; en la nube, te ahorra la factura. Y en ambos, evita los duplicados en el store, que es un costo de calidad independiente del de cómputo.
La clave correcta: el hash del contenido, no el nombre del archivo
Aquí está la decisión de diseño que hace toda la diferencia, y conecta directo con el falso duplicado de la lección 4: ¿qué clave identifica "este documento ya lo procesé"?
La tentación es usar el nombre del archivo: "si ya vi politica-devoluciones.pdf, sáltalo". Pero eso falla en los dos sentidos. Si el archivo se actualizó —cambió la política, pero el nombre es el mismo—, el nombre te haría saltarlo, y tu vector store quedaría con la versión vieja para siempre: un duplicado que se escapa al revés, un documento que debía reprocesarse y no lo hiciste. Y si el mismo contenido llega con dos nombres distintos, lo procesarías dos veces.
La clave correcta es un hash del contenido del archivo. Un hash es una huella digital: una función que toma el contenido completo y produce una cadena corta y fija que lo representa. Su propiedad clave es que el mismo contenido produce siempre el mismo hash, y el más mínimo cambio en el contenido produce un hash completamente distinto. Piénsalo como la huella dactilar de un documento: si cambias una sola palabra, la huella cambia; si es idéntico byte por byte, la huella es idéntica, sin importar cómo se llame el archivo.
Con el hash de contenido como clave, la deduplicación hace justo lo correcto:
- Documento idéntico al que ya procesaste → mismo hash → ya está en la tienda → sáltalo (no gastes cómputo, no dupliques).
- Documento que cambió (aunque tenga el mismo nombre) → hash distinto → clave nueva → procésalo (embébelo, actualiza el conocimiento).
- Mismo contenido con otro nombre → mismo hash → ya está → sáltalo (no dupliques por un cambio de nombre).
Es la misma lógica de "identifica el trabajo, no el intento" del módulo 2. El "trabajo" aquí es embeber este contenido exacto; el hash de contenido es lo que lo identifica sin ambigüedad.
La tienda de dedup para documentos
La tabla es idéntica en forma a la processed_orders de la lección 5; solo cambian los nombres para reflejar el dominio:
CREATE TABLE IF NOT EXISTS processed_documents (
content_hash TEXT PRIMARY KEY,
source_name TEXT NOT NULL,
processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
content_hash es la clave primaria —única, la cerradura de la taquilla—. source_name guarda el nombre del archivo o su origen, solo para poder mirar la tabla y entender qué es cada fila (igual que order_id en la tienda de pedidos: no es la clave, es la etiqueta legible). Y processed_at marca cuándo se ingirió.
La compuerta es, palabra por palabra, la misma sentencia que ya conoces, con la clave y la tabla cambiadas:
INSERT INTO processed_documents (content_hash, source_name)
VALUES ($1, $2)
ON CONFLICT (content_hash) DO NOTHING
RETURNING content_hash;
Si devuelve una fila, el documento es nuevo: embébelo. Si devuelve vacío, ya estaba: sáltalo. Reconoces el patrón porque es el patrón. No inventamos nada nuevo; movimos el mismo mecanismo a otra puerta.
Calcular el hash del contenido con crypto
El hash lo calculas en un nodo Code con crypto, igual que la idempotency_key. Pero aquí hay una restricción de n8n 2.0 que conviene mirar de frente, porque afecta cómo llega el contenido al código.
El nodo Code no puede leer archivos del disco. No tiene acceso al sistema de archivos. Así que el contenido del documento no lo lee tu código: lo lee un nodo dedicado —por ejemplo, un nodo que lee un archivo binario, o el propio cargador de documentos del flujo de RAG— y el contenido llega al nodo Code como parte del item, ya sea como texto o como datos binarios. Tu código solo calcula el hash de lo que le llega, no va a buscar el archivo.
// ============================================================
// Nodo: Code — "Compute content hash"
// Modo: Run Once for Each Item
//
// ENTRADA: un item con el CONTENIDO del documento ya leído por un
// nodo anterior (el Code node NO lee archivos del disco en n8n 2.0).
// SALIDA: el mismo item, con content_hash calculado.
// NOTA: crypto SÍ está permitido en el nodo Code de n8n 2.0.
// ============================================================
const crypto = require('crypto');
// El texto del documento llega en el item, puesto por un nodo previo
// (un lector de archivos, un cargador de documentos, etc.).
const documentText = $input.item.json.text;
// La huella digital del contenido: mismo contenido → mismo hash,
// cualquier cambio → hash distinto. Por eso identifica "este documento exacto".
const contentHash = crypto.createHash('sha256').update(documentText).digest('hex');
return {
json: {
...$input.item.json,
content_hash: contentHash,
source_name: $input.item.json.source_name,
},
};
Un matiz honesto: de qué campo exacto sale el contenido depende de cómo lo hayas cargado —puede ser text, puede venir en un campo binario que primero conviertes a texto, puede ser el resultado de un cargador—. La idea que no cambia es que el contenido ya viene en el item, puesto por un nodo que sí puede leerlo, y el Code solo lo hashea. Ajusta el nombre del campo a tu flujo; verifica en el panel qué trae cada item.
Ejemplo trabajado: ingerir la base de conocimiento de Cumbre
Cumbre quiere que su agente de soporte pueda responder preguntas sobre políticas, productos y proveedores. Para eso ingiere una carpeta de documentos a Qdrant. El flujo, con la compuerta de dedup, queda así:
Trigger (manual o programado)
└─► Leer la lista de documentos ← un nodo que trae los archivos/contenidos
└─► Code: "Compute content hash" ← huella de cada documento
└─► Postgres: "Doc dedup gate" (INSERT ... ON CONFLICT ... RETURNING)
└─► IF: "¿Es nuevo?"
├─ true (devolvió fila → contenido nuevo)
│ └─► Embeddings (Ollama) → Qdrant: Insert Documents
│
└─ false (vacío → ya ingerido)
└─► saltar (no embeber, no insertar)
La compuerta se coloca antes de embeber, no después. Esa es la clave del ahorro: si un documento ya está, ni siquiera llamas a Ollama para embeberlo. El cómputo caro solo ocurre en la rama "nuevo".
Qué esperar. La primera vez que corres la ingesta con, digamos, veinte documentos, la tienda processed_documents está vacía, así que los veinte pasan por true, se embeben y se insertan en Qdrant. Ves veinte filas nuevas en processed_documents y los fragmentos correspondientes en Qdrant. La segunda vez que corres la ingesta con los mismos veinte documentos más dos nuevos, veinte chocan con ON CONFLICT y van a false —se saltan, sin llamar a Ollama—, y solo los dos nuevos se embeben. En processed_documents ahora hay veintidós filas, y en Qdrant no hay ni un fragmento duplicado. Ejecutaste la ingesta completa dos veces y el trabajo caro solo se hizo sobre lo que de verdad era nuevo. Eso es idempotencia en la ingesta: correr el pipeline mil veces deja el mismo resultado que correrlo una, sin desperdicio y sin duplicados.
Y si un documento cambió entre la primera y la segunda corrida —se editó la política de devoluciones—, su contenido nuevo produce un hash nuevo, así que la compuerta lo trata como nuevo y lo reprocesa. Justo lo que quieres: saltar lo idéntico, reprocesar lo que cambió.
Granularidad: el documento cambió, ¿y los fragmentos viejos?
Hay un detalle que separa una ingesta idempotente de juguete de una que funciona en serio, y vale la pena mirarlo de frente porque es donde mucha gente se tropieza.
Un documento no se embebe entero de un golpe: primero se parte en fragmentos (chunks) —párrafos, secciones, pedazos de un tamaño manejable— y cada fragmento se embebe por separado y se guarda como un punto en Qdrant. Un solo PDF de políticas puede convertirse en quince fragmentos. Esto es normal y necesario, porque las búsquedas quieren devolver el pedazo relevante, no el documento completo.
Aquí está el matiz. Estamos deduplicando a nivel de documento —la clave es el hash del contenido completo del archivo—, no a nivel de fragmento. Eso está bien para el caso común: si el documento es idéntico, saltas de una todos sus fragmentos, que es exactamente el ahorro que buscas. El problema aparece cuando un documento cambia: su hash nuevo hace que la compuerta lo trate como nuevo y reprocese todos sus fragmentos —correcto—, pero los fragmentos viejos de la versión anterior siguen en Qdrant. Si solo insertas los nuevos, terminas con las dos versiones conviviendo: la vieja y la nueva, y tus búsquedas devuelven una mezcla de las dos.
La solución es hacer que reprocesar un documento sea también idempotente en el store: antes de insertar los fragmentos nuevos, borra los viejos de ese mismo documento. Para poder hacerlo, al insertar cada fragmento le guardas una etiqueta que diga de qué documento vino —su source_name o, mejor, un identificador estable del documento—, de modo que puedas decirle a Qdrant "borra todos los puntos de este documento" antes de meter los nuevos. El flujo para un documento que cambió queda así:
Documento cambió (hash nuevo → pasa la compuerta como nuevo)
└─► Borrar en Qdrant los puntos etiquetados con este documento ← limpia lo viejo
└─► Embeber los fragmentos nuevos con Ollama
└─► Insertar los fragmentos nuevos en Qdrant ← queda solo lo nuevo
Un matiz honesto sobre las capacidades: si el nodo de Qdrant de tu versión te deja borrar puntos por una etiqueta o filtro depende de la versión y de la operación disponible, así que verifícalo en el panel; si no lo expone directamente, la idea —limpiar lo viejo antes de meter lo nuevo— sigue siendo la correcta, y la implementas con la operación de borrado que tu versión sí ofrezca. Para el caso más común de este módulo —documentos que casi siempre son idénticos entre corridas— la compuerta a nivel de documento ya te da el 90% del valor: no reprocesa lo que no cambió. El manejo fino de la actualización es la milla extra, y conviene saber que existe para no llevarte una sorpresa con versiones viejas conviviendo.
Un matiz sobre Qdrant y la honestidad de verificar
Hay una segunda forma de buscar idempotencia en un vector store, y conviene mencionarla con honestidad sobre sus límites. Algunos vector stores, Qdrant incluido, permiten darle a cada punto un identificador determinista —derivado, por ejemplo, del hash del contenido—, de modo que insertar el mismo punto dos veces sobrescriba en lugar de duplicar. Sería idempotencia del lado del almacén: aunque intentaras insertar dos veces, no habría duplicado porque el segundo pisa al primero.
El matiz honesto: si el nodo de Qdrant de n8n te deja fijar ese identificador de punto depende de la versión y de la operación, y la documentación del nodo, al escribir esta guía, no lo detalla claramente para la operación de insertar documentos. Por eso, el patrón que enseñamos —la compuerta de dedup en Postgres antes de embeber— es el más fiable y el que no depende de esa capacidad: funciona con cualquier vector store, te ahorra el cómputo del embedding (que el enfoque de sobrescribir no te ahorra, porque igual embebes antes de insertar), y lo puedes auditar con un SELECT. Si tu versión del nodo Qdrant sí expone identificadores de punto deterministas, puedes sumarlo como segunda línea de defensa; pero no dependas solo de eso, y verifica en el panel qué permite tu versión. Donde la documentación no confirma, enseña el concepto y comprueba en tu instalación: nunca supongas una capacidad que no viste funcionar.
Errores comunes
Deduplicar por nombre de archivo en vez de por contenido (conceptual, el grande). Qué pasa: se usa el nombre del archivo como clave, y cuando un documento se actualiza conservando el nombre, el sistema lo salta y el vector store se queda con la versión vieja para siempre. Por qué pasa: el nombre es lo más visible y parece identificar el documento. Cómo detectarlo: si actualizas el contenido de un archivo, vuelves a correr la ingesta y las búsquedas siguen devolviendo lo viejo, es esto. Cómo corregirlo: usa un hash del contenido como clave; así un cambio en el contenido produce una clave nueva y el documento se reprocesa, mientras que un contenido idéntico se salta.
Poner la compuerta después de embeber (práctico). Qué pasa: se embeben todos los documentos y solo después se revisa cuáles ya estaban, así que se gasta el cómputo caro incluso en los que se van a saltar. Por qué pasa: parece natural "procesar y luego filtrar". Cómo detectarlo: si tu ingesta tarda lo mismo la segunda vez que la primera aunque no haya documentos nuevos, la compuerta está mal ubicada. Cómo corregirlo: pon la compuerta de dedup antes de llamar a Ollama; el objetivo es no embeber lo que ya está, y eso solo se logra decidiendo antes del embedding.
Insertar sin deduplicar y degradar las búsquedas (práctico). Qué pasa: se corre la ingesta varias veces sin ninguna dedup, Qdrant acumula copias de los mismos fragmentos, y las respuestas del agente empeoran porque las búsquedas devuelven duplicados. Por qué pasa: sin dedup, cada corrida inserta todo otra vez. Cómo detectarlo: si al buscar un tema te salen fragmentos repetidos, o el conteo de puntos en Qdrant crece cada vez que reingieres lo mismo, es esto. Cómo corregirlo: la compuerta de dedup por hash de contenido evita insertar lo que ya está; y si ya ensuciaste el store, quizá tengas que limpiarlo y reingerir con la compuerta puesta.
Intentar leer el archivo desde el nodo Code (práctico). Qué pasa: se escribe código que intenta abrir el archivo para hashearlo, y falla porque el nodo Code no accede al sistema de archivos en n8n 2.0. Por qué pasa: viene natural pensar "leo el archivo y lo hasheo en el mismo nodo". Cómo detectarlo: si tu nodo Code intenta leer del disco y no puede, es esto. Cómo corregirlo: que un nodo dedicado lea el archivo y pase su contenido en el item; el nodo Code solo calcula el hash de lo que recibe. crypto sí está permitido para hashear; leer archivos, no.
Suponer que el nodo Qdrant deduplica solo (conceptual). Qué pasa: se asume que insertar el mismo documento dos veces "seguro que Qdrant lo maneja" y no se pone dedup, confiando en una sobrescritura que la versión del nodo quizá no hace. Por qué pasa: se da por hecho una capacidad sin verificarla. Cómo detectarlo: si tu única defensa contra duplicados es una suposición sobre el nodo Qdrant, y no lo comprobaste, es esto. Cómo corregirlo: no dependas de capacidades no verificadas; la compuerta en Postgres es fiable, auditable y ahorra el embedding. Verifica qué hace tu nodo Qdrant, y si expone IDs deterministas, úsalo como refuerzo, no como única defensa.
Ejercicios
Ejercicio 1 — Elige la clave. Para cada situación de la ingesta de Cumbre, di qué debería ser la clave de dedup y por qué, en una frase.
(a) La misma carpeta se reingiere cada noche; casi todo es idéntico al día anterior. (b) La política de devoluciones se editó, pero el archivo se sigue llamando igual. (c) Un proveedor manda su catálogo por correo dos veces, con nombres de archivo distintos, mismo contenido.
Ver solución
(a) Hash de contenido. Los documentos idénticos producen el mismo hash y se saltan sin gastar cómputo; solo se reprocesa lo que de verdad cambió. Exactamente el caso para el que la dedup por contenido existe.
(b) Hash de contenido, y aquí se ve por qué el nombre no sirve. El nombre no cambió, pero el contenido sí, así que el hash es distinto y el documento se reprocesa. Si dedujeras por nombre, te quedarías con la política vieja para siempre.
(c) Hash de contenido. Dos nombres distintos, mismo contenido → mismo hash → se procesa una sola vez. Si dedujeras por nombre, lo procesarías dos veces y duplicarías los fragmentos en Qdrant.
Por qué funciona: los tres casos apuntan a lo mismo: lo que identifica "el mismo documento" es su contenido, no su nombre. El hash de contenido es la única clave que acierta en los tres, porque responde a la pregunta correcta —"¿es este contenido exacto?"— y no a una aproximación —"¿se llama igual?"—.
Ejercicio 2 — Ubica la compuerta. Te dan dos diseños de ingesta. En el diseño A, el orden es: leer documentos → embeber todos con Ollama → compuerta de dedup → insertar en Qdrant los nuevos. En el diseño B: leer documentos → hash → compuerta de dedup → embeber solo los nuevos → insertar en Qdrant. ¿Cuál ahorra el cómputo caro y por qué? ¿Qué desperdicia el otro?
Ver solución
El diseño B ahorra el cómputo caro. La compuerta va antes de embeber, así que solo se llama a Ollama para los documentos que resultaron nuevos. Los que ya estaban se descartan antes de tocar el modelo.
El diseño A desperdicia el embedding. Embebe todos los documentos —incluidos los que ya estaban— y solo después decide cuáles insertar. El paso caro (pedirle a Ollama el embedding de cada fragmento) ya se pagó para todos, aunque la mayoría se vaya a descartar. La dedup del diseño A evita duplicados en Qdrant, sí, pero no ahorra nada de cómputo, que suele ser lo más caro del pipeline.
Por qué funciona: el principio es el mismo del ledger de la lección 3 y de la compuerta de la lección 5 —decidir antes de actuar sobre el efecto costoso—. Aquí el efecto costoso es embeber, así que la decisión tiene que ir antes del embedding, no después. Ubicar la compuerta es ubicar dónde está el gasto que quieres evitar.
Ejercicio 3 — Conecta el patrón. En una explicación breve, describe en qué se parece y en qué se diferencia deduplicar cobros (lección 5) de deduplicar ingesta de documentos (esta lección). El objetivo es que veas que es el mismo patrón.
Ver solución
Una versión de referencia:
En qué se parecen —que es casi todo—: el mecanismo es idéntico. Una tabla con una columna clave marcada como única, y un
INSERT ... ON CONFLICT DO NOTHING RETURNINGque decide, en un solo paso atómico, si es la primera vez (devuelve fila → actúa) o un duplicado (devuelve vacío → salta). En los dos casos la compuerta va antes del efecto costoso, y en los dos la clave debe identificar "el trabajo" y no "el intento".En qué se diferencian —solo dos cosas—: primero, la clave. En los cobros es un
order_ido un hash de campos del pedido; en la ingesta es un hash del contenido del archivo. Segundo, el efecto que proteges. En los cobros es crear un cargo en el CRM (cuesta dinero, es irreversible); en la ingesta es embeber con Ollama e insertar en Qdrant (cuesta cómputo y ensucia el store con duplicados). Distinta clave, distinto efecto, mismo esqueleto.La conclusión: la idempotencia no es una técnica para cobros. Es un patrón —consulta persistente antes de un efecto costoso que no quieres repetir— y sirve igual para dinero, para vectores, o para cualquier otra acción con consecuencias.
Por qué funciona: ver el patrón por encima de sus dos aplicaciones es lo que te permite reconocerlo en un tercer caso que todavía no viste. Quien solo aprendió "cómo no cobrar dos veces" resuelve un problema; quien aprendió "cómo no repetir un efecto costoso" resuelve una familia entera.
Resumen y siguiente paso
En esta lección llevaste la deduplicación del módulo a la ingesta de RAG y comprobaste que es el mismo patrón con otra piel. Re-embeber un documento que ya procesaste desperdicia cómputo y llena el vector store de fragmentos duplicados que degradan las búsquedas —como volver a fotocopiar y archivar un documento que ya estaba en el archivero—. La solución es la tienda de dedup de la lección 5, con una tabla processed_documents, la misma compuerta INSERT ... ON CONFLICT DO NOTHING RETURNING, y un cambio de clave.
Ese cambio de clave es la decisión central: la clave correcta es un hash del contenido del archivo, no su nombre. El hash es la huella digital del contenido —mismo contenido, mismo hash; cualquier cambio, hash distinto—, así que salta lo idéntico, reprocesa lo que cambió aunque se llame igual, y no se deja engañar por un cambio de nombre. Conociste embeddings (la traducción del significado a números) y vector stores (el archivero que busca por cercanía), con Qdrant y Ollama del Starter Kit haciéndolo local y a costo cero, con modelos como nomic-embed-text cuya lista conviene verificar. Y viste la regla de oro: la compuerta va antes de embeber, para que el cómputo caro solo ocurra sobre lo nuevo.
Y viste la milla extra: como un documento se parte en fragmentos, cuando uno cambia conviene borrar sus fragmentos viejos de Qdrant antes de insertar los nuevos, para no dejar dos versiones conviviendo. La compuerta a nivel de documento te da la mayor parte del valor; ese manejo fino de la actualización es lo que la lleva a producción.
Antes de avanzar deberías poder: explicar por qué el hash de contenido es mejor clave que el nombre del archivo; decir por qué la compuerta va antes del embedding; y articular en qué se parece esto a deduplicar cobros.
La lección 8 cierra el módulo con el proyecto: construyes el ledger y la tienda de dedup en el Postgres local, los conectas a un webhook que dispara dos veces, y demuestras —con las tablas y el CRM a la vista— que el segundo disparo se descarta antes del efecto. Todo lo que diseñaste, montaste y aplicaste en las siete lecciones se junta en un entregable que puedes defender: el esquema de las tablas más el flujo que las usa, probado de punta a punta contra el duplicado.
Recursos
- Qdrant Vector Store node — n8n Docs — las operaciones del nodo (Insert Documents, Get Many, Retrieve) y el nombre de colección. Verifica ahí si tu versión expone identificadores de punto para la inserción.
- Embeddings Ollama node — n8n Docs — cómo generar embeddings con Ollama en local dentro de n8n, y dónde eliges el modelo.
- Ollama embedding models — Ollama Blog — la presentación de los modelos de embedding de Ollama; úsala para ver la lista vigente (
nomic-embed-text,mxbai-embed-largey los que se sumen) cuando montes esto. - PostgreSQL — INSERT ... ON CONFLICT — la referencia del mecanismo atómico que aquí reutilizas con
content_hashcomo clave. - Code node — n8n Docs — el nodo donde calculas el hash con
crypto, y el recordatorio de que no lee archivos del disco: el contenido llega en el item desde un nodo anterior. - Deploy with the AI starter kit — n8n Docs — el stack que trae Qdrant y Ollama listos para la ingesta local, a costo cero.