MongoDB Avanzado: Aggregation Pipeline, Índices y Vector Search

D
DanisCh
15 min de lectura
MongoDB Avanzado: Aggregation Pipeline, Índices y Vector Search
Conceptos de Base de Datos

Ya sabes qué es MongoDB, cómo funciona un documento, cómo hacer operaciones CRUD básicas. Ahora llega la parte donde la mayoría de los desarrolladores se quedan cortos: el Aggregation Pipeline, el diseño correcto de esquemas, los índices que marcan la diferencia y las capacidades de búsqueda vectorial que hacen de MongoDB una herramienta imprescindible en proyectos de IA.

Este artículo da un paso más allá de lo básico. Si aún no tienes claro qué es MongoDB o cómo funciona internamente, te recomendamos leer primero nuestra guía de MongoDB para principiantes. Si vienes con bases sólidas, sigue leyendo — aquí aprenderás a usarlo como un profesional.


1. El Aggregation Pipeline — el motor de consultas avanzado

El Aggregation Pipeline es la herramienta más poderosa de MongoDB. Permite procesar documentos a través de una serie de etapas (stages), donde cada etapa transforma, filtra, agrupa o enriquece los datos antes de pasarlos a la siguiente.

Piénsalo como una cadena de montaje: los documentos entran en bruto y salen transformados, filtrados, agrupados y calculados exactamente como necesitas.

db.coleccion.aggregate([
  { $etapa1: { ... } },  // transforma documentos
  { $etapa2: { ... } },  // filtra o agrupa
  { $etapa3: { ... } },  // proyecta campos
  // ...
])

Las etapas más importantes

$match — filtrar documentos (siempre primero)

// Equivalente a WHERE en SQL
// REGLA DE ORO: usa $match lo antes posible para reducir documentos

db.pedidos.aggregate([
  {
    $match: {
      estado: "completado",
      fecha: { $gte: new Date("2026-01-01") },
      total: { $gt: 100 }
    }
  }
  // Solo los documentos que pasen este filtro avanzan al siguiente stage
])

$group — agrupar y calcular

// Equivalente a GROUP BY + funciones de agregación en SQL

db.pedidos.aggregate([
  { $match: { estado: "completado" } },
  {
    $group: {
      _id: "$cliente_id",           // agrupar por cliente
      total_gastado: { $sum: "$total" },    // suma de totales
      num_pedidos:   { $count: {} },        // contar pedidos
      ticket_medio:  { $avg: "$total" },    // media
      primer_pedido: { $min: "$fecha" },    // mínimo
      ultimo_pedido: { $max: "$fecha" }     // máximo
    }
  },
  { $sort: { total_gastado: -1 } },  // ordenar por gasto desc
  { $limit: 10 }                     // top 10 clientes
])

// Resultado:
// { _id: "cliente_42", total_gastado: 4580.50, num_pedidos: 23, ... }
// { _id: "cliente_17", total_gastado: 3920.00, num_pedidos: 15, ... }

$project — seleccionar y transformar campos

db.usuarios.aggregate([
  {
    $project: {
      nombre_completo: { $concat: ["$nombre", " ", "$apellido"] },
      email: 1,          // incluir
      edad: 1,
      password: 0,       // excluir (seguridad)
      _id: 0,            // excluir _id
      año_registro: { $year: "$fecha_registro" },  // extraer año
      es_adulto: { $gte: ["$edad", 18] }           // campo calculado
    }
  }
])

$lookup — unir colecciones (el JOIN de MongoDB)

// Unir pedidos con datos del cliente — equivalente a JOIN en SQL
// Esta es la alternativa a los JOINs relacionales en MongoDB

db.pedidos.aggregate([
  {
    $lookup: {
      from: "clientes",         // colección a unir
      localField: "cliente_id", // campo en pedidos
      foreignField: "_id",      // campo en clientes
      as: "cliente"             // nombre del campo resultado
    }
  },
  {
    $unwind: "$cliente"  // desenvuelve el array resultado a un objeto
  },
  {
    $project: {
      "cliente.password": 0  // excluir campos sensibles del cliente
    }
  }
])

// Resultado:
// {
//   _id: ObjectId(...),
//   total: 299.99,
//   cliente: {
//     nombre: "Ana García",
//     email: "ana@ejemplo.com",
//     ciudad: "Madrid"
//   }
// }

$lookup con pipeline — joins avanzados

// $lookup con pipeline: más flexible y potente
// Permite filtrar y transformar durante el join

db.pedidos.aggregate([
  {
    $lookup: {
      from: "productos",
      let: { ids_productos: "$items.producto_id" },
      pipeline: [
        {
          $match: {
            $expr: { $in: ["$_id", "$$ids_productos"] }
          }
        },
        {
          $project: { nombre: 1, precio: 1, categoria: 1 }
        }
      ],
      as: "productos_detalle"
    }
  }
])

$unwind — expandir arrays

// Si un documento tiene un array, $unwind crea un documento por cada elemento

// Documento original:
// { pedido_id: 1, items: ["laptop", "mouse", "teclado"] }

db.pedidos.aggregate([
  { $unwind: "$items" }
])

// Resultado (3 documentos):
// { pedido_id: 1, items: "laptop" }
// { pedido_id: 1, items: "mouse" }
// { pedido_id: 1, items: "teclado" }

// Con opciones para preservar documentos vacíos:
{ $unwind: { path: "$items", preserveNullAndEmptyArrays: true } }

$addFields y $set — añadir campos calculados

db.productos.aggregate([
  {
    $addFields: {
      precio_con_iva: { $multiply: ["$precio", 1.21] },
      en_stock: { $gt: ["$stock", 0] },
      categoria_upper: { $toUpper: "$categoria" },
      descripcion_corta: {
        $substr: ["$descripcion", 0, 100]  // primeros 100 caracteres
      }
    }
  }
])

$facet — múltiples pipelines en paralelo

// Ejecuta múltiples sub-pipelines sobre los mismos documentos
// Ideal para páginas con filtros + resultados + estadísticas simultáneos

db.productos.aggregate([
  { $match: { categoria: "tecnologia" } },
  {
    $facet: {
      // Pipeline 1: resultados paginados
      resultados: [
        { $sort: { precio: 1 } },
        { $skip: 0 },
        { $limit: 20 }
      ],
      // Pipeline 2: total de resultados
      total: [
        { $count: "cantidad" }
      ],
      // Pipeline 3: rango de precios
      rango_precios: [
        {
          $group: {
            _id: null,
            min: { $min: "$precio" },
            max: { $max: "$precio" },
            promedio: { $avg: "$precio" }
          }
        }
      ],
      // Pipeline 4: distribución por subcategoría
      por_subcategoria: [
        { $group: { _id: "$subcategoria", total: { $sum: 1 } } }
      ]
    }
  }
])
// Una sola consulta devuelve resultados, totales y estadísticas

2. Pipeline completo real: análisis de ventas

// Reporte: top 5 categorías por ingresos en el último trimestre,
// con ticket medio y número de pedidos

db.pedidos.aggregate([
  // 1. Filtrar por fecha (limita documentos lo antes posible)
  {
    $match: {
      fecha: { $gte: new Date("2026-01-01"), $lt: new Date("2026-04-01") },
      estado: { $in: ["completado", "entregado"] }
    }
  },

  // 2. Expandir los items de cada pedido
  { $unwind: "$items" },

  // 3. Unir con la colección de productos para obtener la categoría
  {
    $lookup: {
      from: "productos",
      localField: "items.producto_id",
      foreignField: "_id",
      as: "producto"
    }
  },
  { $unwind: "$producto" },

  // 4. Agrupar por categoría
  {
    $group: {
      _id: "$producto.categoria",
      ingresos_totales: { $sum: { $multiply: ["$items.cantidad", "$items.precio"] } },
      num_pedidos:      { $count: {} },
      ticket_medio:     { $avg: { $multiply: ["$items.cantidad", "$items.precio"] } }
    }
  },

  // 5. Añadir campo calculado
  {
    $addFields: {
      ticket_medio: { $round: ["$ticket_medio", 2] }
    }
  },

  // 6. Ordenar por ingresos
  { $sort: { ingresos_totales: -1 } },

  // 7. Top 5
  { $limit: 5 },

  // 8. Proyectar solo lo necesario
  {
    $project: {
      categoria: "$_id",
      ingresos_totales: 1,
      num_pedidos: 1,
      ticket_medio: 1,
      _id: 0
    }
  }
])

3. Índices avanzados — el rendimiento que marca la diferencia

Como ya vimos en nuestro artículo sobre índices en SQL, los índices son la diferencia entre una consulta de milisegundos y una de minutos. En MongoDB el principio es el mismo, pero con algunas particularidades importantes.

Tipos de índices en MongoDB

// Índice simple — el más básico
db.usuarios.createIndex({ email: 1 })       // 1 = ascendente
db.usuarios.createIndex({ fecha: -1 })      // -1 = descendente

// Índice único — garantiza unicidad
db.usuarios.createIndex({ email: 1 }, { unique: true })

// Índice compuesto — múltiples campos
// El ORDEN IMPORTA: diseña según tus patrones de consulta
db.pedidos.createIndex({ cliente_id: 1, fecha: -1 })
// Optimiza consultas como:
// db.pedidos.find({ cliente_id: "x" }).sort({ fecha: -1 })

// Índice de texto completo — búsqueda full-text
db.articulos.createIndex({
  titulo: "text",
  contenido: "text",
  tags: "text"
}, {
  weights: { titulo: 10, contenido: 5, tags: 3 },  // importancia relativa
  default_language: "spanish"
})

// Usar el índice de texto:
db.articulos.find({
  $text: { $search: "inteligencia artificial machine learning" }
}, {
  score: { $meta: "textScore" }  // añadir puntuación de relevancia
}).sort({ score: { $meta: "textScore" } })

// Índice geoespacial — para ubicaciones
db.restaurantes.createIndex({ ubicacion: "2dsphere" })
db.restaurantes.find({
  ubicacion: {
    $near: {
      $geometry: { type: "Point", coordinates: [-3.7038, 40.4168] },
      $maxDistance: 2000  // 2km en metros
    }
  }
})

// Índice TTL — documentos que expiran automáticamente
db.sesiones.createIndex(
  { creado_en: 1 },
  { expireAfterSeconds: 3600 }  // documerntos expiran en 1 hora
)

// Índice parcial — solo indexa documentos que cumplen una condición
db.pedidos.createIndex(
  { fecha: 1 },
  {
    partialFilterExpression: { estado: "pendiente" }
    // Solo indexa pedidos pendientes — más pequeño y eficiente
  }
)

explain() — diagnosticar consultas lentas

// .explain() es el equivalente a EXPLAIN en SQL
// Te dice si la consulta usa índices o hace un COLLSCAN (full scan)

db.pedidos.find({ cliente_id: "abc123" }).explain("executionStats")

// Lo que necesitas mirar:
// ✅ IXSCAN → usa índice → rápido
// ❌ COLLSCAN → escaneo completo → necesita índice

// Resultado típico sin índice:
{
  "stage": "COLLSCAN",      // ← MAL: revisa todos los documentos
  "nReturned": 5,
  "docsExamined": 1000000,  // ← examinó 1M documentos para devolver 5
  "executionTimeMillisEstimate": 420  // 420ms
}

// Resultado con índice:
{
  "stage": "IXSCAN",     // ← BIEN: usa el índice
  "nReturned": 5,
  "docsExamined": 5,     // ← solo examina los 5 documentos necesarios
  "executionTimeMillisEstimate": 1   // 1ms
}

La regla ESR para índices compuestos

// Regla ESR — el orden correcto para índices compuestos:
// E = Equality (igualdad) → primero
// S = Sort (ordenamiento) → segundo
// R = Range (rango: $gt, $lt, $in) → último

// Consulta:
db.pedidos.find({
  cliente_id: "x",           // E (igualdad)
  estado: { $in: ["a","b"] } // R (rango)
}).sort({ fecha: -1 })       // S (sort)

// Índice correcto siguiendo ESR:
db.pedidos.createIndex({
  cliente_id: 1,  // E primero
  fecha: -1,      // S segundo (coincide con el sort)
  estado: 1       // R último
})

4. Diseño de esquemas — las decisiones que importan

A diferencia de SQL donde el diseño es siempre "normalizar al máximo", en MongoDB la pregunta es: ¿embebe o referencia? La respuesta depende de los patrones de acceso de tu aplicación.

Patrón 1: Embedding (embeber) — cuando los datos se usan juntos

// ✅ EMBEBER cuando:
// - Los datos siempre se leen juntos
// - Relación 1:pocos (máximo ~10-15 elementos)
// - Los subdocumentos no se acceden de forma independiente

// Ejemplo: post de blog con comentarios
{
  "_id": ObjectId("..."),
  "titulo": "MongoDB Avanzado",
  "contenido": "...",
  "autor": {
    "nombre": "Ana García",
    "avatar": "https://..."
  },
  "comentarios": [
    {
      "usuario": "Luis",
      "texto": "Excelente artículo",
      "fecha": ISODate("2026-04-01"),
      "likes": 12
    },
    {
      "usuario": "María",
      "texto": "Muy útil, gracias",
      "fecha": ISODate("2026-04-02"),
      "likes": 5
    }
  ],
  "tags": ["mongodb", "nosql", "avanzado"]
}

// Una sola consulta trae todo lo necesario para mostrar el post
db.posts.findOne({ _id: postId })  // ← sin JOINs

Patrón 2: Referencing (referenciar) — cuando los datos son independientes

// ✅ REFERENCIAR cuando:
// - Los datos se acceden de forma independiente
// - Relación 1:muchos (miles o millones de elementos)
// - Los datos se actualizan con frecuencia
// - Múltiples entidades referencian los mismos datos

// Ejemplo: pedido referenciando usuario y productos
{
  "_id": ObjectId("..."),
  "fecha": ISODate("2026-04-01"),
  "cliente_id": ObjectId("abc123"),   // referencia al usuario
  "items": [
    {
      "producto_id": ObjectId("prod1"),  // referencia al producto
      "cantidad": 2,
      "precio_unitario": 29.99  // ← precio al momento de compra (snapshot)
    }
  ],
  "total": 59.98
}

// Para ver el pedido completo, usas $lookup en el pipeline de agregación

El patrón Bucket — para series temporales

// ❌ Antipatrón: un documento por cada medición
// Con millones de sensores enviando datos cada segundo → millones de documentos
{ sensor_id: "s1", temperatura: 22.4, timestamp: ISODate("...") }
{ sensor_id: "s1", temperatura: 22.5, timestamp: ISODate("...") }
// ... millones de documentos pequeños → muy ineficiente

// ✅ Patrón Bucket: agrupa mediciones en "cubos" por hora
{
  sensor_id: "s1",
  hora: ISODate("2026-04-01T14:00:00Z"),
  mediciones: [
    { minuto: 0, temperatura: 22.4 },
    { minuto: 1, temperatura: 22.5 },
    // ... hasta 60 mediciones por documento
  ],
  stats: {
    min: 22.1,
    max: 23.0,
    promedio: 22.5,
    count: 60
  }
}
// Reduce el número de documentos 60x, mejora dramáticamente el rendimiento

El antipatrón del array ilimitado

// ❌ NUNCA hagas esto: arrays que crecen indefinidamente
{
  usuario_id: "user1",
  // Un usuario puede tener millones de seguidores
  // El documento puede superar el límite de 16MB de MongoDB
  seguidores: [
    "user2", "user3", "user4", ... // potencialmente millones
  ]
}

// ✅ Inversión de relación: colección separada
// En su lugar, una colección "seguimientos":
{
  seguidor_id: "user2",
  seguido_id: "user1",
  fecha: ISODate("...")
}
// Indexa seguidor_id y seguido_id para queries eficientes

5. Transacciones en MongoDB

Desde MongoDB 4.0, las transacciones ACID están disponibles para operaciones que involucran múltiples documentos o colecciones. Si ya leíste nuestro artículo sobre transacciones en SQL, los conceptos son similares.

const { MongoClient } = require('mongodb');

const client = new MongoClient('mongodb://localhost:27017');

async function transferirDinero(origenId, destinoId, importe) {
  const session = client.startSession();

  try {
    await session.withTransaction(async () => {
      const cuentas = client.db('banco').collection('cuentas');

      // Verificar saldo con bloqueo
      const origen = await cuentas.findOne(
        { _id: origenId },
        { session }
      );

      if (origen.saldo < importe) {
        throw new Error('Saldo insuficiente');
      }

      // Descontar del origen
      await cuentas.updateOne(
        { _id: origenId },
        { $inc: { saldo: -importe } },
        { session }
      );

      // Acreditar en el destino
      await cuentas.updateOne(
        { _id: destinoId },
        { $inc: { saldo: importe } },
        { session }
      );

      // Registrar el movimiento
      await client.db('banco')
        .collection('movimientos')
        .insertOne({
          origen: origenId,
          destino: destinoId,
          importe,
          fecha: new Date()
        }, { session });

      // Si llegamos aquí, todo fue bien → COMMIT automático
    });

    console.log('✅ Transferencia completada');

  } catch (error) {
    console.error('❌ Error — ROLLBACK automático:', error.message);
    throw error;

  } finally {
    session.endSession();
  }
}

⚠️ Importante: Las transacciones en MongoDB tienen un costo de rendimiento. Úsalas solo cuando realmente necesites atomicidad entre múltiples documentos. Para la mayoría de operaciones, el modelo de atomicidad a nivel de documento de MongoDB es suficiente.


6. Mongoose avanzado — esquemas, middlewares y plugins

const mongoose = require('mongoose');

// ============================================
// ESQUEMA CON VALIDACIONES AVANZADAS
// ============================================

const productoSchema = new mongoose.Schema({
  nombre: {
    type: String,
    required: [true, 'El nombre es obligatorio'],
    trim: true,
    minlength: [3, 'Mínimo 3 caracteres'],
    maxlength: [100, 'Máximo 100 caracteres']
  },
  precio: {
    type: Number,
    required: true,
    min: [0, 'El precio no puede ser negativo'],
    get: v => Math.round(v * 100) / 100  // siempre 2 decimales al leer
  },
  categoria: {
    type: String,
    enum: {
      values: ['tecnologia', 'ropa', 'hogar', 'deportes'],
      message: '{VALUE} no es una categoría válida'
    }
  },
  etiquetas: [{ type: String, lowercase: true, trim: true }],
  creado_en: { type: Date, default: Date.now },
  activo: { type: Boolean, default: true }
}, {
  timestamps: true,   // añade createdAt y updatedAt automáticamente
  versionKey: false   // elimina el campo __v
});

// ============================================
// ÍNDICES DEFINIDOS EN EL ESQUEMA
// ============================================

productoSchema.index({ nombre: 'text', descripcion: 'text' });
productoSchema.index({ categoria: 1, precio: 1 });
productoSchema.index({ activo: 1, creado_en: -1 });

// ============================================
// CAMPOS VIRTUALES — calculados, no guardados en BD
// ============================================

productoSchema.virtual('precio_con_iva').get(function() {
  return (this.precio * 1.21).toFixed(2);
});

productoSchema.virtual('es_nuevo').get(function() {
  const diasDesdeCreacion = (Date.now() - this.creado_en) / (1000 * 60 * 60 * 24);
  return diasDesdeCreacion < 30;
});

// ============================================
// MIDDLEWARES (hooks) — ejecutan antes/después de operaciones
// ============================================

// Pre-save: antes de guardar
productoSchema.pre('save', function(next) {
  // Normalizar nombre a Title Case
  this.nombre = this.nombre
    .toLowerCase()
    .split(' ')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ');
  next();
});

// Post-save: después de guardar
productoSchema.post('save', function(doc) {
  console.log(`Producto guardado: ${doc.nombre} (${doc._id})`);
});

// Pre-find: añadir filtro automático en todas las consultas
productoSchema.pre(/^find/, function(next) {
  // Solo devuelve productos activos por defecto
  this.where({ activo: { $ne: false } });
  next();
});

// ============================================
// MÉTODOS DE INSTANCIA — en un documento específico
// ============================================

productoSchema.methods.aplicarDescuento = function(porcentaje) {
  this.precio = this.precio * (1 - porcentaje / 100);
  return this.save();
};

// ============================================
// MÉTODOS ESTÁTICOS — en el modelo
// ============================================

productoSchema.statics.findPorCategoria = function(categoria) {
  return this.find({ categoria }).sort({ precio: 1 });
};

productoSchema.statics.masVendidos = function(limit = 10) {
  return this.aggregate([
    { $sort: { ventas: -1 } },
    { $limit: limit },
    { $project: { nombre: 1, precio: 1, ventas: 1 } }
  ]);
};

// ============================================
// PLUGIN REUTILIZABLE — añade funcionalidad a múltiples esquemas
// ============================================

function softDeletePlugin(schema) {
  schema.add({ eliminado: { type: Boolean, default: false } });
  schema.add({ eliminado_en: Date });

  schema.methods.softDelete = function() {
    this.eliminado = true;
    this.eliminado_en = new Date();
    return this.save();
  };

  schema.pre(/^find/, function() {
    this.where({ eliminado: { $ne: true } });
  });
}

// Aplicar el plugin a cualquier esquema:
productoSchema.plugin(softDeletePlugin);

const Producto = mongoose.model('Producto', productoSchema);

7. Vector Search — MongoDB para aplicaciones de IA

En 2026, MongoDB Atlas Vector Search es una de las características más relevantes para desarrolladores que trabajan con IA. Permite almacenar embeddings (vectores numéricos que representan el significado semántico de texto o imágenes) y hacer búsquedas por similitud semántica.

Esto es la base de los sistemas RAG (Retrieval-Augmented Generation) — la técnica que permite a ChatGPT y otros modelos responder preguntas sobre tus propios documentos.

// 1. Crear el índice vectorial en MongoDB Atlas
// (se configura desde la UI de Atlas o con la API)
// {
//   "fields": [{
//     "type": "vector",
//     "path": "embedding",
//     "numDimensions": 1536,     ← dimensiones del modelo (OpenAI: 1536)
//     "similarity": "cosine"     ← métrica de similitud
//   }]
// }

// 2. Insertar documentos con sus embeddings
const { OpenAI } = require('openai');
const openai = new OpenAI();

async function indexarDocumento(db, texto, metadata) {
  // Generar embedding con OpenAI
  const response = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: texto
  });

  const embedding = response.data[0].embedding;  // vector de 1536 dimensiones

  await db.collection('documentos').insertOne({
    texto,
    embedding,
    ...metadata,
    indexado_en: new Date()
  });
}

// 3. Búsqueda semántica — encuentra documentos por significado, no por palabras exactas
async function busquedaSemantica(db, consulta, limite = 5) {
  // Generar embedding de la consulta
  const response = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: consulta
  });
  const queryEmbedding = response.data[0].embedding;

  // Búsqueda vectorial con el pipeline de agregación
  const resultados = await db.collection('documentos').aggregate([
    {
      $vectorSearch: {
        index: "vector_index",
        path: "embedding",
        queryVector: queryEmbedding,
        numCandidates: 100,   // candidatos a considerar
        limit: limite         // resultados a devolver
      }
    },
    {
      $project: {
        texto: 1,
        score: { $meta: "vectorSearchScore" },  // puntuación de relevancia
        embedding: 0  // no devolver el vector (ahorra ancho de banda)
      }
    }
  ]).toArray();

  return resultados;
}

// Ejemplo de uso:
// busquedaSemantica(db, "¿cómo optimizar consultas en bases de datos?")
// → Devuelve los 5 documentos más semánticamente relacionados
// aunque no contengan exactamente esas palabras

8. Rendimiento: patrones que debes conocer

Proyecciones — solo trae lo que necesitas

// ❌ Malo: trae todos los campos aunque solo necesites 2
db.usuarios.find({ activo: true })

// ✅ Proyección: solo los campos necesarios
db.usuarios.find(
  { activo: true },
  { nombre: 1, email: 1, _id: 0 }  // 1=incluir, 0=excluir
)
// Reduce dramáticamente el uso de red y memoria

Paginación eficiente con cursor

// ❌ Paginación con skip — lento en páginas grandes
// skip(10000) obliga a MongoDB a saltar 10,000 documentos
db.productos.find().skip(10000).limit(20)

// ✅ Paginación con cursor — eficiente a cualquier profundidad
// Usa el _id del último documento de la página anterior
db.productos.find({
  _id: { $gt: ultimo_id_de_pagina_anterior }
}).limit(20).sort({ _id: 1 })
// MongoDB usa el índice del _id directamente, sin saltar

Contador eficiente

// ❌ count() con filtros complejos puede ser lento
db.pedidos.find({ estado: "pendiente" }).count()

// ✅ countDocuments() usa el índice si existe
db.pedidos.countDocuments({ estado: "pendiente" })

// ✅ Para estimados rápidos del total (sin filtros)
db.pedidos.estimatedDocumentCount()
// Lee el total de los metadatos de la colección → instantáneo

Resumen: lo que aprendiste hoy

  • ✅ El Aggregation Pipeline transforma documentos en etapas: $match, $group, $lookup, $unwind, $facet y más
  • $match siempre lo primero para filtrar antes de procesar
  • $lookup es el JOIN de MongoDB — con pipeline para joins avanzados
  • $facet ejecuta múltiples pipelines en paralelo — ideal para páginas con filtros
  • ✅ La regla ESR para índices compuestos: Equality → Sort → Range
  • explain("executionStats") diagnostica si se usa índice (IXSCAN) o full scan (COLLSCAN)
  • Embeber vs Referenciar: embebe cuando los datos se leen juntos; referencia cuando son independientes o crecen sin límite
  • ✅ El patrón Bucket agrupa datos de series temporales para eficiencia máxima
  • ✅ Las transacciones ACID están disponibles desde MongoDB 4.0 para múltiples documentos
  • ✅ Mongoose avanzado: middlewares pre/post, métodos estáticos, campos virtuales y plugins reutilizables
  • Vector Search convierte MongoDB en el backend ideal para aplicaciones de IA con búsqueda semántica
  • ✅ Proyecciones, paginación por cursor y countDocuments() son claves para el rendimiento


🧪 ¿Tienes bien claros los fundamentos de bases de datos NoSQL?

Para aprovechar MongoDB a nivel avanzado necesitas tener sólidos los conceptos de NoSQL y saber cuándo usarlo frente a SQL. Comprueba dónde estás:

👉 Test: SQL vs NoSQL 👉 Test: NoSQL Fundamentos y Tipos 

¿Cuál parte te resultó más útil: el Aggregation Pipeline, el diseño de esquemas o el Vector Search? ¿Ya usabas transacciones en MongoDB o es la primera vez que lo ves? Cuéntanos en los comentarios 👇 — respondemos todos. 🚀

Etiquetas: MongoDB NoSQL

¿Te ha gustado esta entrada?

Compártela con tus compañeros para que también sigan aprendiendo.

Comunidad y Comentarios

0 COMENTARIOS

No hay comentarios todavía. Sé el primero en compartir tu opinión.

Escribe tu opinión
Respondiendo a