← Volver al Índice Principal 📄 Views y Templates Completos

🗄️ Bases de Datos NoSQL

MongoDB Atlas con Django

Unidad II: Implementación de APIs de Terceros
Universidad: UTH | Ciclo: 2026-1

0. 🎯 Introducción - ¿Qué vamos a lograr?

🎓 LO QUE APRENDERÁS EN ESTA GUÍA (Sistema Híbrido 2026):

Crear un sistema de biblioteca que usa MySQL (local) y MongoDB Atlas (nube) trabajando juntos mediante un campo común.

🗄️ MySQL (Local)
  • Usuarios: auth_user (Django)
  • Préstamos: biblioteca_prestamo
  • Multas: biblioteca_multa
  • Campo clave: libro_id (entero)
  • ¿Por qué? Transacciones ACID, relaciones FK
☁️ MongoDB Atlas (Nube)
  • Catálogo: libros (colección)
  • Metadatos: autor, categorías, reseñas
  • Logs: actividad del sistema
  • Campo clave: libro_id (entero común)
  • ¿Por qué? Esquema flexible, escalable
🔗 INTEGRACIÓN: Campo Común libro_id

Ejemplo de consulta híbrida:

# 1. Django obtiene préstamo de MySQL
prestamo = Prestamo.objects.get(id=1)
libro_id = prestamo.libro_id  # ← Campo común (ej: 5)

# 2. Django busca detalles del libro en MongoDB Atlas
libro_mongo = db.libros.find_one({"libro_id": libro_id})

# 3. Combina datos de ambas BDs
resultado = {
    "usuario": prestamo.usuario.username,  # MySQL
    "libro": libro_mongo["titulo"],        # MongoDB
    "autor": libro_mongo["autor"]["nombre"], # MongoDB
    "fecha_prestamo": prestamo.fecha        # MySQL
}

🏆 Sistema Híbrido: Lo Mejor de Ambos Mundos

Este proyecto combina MySQL local (datos relacionales) con MongoDB Atlas (datos flexibles) usando un campo común: libro_id

Aspecto MySQL (Local) MongoDB Atlas (Nube)
¿Qué guarda? Usuarios, préstamos, multas, transacciones Catálogo de libros, reseñas, metadatos, logs
¿Por qué? ACID, integridad referencial, transacciones críticas Esquema flexible, escalabilidad, datos no estructurados
Campo común libro_id (INT) en tabla prestamos libro_id (INT) en documento libros
Tecnología Django ORM (models.py) PyMongo (consultas directas)
Ejemplo de datos Prestamo(id=1, libro_id=5, usuario_id=10) {"libro_id": 5, "titulo": "Cien Años..."}
🔗 Flujo de Integración:
  1. Usuario pide un libro prestado → Django crea registro en MySQL (tabla prestamos)
  2. Sistema necesita mostrar datos del libro → Django consulta MongoDB Atlas usando el libro_id
  3. Se combinan datos:
    • Fecha de préstamo (MySQL)
    • Título, autor, categorías (MongoDB)
  4. Usuario devuelve el libro → Django actualiza MySQL
  5. Se registra log de actividad → Django inserta en MongoDB Atlas

⚠️ IMPORTANTE - MongoDB Atlas Versión Gratuita 2026:

Esta guía está actualizada para las limitaciones actuales de Atlas:

  • 🔴 1 solo cluster M0 gratuito por cuenta
  • Múltiples bases de datos dentro del cluster (ilimitadas)
  • Múltiples colecciones por base de datos (ilimitadas)
  • 💾 512 MB de almacenamiento total compartido entre todas las BDs
  • ⏸️ Pausa automática después de 60 días de inactividad

📖 Esta guía te enseñará:

  1. Cómo crear tu único cluster gratuito
  2. Cómo organizar múltiples bases de datos dentro de él
  3. Cómo conectar Django a diferentes BDs del mismo cluster
  4. Cómo monitorear el uso de almacenamiento (max 512 MB)

🎬 Flujo de Trabajo Completo - De Principio a Fin:

  1. Crear cuenta en MongoDB Atlas
    • Registrarse con Google o email
    • Verificar cuenta
    • Crear organización y proyecto
  2. Configurar cluster gratuito M0
    • Seleccionar plan FREE (512 MB)
    • Elegir región (AWS us-east-1)
    • Nombrar cluster: BibliotecaCluster
  3. Configurar seguridad
    • Crear usuario de BD: biblioteca_admin
    • Agregar IP a whitelist: 0.0.0.0/0 (desarrollo)
  4. Obtener connection string
    • Copiar URL de conexión
    • Reemplazar contraseña
    • Agregar nombre de base de datos
  5. Instalar dependencias Python
    • pip install pymongo dnspython
  6. Conectar Django a MongoDB
    • Configurar settings.py
    • Crear servicio MongoService
    • Probar conexión con script de test
  7. Crear bases de datos en el cluster
    • biblioteca_catalogo → libros, autores
    • biblioteca_logs → actividad, errores
  8. Implementar CRUD en Django
    • Views para crear, listar, editar, eliminar
    • Templates HTML con formularios
    • Validaciones y manejo de errores
  9. Probar y verificar en Atlas
    • Insertar documentos de prueba
    • Ver en Atlas → Browse Collections
    • Monitorear uso de almacenamiento

⚙️ ¿Qué Hace Cada Tecnología en Este Sistema Híbrido?

Componente Función Específica ¿Por qué lo usamos?
MySQL (Local) Base de datos relacional para préstamos, usuarios, multas ACID, transacciones críticas, integridad referencial con Foreign Keys
Django ORM Interfaz Python para MySQL (models.py) Define modelos: User, Prestamo, Multa con relaciones y validaciones
MongoDB Atlas Base de datos NoSQL en la nube para catálogo y logs Esquema flexible, 512 MB gratis, escalable, sin instalación local
Cluster M0 Servidor virtual gratuito que contiene todas las BDs de MongoDB Única opción gratuita, incluye replicación y backups automáticos
PyMongo Driver Python oficial para conectar Django con MongoDB API pythónica, operaciones CRUD directas, soporte MongoDB 7.x
Django Framework web que orquesta MySQL y MongoDB Usa ORM para MySQL y PyMongo para MongoDB en paralelo
Campo Común (libro_id) Entero que identifica el mismo libro en ambas BDs Permite relacionar préstamos (MySQL) con detalles del libro (MongoDB)
Connection String URL de conexión a MongoDB Atlas Formato: mongodb+srv://user:pass@cluster.mongodb.net/database
🔗 Ejemplo de Interacción MySQL ↔ MongoDB:
views.py - Consulta Híbrida
from django.shortcuts import render
from .models import Prestamo  # Modelo Django → MySQL
from django.conf import settings  # Para acceder a MongoDB

def detalle_prestamo(request, prestamo_id):
    # 1. Obtener préstamo de MySQL (Django ORM)
    prestamo = Prestamo.objects.get(id=prestamo_id)
    libro_id = prestamo.libro_id  # ← Campo común (ej: 5)
    
    # 2. Obtener detalles del libro de MongoDB (PyMongo)
    mongo_db = settings.MONGO_CLIENT['biblioteca_catalogo']
    libro = mongo_db.libros.find_one({"libro_id": libro_id})
    
    # 3. Combinar datos de ambas bases de datos
    context = {
        'usuario': prestamo.usuario.username,  # MySQL
        'fecha_prestamo': prestamo.fecha,      # MySQL
        'titulo': libro['titulo'],            # MongoDB
        'autor': libro['autor']['nombre'],     # MongoDB
        'isbn': libro['isbn'],                # MongoDB
        'categorias': libro['categorias']      # MongoDB
    }
    
    return render(request, 'detalle.html', context)

⚙️ Requisitos Previos (VERIFICAR ANTES DE COMENZAR)

Asegúrate de tener TODO listo antes de empezar:

1. Python 3.8 o superior

¿Cómo verificar?

python --version

Debe mostrar: Python 3.8.x, 3.9.x, 3.10.x o superior

❌ Si no está instalado: Descarga desde python.org/downloads

2. Cuenta de MongoDB Atlas (Gratuita)

  • ✅ Email verificado (Gmail, Outlook, etc.)
  • ✅ Conexión a Internet estable
  • ✅ Navegador moderno (Chrome, Firefox, Edge)

3. Editor de Código

Recomendado: Visual Studio Code (gratis)

4. Conocimientos Básicos

  • 💡 Python básico (variables, funciones, clases)
  • 💡 JSON (formato de datos)
  • 💡 Línea de comandos básica (cd, pip, python)
  • 💡 Django básico (opcional pero recomendado)

📅 Tiempo estimado total: 2-3 horas (primera vez)

🎯 Nivel de dificultad: Intermedio

📦 Dependencias a instalar: 4 paquetes Python

📊 Arquitectura del Sistema Híbrido (MySQL + MongoDB Atlas)

┌─────────────────────────────────────────────────────────────────────┐
│   NAVEGADOR WEB (Cliente)                                           │
│   - HTML/CSS/JavaScript/Bootstrap                                   │
│   - Peticiones HTTP                                                 │
└──────────────────────┬──────────────────────────────────────────────┘
                       │
                       │ GET /prestamos/
                       │ POST /prestar-libro/
                       │
                       ▼
┌─────────────────────────────────────────────────────────────────────┐
│   SERVIDOR DJANGO (Backend - Orquestador)                           │
│                                                                      │
│  ┌────────────────────────────────────────────────────────────┐   │
│  │  views.py (Lógica de Negocio)                              │   │
│  │                                                             │   │
│  │  def prestar_libro(request):                               │   │
│  │      # 1. Buscar libro en MongoDB                          │   │
│  │      libro = mongo_db.libros.find_one({"libro_id": 5})    │   │
│  │                                                             │   │
│  │      # 2. Crear préstamo en MySQL                          │   │
│  │      prestamo = Prestamo.objects.create(                   │   │
│  │          libro_id=5,  ← CAMPO COMÚN                        │   │
│  │          usuario=request.user                              │   │
│  │      )                                                      │   │
│  │                                                             │   │
│  │      # 3. Combinar datos para respuesta                    │   │
│  │      return render(request, 'prestamo.html', {             │   │
│  │          'libro': libro,        # MongoDB                  │   │
│  │          'prestamo': prestamo   # MySQL                    │   │
│  │      })                                                     │   │
│  └────────────────────────────────────────────────────────────┘   │
│                                                                      │
│  ┌──────────────────┐              ┌──────────────────────────┐   │
│  │  models.py       │              │  services/mongo.py       │   │
│  │  (Django ORM)    │              │  (PyMongo)               │   │
│  │                  │              │                          │   │
│  │  class Prestamo  │              │  client = MongoClient()  │   │
│  │  class Usuario   │              │  db = client['biblioteca']│   │
│  │  class Multa     │              │  libros = db.libros      │   │
│  └────────┬─────────┘              └──────────┬───────────────┘   │
│           │                                   │                    │
└───────────┼───────────────────────────────────┼────────────────────┘
            │                                   │
            │ Django ORM                        │ PyMongo Driver
            │ SQL Queries                       │ BSON/JSON
            │                                   │
            ▼                                   ▼
┌──────────────────────────┐      ┌───────────────────────────────────┐
│   MYSQL (Local/Xampp)    │      │   MONGODB ATLAS (Nube)            │
│                          │      │                                    │
│  📁 biblioteca_db        │      │  ☁️ BibliotecaCluster (M0 Free)   │
│                          │      │                                    │
│  📋 Tablas:              │      │  📁 biblioteca_catalogo (BD)       │
│  ┌─────────────────┐    │      │  ┌──────────────────────────┐    │
│  │ auth_user       │    │      │  │ libros (Colección)       │    │
│  │ ├─ id           │    │      │  │ {                        │    │
│  │ ├─ username     │    │      │  │   libro_id: 5, ← COMÚN  │    │
│  │ └─ email        │    │      │  │   titulo: "Cien Años...",│    │
│  └─────────────────┘    │      │  │   isbn: "978-0307...",   │    │
│                          │      │  │   autor: {               │    │
│  ┌─────────────────┐    │      │  │     nombre: "García...", │    │
│  │ biblioteca_prest│    │      │  │     pais: "Colombia"     │    │
│  │ ├─ id           │    │      │  │   },                     │    │
│  │ ├─ libro_id  ◄──┼────┼──────┼──┼─▶ categorias: [...],     │    │
│  │ ├─ usuario_id   │    │      │  │   stock: 10              │    │
│  │ ├─ fecha_prest  │    │      │  │ }                        │    │
│  │ └─ fecha_devol  │    │      │  └──────────────────────────┘    │
│  └─────────────────┘    │      │                                    │
│                          │      │  📁 biblioteca_logs (BD)          │
│  ┌─────────────────┐    │      │  ┌──────────────────────────┐    │
│  │ biblioteca_multa│    │      │  │ logs_actividad           │    │
│  │ ├─ id           │    │      │  │ {                        │    │
│  │ ├─ prestamo_id  │    │      │  │   usuario: "admin",      │    │
│  │ ├─ monto        │    │      │  │   accion: "PRESTAMO",    │    │
│  │ └─ pagada       │    │      │  │   libro_id: 5,           │    │
│  └─────────────────┘    │      │  │   timestamp: ISODate()   │    │
│                          │      │  │ }                        │    │
│  ⚙️ ACID Transactions   │      │  └──────────────────────────┘    │
│  🔗 Foreign Keys        │      │                                    │
│  📊 Relaciones 1-N      │      │  📁 biblioteca_estadisticas (BD)  │
└──────────────────────────┘      │  ┌──────────────────────────┐    │
                                  │  │ visitas                  │    │
                                  │  │ {                        │    │
                                  │  │   fecha: "2026-01-30",   │    │
                                  │  │   total_visitas: 120,    │    │
                                  │  │   libros_prestados: 15   │    │
                                  │  │ }                        │    │
                                  │  └──────────────────────────┘    │
                                  │                                    │
                                  │  💾 512 MB total                  │
                                  │  ✨ Esquema Flexible              │
                                  │  🔄 Replicación Automática        │
                                  └───────────────────────────────────┘

═══════════════════════════════════════════════════════════════════════
FLUJO DE DATOS - PRESTAR LIBRO:
═══════════════════════════════════════════════════════════════════════

1. 📝 Usuario selecciona libro "Cien Años de Soledad" (libro_id=5)

2. 🔍 Django consulta MongoDB Atlas:
   libro = db.libros.find_one({"libro_id": 5})
   → Obtiene: {titulo, autor, stock, categorias}

3. ✅ Django verifica stock disponible (MongoDB)
   if libro["stock"] > 0:

4. 💾 Django crea préstamo en MySQL:
   Prestamo.objects.create(
       libro_id=5,  ← Campo común
       usuario=request.user,
       fecha_prestamo=datetime.now()
   )

5. 🔄 Django actualiza stock en MongoDB:
   db.libros.update_one(
       {"libro_id": 5},
       {"$inc": {"stock": -1}}
   )

6. 📊 Django registra log en MongoDB:
   db.logs_actividad.insert_one({
       "accion": "PRESTAMO",
       "libro_id": 5,
       "usuario": request.user.username,
       "timestamp": datetime.now()
   })

7. ✨ Django combina datos y muestra:
   - Nombre usuario (MySQL)
   - Título del libro (MongoDB)
   - Fecha préstamo (MySQL)
   - Autor del libro (MongoDB)

═══════════════════════════════════════════════════════════════════════
¿POR QUÉ ESTE DISEÑO HÍBRIDO?
═══════════════════════════════════════════════════════════════════════

✅ MySQL: Préstamos necesitan ACID (si falla préstamo, hacer rollback)
✅ MongoDB: Libros tienen datos flexibles (algunos tienen ebook, otros no)
✅ Campo común (libro_id): Permite unir datos de ambas BDs
✅ Escalabilidad: Catálogo crece sin afectar transacciones críticas
                    

⏱️ Tiempo Estimado Total: 2-3 horas

Fase Tiempo Qué harás
PASO 1: MongoDB Atlas 20 min Crear cuenta, cluster gratuito, usuario de BD, whitelist IPs
PASO 2: Proyecto Django 15 min Crear proyecto, instalar PyMongo y dnspython
PASO 3: Conexión MongoDB 15 min Configurar connection string, probar conexión, crear primera BD
PASO 4: Organizar Bases de Datos 20 min Crear múltiples BDs en el cluster, configurar acceso en settings.py
PASO 5: Operaciones CRUD 35 min insert_one, find, update_one, delete_one, queries avanzadas
PASO 6: Views Django 25 min Crear views para listar, crear, editar y eliminar documentos
PASO 7: Templates HTML 20 min Formularios y listados con Bootstrap 5
PASO 8: Pruebas y Depuración 20 min Verificar CRUD completo, monitorear uso en Atlas

🔄 ¿Por Qué Sistema Híbrido MySQL + MongoDB?

Este diseño aprovecha las fortalezas de cada base de datos:

Usa MySQL Para... Usa MongoDB Para... Conecta Con...
Préstamos (Prestamo model) Catálogo de libros (colección libros) Campo común:
libro_id (INT)


MySQL: prestamo.libro_id
↕️
MongoDB: libro.libro_id
Usuarios (auth_user) Reseñas (subdocumentos flexibles)
Multas (Multa model) Logs de actividad (eventos del sistema)
✅ Ventajas del Sistema Híbrido:
  • MySQL: Garantiza que un préstamo no se pierda (transacciones ACID)
  • MongoDB: Permite agregar campos al catálogo sin migraciones (ebook, audiobook, etc.)
  • Escalabilidad: Catálogo puede crecer a millones de libros sin afectar MySQL
  • Performance: Queries de préstamos (frecuentes) son rápidas en MySQL
  • Flexibilidad: Cada libro puede tener diferentes metadatos sin cambiar esquema
📊 Ejemplo Real de Consulta Híbrida:
Caso: Mostrar préstamos activos con detalles del libro
from .models import Prestamo
from django.conf import settings

def prestamos_activos(request):
    # 1. Obtener préstamos pendientes de MySQL
    prestamos = Prestamo.objects.filter(
        fecha_devolucion__isnull=True
    ).select_related('usuario')  # Optimización con JOIN en MySQL
    
    # 2. Conectar a MongoDB
    mongo_db = settings.MONGO_CLIENT['biblioteca_catalogo']
    
    # 3. Para cada préstamo, obtener datos del libro
    resultado = []
    for prestamo in prestamos:
        # Buscar libro en MongoDB usando el campo común
        libro = mongo_db.libros.find_one({
            "libro_id": prestamo.libro_id  # ← Campo común
        })
        
        # Combinar datos
        resultado.append({
            'prestamo_id': prestamo.id,           # MySQL
            'usuario': prestamo.usuario.username, # MySQL (relación FK)
            'fecha': prestamo.fecha_prestamo,     # MySQL
            'titulo': libro['titulo'],            # MongoDB
            'autor': libro['autor']['nombre'],     # MongoDB (subdocumento)
            'portada': libro.get('portada_url'),  # MongoDB (opcional)
        })
    
    return render(request, 'prestamos.html', {'prestamos': resultado})

💡 En esta práctica implementarás:

  • ✅ Modelos Django para MySQL (Prestamo, Usuario, Multa)
  • ✅ Colecciones MongoDB para catálogo (libros, autores)
  • ✅ Campo común libro_id en ambas BDs
  • ✅ Views que combinan datos de MySQL y MongoDB
  • ✅ Templates que muestran información híbrida

1. 📚 Saber - Dimensión Conceptual

🔵 ¿Qué es un Cluster en MongoDB Atlas?

Un cluster es un conjunto de servidores (nodos) que trabajan juntos para almacenar y gestionar tus datos en MongoDB Atlas. Piensa en él como una "máquina virtual en la nube" dedicada a tu base de datos.

📊 Componentes de un Cluster:
  • Nodos (Servers): Computadoras que almacenan copias de tus datos
  • Réplicas: Copias redundantes para alta disponibilidad (si un servidor falla, otro toma su lugar)
  • Almacenamiento: Espacio en disco donde se guardan tus documentos (512 MB gratis en M0)
  • Memoria RAM: Para ejecutar queries rápidamente
  • CPU: Procesamiento de operaciones (lecturas/escrituras)
🆓 Cluster Gratuito (M0) en 2026:
Característica Versión Gratuita (M0) Limitación
Clusters permitidos 1 cluster por cuenta ❌ No puedes crear múltiples clusters
Bases de datos Ilimitadas dentro del cluster ✅ Puedes crear: biblioteca_db, logs_db, usuarios_db, etc.
Colecciones Ilimitadas ✅ En cada BD: libros, autores, reseñas, etc.
Almacenamiento 512 MB Suficiente para ~100,000 documentos pequeños
RAM Compartida Rendimiento moderado para desarrollo
Backups automáticos ❌ No incluidos Debes hacer backups manuales
💡 Ejemplo Práctico:
UN CLUSTER = Un "servidor virtual" que contiene:
│
├── biblioteca_logs (Base de Datos 1 - Logs)
│   ├── logs_actividad (Colección)
│   ├── logs_errores (Colección)
│   └── logs_auditoria (Colección)
│
├── biblioteca_catalogo (Base de Datos 2 - Catálogo)
│   ├── libros (Colección)
│   ├── autores (Colección)
│   ├── categorias (Colección)
│   └── reseñas (Colección)
│
├── biblioteca_usuarios (Base de Datos 3 - Usuarios)
│   ├── usuarios (Colección)
│   ├── sesiones (Colección)
│   └── preferencias (Colección)
│
└── biblioteca_estadisticas (Base de Datos 4 - Analytics)
    ├── visitas (Colección)
    ├── prestamos (Colección)
    └── tendencias (Colección)

✅ PERMITIDO: Crear múltiples bases de datos en el mismo cluster
❌ NO PERMITIDO: Crear un segundo cluster (requiere cuenta de pago)
                    
🔄 ¿Por qué esta limitación?
  • Costos de infraestructura: Cada cluster consume recursos reales (CPU, RAM, almacenamiento)
  • Prevención de abuso: Evitar que usuarios creen cientos de cuentas gratuitas
  • Incentivo a upgrade: Para proyectos grandes, necesitas plan de pago ($0.08/hora M10)

⚠️ IMPORTANTE - Versión Gratuita 2026:

  • Solo puedes tener 1 cluster activo por cuenta
  • Si intentas crear un segundo cluster, Atlas te pedirá eliminar el primero o pagar
  • Solución: Organiza TODAS tus aplicaciones en bases de datos separadas dentro del mismo cluster
  • Ejemplo: biblioteca_db, tienda_db, blog_db → TODAS en el mismo cluster

¿Qué es NoSQL? (Explicación Completa)

NoSQL (Not Only SQL) son bases de datos no relacionales diseñadas para manejar grandes volúmenes de datos no estructurados, semi-estructurados o con esquemas flexibles. Surgieron en la década de 2000 para resolver limitaciones de las bases de datos SQL tradicionales en contextos de Big Data, alta concurrencia y esquemas variables.

🎯 Diferencias SQL vs NoSQL (Detallado):

Aspecto SQL (Relacional) NoSQL
Estructura Tablas con filas y columnas (2D) Documentos JSON, clave-valor, grafos, columnas
Esquema Fijo y predefinido (CREATE TABLE) Flexible y dinámico (schema-less o schema-on-read)
Escalabilidad Vertical (más CPU/RAM en 1 servidor) Horizontal (distribuir en múltiples servidores)
Transacciones ACID completo (Atomicidad, Consistencia, Aislamiento, Durabilidad) BASE (Basically Available, Soft state, Eventual consistency)
Consultas SQL estándar (SELECT, JOIN, WHERE) APIs específicas (find, aggregate, get)
Relaciones JOINs entre tablas (Foreign Keys) Embedding (documentos anidados) o Referencing
Velocidad Lento en escrituras masivas Rápido en lecturas/escrituras a gran escala
Casos de Uso Sistemas bancarios, ERP, CRM Redes sociales, IoT, catálogos, logs

📊 ACID vs BASE - ¿Qué significan?

🔐 ACID (SQL - Bancos, Transacciones Críticas)
  • Atomicity (Atomicidad): Transacción completa o nada
    • Ejemplo: Transferencia bancaria $100 A→B
    • Se ejecuta: Restar $100 de A + Sumar $100 a B
    • Si falla cualquier paso, se revierte TODO (rollback)
  • Consistency (Consistencia): Datos siempre válidos
    • Las reglas de integridad se cumplen SIEMPRE
    • Ejemplo: Balance total = $1000 antes y después
  • Isolation (Aislamiento): Transacciones no interfieren
    • Dos usuarios modificando la misma fila → no se mezclan
    • Locks, niveles de aislamiento (READ COMMITTED, etc.)
  • Durability (Durabilidad): Datos permanentes tras commit
    • Si se confirma la transacción, persiste incluso si el servidor falla
🌐 BASE (NoSQL - Aplicaciones Web, Alta Disponibilidad)
  • Basically Available (Básicamente Disponible):
    • El sistema responde SIEMPRE, aunque sea con datos parciales
    • Prioridad: Disponibilidad > Consistencia inmediata
  • Soft state (Estado Suave):
    • Los datos pueden cambiar con el tiempo (sin input)
    • Ejemplo: Replicación eventual entre servidores
  • Eventual consistency (Consistencia Eventual):
    • Los datos serán consistentes EVENTUALMENTE (no inmediatamente)
    • Ejemplo: Post en red social → visible en 1-2 segundos en todos los nodos

💡 Ejemplo Práctico: Transferencia Bancaria

SQL (ACID) - Banco
BEGIN TRANSACTION;
    UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;  -- Restar de A
    UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;  -- Sumar a B
    
    -- Si hay error en cualquier línea:
    ROLLBACK;  -- ❌ Se cancela TODO
    
    -- Si todo OK:
    COMMIT;  -- ✅ Cambios permanentes INMEDIATAMENTE
END TRANSACTION;
NoSQL (BASE) - Red Social
// Usuario publica post
db.posts.insert_one({
    "usuario": "juan123",
    "contenido": "¡Hola mundo!",
    "fecha": new Date()
});

// ✅ Se guarda INMEDIATAMENTE en el nodo primario
// ⏳ Se replica EVENTUALMENTE a nodos secundarios (1-2 seg)
// Mientras tanto, algunos usuarios pueden NO ver el post
// TRADE-OFF: Disponibilidad rápida > Consistencia inmediata

Tipos de Bases de Datos NoSQL (Explicado con Ejemplos)

Tipo Ejemplos Características Casos de Uso
Documentales MongoDB, CouchDB, Firestore Almacena documentos JSON/BSON con estructura flexible Apps web, catálogos de productos, CMS
Clave-Valor Redis, DynamoDB, Memcached Par clave-valor simple (como un diccionario) Caché, sesiones de usuario, contadores
Columnares Cassandra, HBase, ScyllaDB Almacena datos por columnas (no por filas) Big Data, analytics, series temporales
Grafos Neo4j, ArangoDB, OrientDB Nodos conectados por relaciones (aristas) Redes sociales, recomendaciones, rutas

📝 Ejemplos de Cada Tipo de NoSQL

1️⃣ Documental (MongoDB)
Documento de Usuario
{
  "_id": ObjectId("..."),
  "nombre": "Juan Pérez",
  "email": "juan@example.com",
  "direcciones": [  // Array de subdocumentos
    { "tipo": "casa", "calle": "Av. 123", "ciudad": "CDMX" },
    { "tipo": "trabajo", "calle": "Calle 456", "ciudad": "GDL" }
  ],
  "preferencias": {  // Documento anidado
    "tema": "oscuro",
    "idioma": "es"
  }
}
2️⃣ Clave-Valor (Redis)
Cache de Sesiones
// Guardar sesión
SET session:abc123 "{'user_id': 42, 'name': 'Juan', 'role': 'admin'}"
EXPIRE session:abc123 3600  // Expira en 1 hora

// Obtener sesión
GET session:abc123  // → "{'user_id': 42, ...}"

// Contador de visitas
INCR page_views:home  // Incrementa atómicamente
3️⃣ Columnar (Cassandra)
Tabla de Eventos (Series Temporales)
// CREATE TABLE events (
//   user_id UUID,
//   timestamp TIMESTAMP,
//   event_type TEXT,
//   data TEXT,
//   PRIMARY KEY (user_id, timestamp)
// );

// Los datos se almacenan por columnas para queries analíticas:
// Fila: user_id | timestamp | event_type | data
// Pero físicamente:
// Columna user_id:    [uuid1, uuid2, uuid3, ...]
// Columna timestamp:  [t1, t2, t3, ...]
// Columna event_type: ["click", "view", ...]
4️⃣ Grafos (Neo4j)
Red Social (Cypher Query Language)
// Crear nodos y relaciones
CREATE (juan:Usuario {nombre: "Juan"})
CREATE (maria:Usuario {nombre: "María"})
CREATE (juan)-[:SIGUE]->(maria)
CREATE (juan)-[:AMIGO_DE]->(pedro)

// Query: ¿Quiénes son amigos de amigos de Juan?
MATCH (juan:Usuario {nombre: "Juan"})-[:AMIGO_DE*2]-(amigo)
RETURN amigo.nombre

🔍 ¿Cuándo Usar Cada Tipo?

  • Documentales (MongoDB): Cuando tus objetos de negocio tienen estructura compleja
    • ✓ E-commerce (productos con atributos variables)
    • ✓ CMS (artículos, páginas con metadatos)
    • ✓ Catálogos con jerarquías
  • Clave-Valor (Redis): Cuando necesitas acceso ultra-rápido a valores simples
    • ✓ Cache de consultas
    • ✓ Sesiones de usuario
    • ✓ Rate limiting (límite de peticiones)
  • Columnares (Cassandra): Cuando procesas enormes volúmenes de datos
    • ✓ Logs de aplicaciones (millones de eventos/día)
    • ✓ Analytics de Big Data
    • ✓ IoT (sensores enviando datos constantemente)
  • Grafos (Neo4j): Cuando las relaciones son tan importantes como los datos
    • ✓ Redes sociales (¿quién conoce a quién?)
    • ✓ Detección de fraude (patrones sospechosos)
    • ✓ Motores de recomendación

MongoDB - Base de Datos Documental (Explicación)

MongoDB es la base de datos NoSQL más popular del mundo (usado por Uber, Airbnb, eBay, LinkedIn). Almacena datos en documentos similares a JSON con esquemas dinámicos, lo que facilita la integración con aplicaciones modernas. MongoDB usa BSON (Binary JSON) internamente para mayor eficiencia.

🌟 ¿Por Qué MongoDB es tan Popular?

  • Esquema Flexible: Agregar campos sin migraciones
    • Documento 1: {"nombre": "Juan", "edad": 30}
    • Documento 2: {"nombre": "María", "edad": 25, "ciudad": "CDMX"} ← ¡Nuevo campo!
    • ✅ Sin error, sin ALTER TABLE
  • Alta Escalabilidad: Sharding horizontal automático
    • Divide datos entre múltiples servidores
    • Ejemplo: 1TB de datos → 10 servidores con 100GB cada uno
  • Replicación Nativa: Alta disponibilidad
    • Replica Set: 1 primario + 2+ secundarios
    • Si el primario falla → secundario se promociona automáticamente
  • Queries Potentes: Agregaciones, índices geoespaciales
    • Buscar documentos por ubicación GPS
    • Pipelines de agregación (como GROUP BY en SQL)
  • Integración con JavaScript: JSON nativo
    • Frontend envía JSON → MongoDB almacena BSON → Backend devuelve JSON
    • Sin conversión de datos
Concepto SQL Equivalente MongoDB Descripción Detallada
Base de Datos Database Contenedor lógico de colecciones (ej: biblioteca_db)
Tabla Collection Grupo de documentos (ej: libros, usuarios)
Fila/Registro Document Objeto JSON/BSON individual (ej: un libro específico)
Columna Field Clave en un documento (ej: "titulo", "precio")
JOIN Embedding/Referencing/$lookup Documentos anidados o referencias entre colecciones
PRIMARY KEY _id Identificador único (ObjectId de 12 bytes, generado automáticamente)
INDEX Index Índices B-tree, hash, geoespaciales, text (búsqueda full-text)
SELECT * FROM db.collection.find() Retorna cursor con documentos que cumplen criterio
INSERT INTO db.collection.insert_one() Inserta un documento, genera _id si no existe
UPDATE db.collection.update_one() Actualiza campos específicos con operadores ($set, $inc)
DELETE FROM db.collection.delete_one() Elimina documento que cumple filtro

📊 Comparación SQL vs MongoDB (Mismo Caso de Uso)

Escenario: Almacenar Libro con Autor
SQL (MySQL) - Normalizado
-- Tabla autores
CREATE TABLE autores (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nombre VARCHAR(200),
    pais VARCHAR(100),
    fecha_nacimiento DATE
);

-- Tabla libros
CREATE TABLE libros (
    id INT PRIMARY KEY AUTO_INCREMENT,
    titulo VARCHAR(200),
    isbn VARCHAR(20),
    autor_id INT,  -- ⚠️ Foreign Key
    precio DECIMAL(10,2),
    stock INT,
    FOREIGN KEY (autor_id) REFERENCES autores(id)
);

-- Insertar datos (2 queries separados)
INSERT INTO autores (nombre, pais, fecha_nacimiento)
VALUES ('Gabriel García Márquez', 'Colombia', '1927-03-06');
-- Supongamos que retorna id = 1

INSERT INTO libros (titulo, isbn, autor_id, precio, stock)
VALUES ('Cien Años de Soledad', '978-0307474728', 1, 15.99, 45);

-- Consultar (requiere JOIN)
SELECT l.titulo, l.isbn, l.precio, a.nombre AS autor, a.pais
FROM libros l
INNER JOIN autores a ON l.autor_id = a.id
WHERE l.titulo LIKE '%Cien%';
MongoDB - Desnormalizado (Embedding)
// Insertar todo en UN SOLO documento (1 query)
db.libros.insert_one({
    "titulo": "Cien Años de Soledad",
    "isbn": "978-0307474728",
    "autor": {  // ⭐ Documento anidado (embedding)
        "nombre": "Gabriel García Márquez",
        "pais": "Colombia",
        "fecha_nacimiento": ISODate("1927-03-06")
    },
    "precio": 15.99,
    "stock": 45
})

// Consultar (SIN JOIN, más rápido)
db.libros.find({ "titulo": { $regex: "Cien" } })
✅ Ventajas MongoDB en este caso:
  • Una sola operación de inserción
  • No necesita JOIN (consulta más rápida)
  • Datos relacionados juntos (localidad de datos)
  • Fácil de serializar a JSON para API
⚠️ Desventajas MongoDB en este caso:
  • Si el autor escribe 100 libros → datos de autor duplicados 100 veces
  • Si cambia el país del autor → hay que actualizar 100 documentos
  • Solución: Usar Referencing (similar a FK) cuando hay duplicación

🔗 Embedding vs Referencing en MongoDB

Embedding (Anidado) Referencing (Referencias)
Cuándo: Relación 1-a-pocos, datos leídos juntos Cuándo: Relación muchos-a-muchos, datos grandes
Ventaja: 1 sola query, ultra rápido Ventaja: Sin duplicación, actualizaciones centralizadas
Desventaja: Duplicación de datos Desventaja: Requiere múltiples queries o $lookup
Ejemplo: Libro + Autor (si autor escribe 1-2 libros) Ejemplo: Libro + Categorías (misma categoría en 1000+ libros)
Ejemplo de Referencing
// Colección autores (separada)
{
    "_id": ObjectId("60a1b2c3d4e5f6g7h8i9j0k1"),
    "nombre": "Gabriel García Márquez",
    "pais": "Colombia"
}

// Colección libros (referencia al autor)
{
    "_id": ObjectId("..."),
    "titulo": "Cien Años de Soledad",
    "autor_id": ObjectId("60a1b2c3d4e5f6g7h8i9j0k1"),  // ← Referencia
    "precio": 15.99
}

// Query con $lookup (JOIN de MongoDB)
db.libros.aggregate([
    {
        $lookup: {
            from: "autores",
            localField: "autor_id",
            foreignField: "_id",
            as: "autor"
        }
    }
])

2. 🌐 MongoDB y MongoDB Atlas

¿Qué es MongoDB Atlas?

MongoDB Atlas es la plataforma de base de datos en la nube totalmente administrada de MongoDB. Ofrece despliegue automatizado, monitoreo, respaldos automáticos y escalabilidad.

🌟 Ventajas de MongoDB Atlas:

  • Tier Gratuito: 512 MB de almacenamiento gratis
  • Sin Configuración: Cluster listo en minutos
  • Alta Disponibilidad: Replicación automática
  • Backups Automáticos: Puntos de restauración
  • Seguridad: Encriptación y autenticación
  • Escalabilidad: Crece según tus necesidades

Estructura de Datos en MongoDB

Ejemplo de documento MongoDB
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),  // ID único generado automáticamente
  "titulo": "Cien Años de Soledad",  // Campo de texto
  "isbn": "978-0307474728",  // ISBN del libro
  "autor": {  // Documento anidado (embedding)
    "nombre": "Gabriel García Márquez",
    "pais": "Colombia",
    "fecha_nacimiento": ISODate("1927-03-06")  // Tipo Date de MongoDB
  },
  "categorias": ["Realismo Mágico", "Literatura"],  // Array de strings
  "precio": 15.99,  // Número decimal
  "stock": 45,  // Número entero
  "disponible": true,  // Booleano
  "resenas": [  // Array de documentos anidados
    {
      "usuario": "usuario123",
      "calificacion": 5,
      "comentario": "Obra maestra",
      "fecha": ISODate("2026-01-10")
    }
  ],
  "metadata": {  // Metadata adicional flexible
    "fecha_creacion": ISODate("2026-01-01"),
    "ultima_actualizacion": ISODate("2026-01-15")
  }
}

3. ⚙️ Configuración de MongoDB Atlas (PASO A PASO DETALLADO)

⏱️ FASE 1: Crear Cuenta MongoDB Atlas (20 minutos)

Objetivo: Registrar cuenta gratuita y configurar organización inicial

Requisitos: Email válido, conexión a internet

📋 Paso 1: Registro de Cuenta (5 min)

1 Abrir página de registro
  1. Abre tu navegador (Chrome, Firefox, Edge)
  2. Ve a: https://www.mongodb.com/cloud/atlas/register
  3. Espera a que cargue el formulario de registro
2 Completar formulario de registro
💡 IMPORTANTE: Registro con Google vs Manual

Si usas "Sign up with Google":

  • ✅ MongoDB tomará automáticamente tu nombre y apellido de tu cuenta de Google
  • ✅ NO te pedirá llenar el formulario de registro completo
  • ✅ Irás directo a la pantalla de bienvenida (más rápido)
  • ✅ NO necesitarás verificar email (Google ya lo verificó)

Si usas "Registro Manual":

  • 📝 Deberás llenar todos los campos del formulario
  • 📧 Recibirás un email de verificación
  • ⏳ Tomará 2-3 minutos más

Opción 1: Registro con Google (⚡ MÁS RÁPIDO - RECOMENDADO):

  • 🔘 Haz clic en el botón "Sign up with Google"
  • 📧 Selecciona tu cuenta de Gmail
  • ✅ Autoriza el acceso cuando aparezca el popup
  • 🎉 Listo! Irás directo al dashboard (NO hay formulario adicional)

Opción 2: Registro manual (solo si no tienes Gmail):

  • ✍️ First Name: Tu nombre
  • ✍️ Last Name: Tu apellido
  • 📧 Email: tu_email@outlook.com (o cualquier email)
  • 🔑 Password: Contraseña segura (mín. 8 caracteres, con mayúsculas y números)
  • ☑️ Marca la casilla "I agree to the Terms of Service and Privacy Policy"
  • 🟢 Haz clic en "Create Account"
3 Verificar tu email (SOLO si usaste registro manual)
  1. 📬 Abre tu bandeja de entrada de email
  2. 🔍 Busca email de "MongoDB Cloud" o "noreply@mongodb.com"
  3. 📧 Abre el email (revisa spam si no aparece)
  4. 🔗 Haz clic en el botón "Verify Email"
  5. ✅ Deberías ver mensaje: "Email verified successfully"

✅ CHECKPOINT 1: Cuenta creada exitosamente

Deberías ver:

  • ✅ Pantalla de bienvenida de MongoDB Atlas
  • ✅ Mensaje "Welcome to MongoDB Atlas"
  • ✅ Tu email en la esquina superior derecha

Si NO ves esto: Revisa tu email y verifica tu cuenta antes de continuar

📋 Paso 2: Configurar Organización y Proyecto (5 min)

1 Completar cuestionario inicial (aparece solo la primera vez)

Atlas te hará algunas preguntas. Responde así:

  • What is your goal today? → Selecciona "Learn MongoDB"
  • What type of application are you building? → Selecciona "Web Application"
  • What is your preferred language? → Selecciona "Python"
  • 🟢 Haz clic en "Finish"
2 Crear Organización
  1. 🏢 En la pantalla principal, localiza la sección "Organizations"
  2. Si ya tienes una organización creada automáticamente, puedes usarla
  3. Si quieres crear una nueva: Haz clic en "Create New Organization"
  4. 📝 Organization Name: UTH-2026
  5. 🟢 Haz clic en "Next"
  6. ⏭️ En "Add Members" → Haz clic en "Create Organization" (puedes saltarte este paso)
3 Crear Proyecto
  1. 📁 Haz clic en "New Project" (botón verde en la esquina superior derecha)
  2. 📝 Project Name: Biblioteca-API
  3. 🟢 Haz clic en "Next"
  4. ⏭️ En "Add Members" → Haz clic en "Create Project" (puedes saltarte este paso)

✅ CHECKPOINT 2: Proyecto creado

Deberías ver:

  • ✅ Mensaje "Project Created Successfully"
  • ✅ Nombre del proyecto "Biblioteca-API" en la parte superior
  • ✅ Botón verde "Build a Database" o "Create a Deployment"

📋 Paso 3: Crear Cluster Gratuito (10 min)

⏱️ FASE 2: Configurar Cluster de MongoDB (15 minutos)

Objetivo: Crear base de datos gratuita en la nube (512 MB)

⚠️ IMPORTANTE - LIMITACIÓN VERSIÓN GRATUITA 2026:

  • 🔴 Solo puedes crear 1 cluster gratuito (M0) por cuenta
  • ❌ Si intentas crear un segundo cluster, Atlas te pedirá:
    • Eliminar el cluster existente, O
    • Actualizar a un plan de pago (M10 desde $0.08/hora)
  • Solución: Crea MÚLTIPLES BASES DE DATOS dentro del mismo cluster
    • Ejemplo: biblioteca_logs, biblioteca_catalogo, biblioteca_usuarios, biblioteca_estadisticas
    • Cada base de datos puede tener múltiples colecciones
    • Todas comparten los 512 MB del cluster gratuito
🏗️ Arquitectura Recomendada 2026:
BibliotecaCluster (Tu único cluster M0 gratuito)
│
├── biblioteca_logs (Base de Datos 1 - Logs)
│   ├── logs_actividad
│   ├── logs_errores
│   └── logs_auditoria
│
├── biblioteca_catalogo (Base de Datos 2 - Catálogo)
│   ├── libros
│   ├── autores
│   ├── categorias
│   └── reseñas
│
├── biblioteca_usuarios (Base de Datos 3 - Usuarios)
│   ├── usuarios
│   ├── sesiones
│   └── preferencias
│
└── biblioteca_estadisticas (Base de Datos 4 - Analytics)
    ├── visitas
    ├── prestamos
    └── tendencias

✅ Esta estructura te permite tener TODO en el mismo cluster gratuito
✅ Sin necesidad de crear múltiples clusters
                        
1 Iniciar creación de cluster (TU ÚNICO CLUSTER GRATUITO)
  1. 🟢 Haz clic en el botón grande verde "Build a Database" o "Create"
  2. 💚 Espera a que cargue la página de opciones de deployment
  3. ⚠️ RECUERDA: Este será tu ÚNICO cluster gratuito, nómbralo bien
2 Seleccionar plan GRATUITO (IMPORTANTE)

Verás 3 opciones de planes. Elige esta:

✅ SELECCIONA: M0 FREE (Shared)

  • 💰 Price: FREE (forever)
  • 💾 Storage: 512 MB
  • 🌐 Shared RAM: Shared vCPU
  • 📊 Ideal para: Aprendizaje, desarrollo, proyectos pequeños

🟢 Haz clic en el botón "Create" debajo de esta opción

❌ NO SELECCIONES:

  • M10: Dedicated ($0.08/hr) - Este es PAGO
  • Serverless: Pay as you go - Este también es PAGO
3 Configurar detalles del cluster

Ahora configura estos campos EXACTAMENTE como se indica:

Campo Valor Recomendado ¿Dónde está?
Cloud Provider AWS (Amazon Web Services) Primera sección, tres opciones: AWS, Google Cloud, Azure
Region us-east-1 (N. Virginia)
o cualquier región con ⭐ FREE TIER AVAILABLE
Lista desplegable con regiones del mundo
Cluster Tier M0 Sandbox (ya seleccionado) No lo cambies, debe decir "FREE" o "$0"
Cluster Name BibliotecaCluster
(sin espacios, sin acentos)
Campo de texto en la parte inferior

🟢 Haz clic en "Create Deployment" o "Create"

⏳ Espera mientras se crea el cluster...

Verás una pantalla de carga con mensaje: "Creating your cluster..."

Tiempo de espera: 1-3 minutos

NO cierres la ventana ni refresques la página

📋 Paso 4: Configurar Seguridad (Usuario y Red)

🔒 IMPORTANTE: Configuración de Seguridad

Atlas te pedirá configurar DOS cosas críticas: Usuario de BD y Acceso de Red

1 Crear Usuario de Base de Datos (Database User)

Deberías ver un formulario titulado "Security Quickstart". Completa así:

Credenciales de Usuario MongoDB
# GUARDA ESTOS DATOS EN UN LUGAR SEGURO (Notepad, bloc de notas)

Username: biblioteca_admin
Password: Biblioteca2026!  # Usa esta o crea una más segura

# IMPORTANTE: Copia EXACTAMENTE como los escribiste
# La contraseña es CASE-SENSITIVE (distingue mayúsculas)
  1. 📝 Authentication Method: Deja seleccionado "Password"
  2. 👤 Username: Escribe biblioteca_admin
  3. 🔑 Password: Escribe Biblioteca2026! (o tu contraseña preferida)
  4. 💾 COPIA Y GUARDA usuario y contraseña en un archivo .txt en tu computadora
  5. ☑️ Database User Privileges: Deja "Atlas admin" o "Read and write to any database"
  6. 🟢 Haz clic en "Create User"
2 Configurar Acceso de Red (IP Whitelist)

Ahora verás una sección "Where would you like to connect from?"

Opción 1: Acceso desde cualquier IP (DESARROLLO - MÁS FÁCIL)

  1. 🌐 Haz clic en "Add My Current IP Address"
  2. Verás tu IP agregada (algo como: 192.168.x.x)
  3. 📝 Para permitir CUALQUIER IP (útil para desarrollo):
    • Haz clic en "Add a Different IP Address"
    • En el campo "IP Address" escribe: 0.0.0.0/0
    • En "Description" escribe: Acceso desarrollo
    • Haz clic en "Add Entry"
  4. 🟢 Haz clic en "Finish and Close"

⚠️ Seguridad en Producción:

  • 0.0.0.0/0 permite acceso desde CUALQUIER IP del mundo
  • ✅ Está bien para DESARROLLO y APRENDIZAJE
  • ❌ NUNCA uses esto en producción con datos reales
  • ✅ En producción: Agrega solo IPs específicas de tu servidor

✅ CHECKPOINT 3: Cluster configurado exitosamente

Deberías ver:

  • ✅ Mensaje "Congratulations on setting up access rules!"
  • ✅ Tu cluster "BibliotecaCluster" aparece en el dashboard
  • ✅ Estado del cluster: "Active" (bolita verde)
  • ✅ Botón "Connect" disponible

Si el cluster dice "Creating..." espera 1-2 minutos más

📋 Paso 5: Obtener Connection String (MUY IMPORTANTE)

🔗 ¿Qué es el Connection String?

Es la "dirección de tu base de datos" que Django usará para conectarse a MongoDB Atlas.

Formato: mongodb+srv://usuario:contraseña@servidor/basededatos

1 Abrir configuración de conexión
  1. 🔍 Localiza tu cluster "BibliotecaCluster" en el dashboard
  2. 🔘 Haz clic en el botón "Connect" (al lado derecho del nombre del cluster)
  3. 💚 Aparecerá un modal con varias opciones de conexión
2 Seleccionar método de conexión (¡IMPORTANTE!)
🎯 ¿Qué Opción Seleccionar?

Aparecerá un modal con 5 opciones de conexión. Para este proyecto Django, selecciona:

Drivers → "Access your Atlas data using MongoDB's native drivers"

Opción ¿Para Qué Sirve? ¿Usarlo en Este Proyecto?
✅ Drivers
"Access your Atlas data using MongoDB's native drivers (e.g. Node.js, Go, etc.)"
Obtener connection string para conectar desde código (Python, Node.js, Java, etc.) ✅ SÍ - Selecciona esta
Para Django con PyMongo
❌ Compass
"Explore, modify, and visualize your data with MongoDB's GUI"
Aplicación de escritorio para explorar datos visualmente (como MySQL Workbench) ❌ NO - No la necesitas
(Opcional para ver datos visualmente)
❌ Shell
"Quickly add & update data using MongoDB's Javascript command-line interface"
Línea de comandos para ejecutar queries directamente (como mysql CLI) ❌ NO - No la necesitas
(Útil para debugging avanzado)
❌ MongoDB for VS Code
"Work with your data in MongoDB directly from your VS Code environment"
Extensión de VS Code para ver y editar datos desde el editor ❌ NO - No la necesitas
(Opcional para ver datos en VS Code)
❌ Atlas SQL
"Easily connect SQL tools to Atlas for data analysis and visualization"
Conectar herramientas SQL tradicionales a MongoDB (BI tools) ❌ NO - No la necesitas
(Para reportes con Tableau, Power BI)
⚠️ IMPORTANTE - Pasos Exactos:
  1. Haz clic en el botón "Connect" de tu cluster
  2. En el modal, busca y haz clic en "Drivers" (primera opción generalmente)
  3. Verás el texto: "Access your Atlas data using MongoDB's native drivers"
  4. NO selecciones Compass, Shell, VS Code ni Atlas SQL para este proyecto
3 Configurar driver
  1. 📦 Driver: Selecciona "Python"
  2. 🔢 Version: Selecciona "4.0 or later"
4 Copiar y modificar Connection String

Verás un recuadro con código similar a este:

Connection String Original (NO USAR DIRECTAMENTE)
mongodb+srv://biblioteca_admin:<password>@bibliotecacluster.xxxxx.mongodb.net/?retryWrites=true&w=majority

🔄 Debes MODIFICARLO así:

  1. 📋 Copia el connection string completo (botón "Copy" al lado)
  2. 📝 Pégalo en Notepad o cualquier editor de texto
  3. 🔑 Reemplaza <password> con tu contraseña REAL: Biblioteca2026!
  4. 🗄️ Agrega el nombre de la base de datos después de .mongodb.net/
✅ Connection String CORRECTO (USAR ESTE)
# ANTES (incorrecto):
mongodb+srv://biblioteca_admin:<password>@bibliotecacluster.xxxxx.mongodb.net/?retryWrites=true&w=majority

# DESPUÉS (correcto):
mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.xxxxx.mongodb.net/biblioteca_logs?retryWrites=true&w=majority

# Componentes explicados:
# mongodb+srv://       → Protocolo de conexión (SRV para Atlas)
# biblioteca_admin:   → Tu usuario de BD
# Biblioteca2026!     → Tu contraseña (SIN los símbolos < >)
# @bibliotecacluster  → Nombre de tu cluster
# .xxxxx.mongodb.net  → Servidor de Atlas (único para tu cluster)
# /biblioteca_logs    → Nombre de la base de datos (la que crearemos)
# ?retryWrites=true   → Parámetros de configuración
5 Guardar Connection String de forma SEGURA
  1. 💾 Crea un archivo mongodb_credentials.txt en tu proyecto
  2. 📝 Pega el connection string COMPLETO y MODIFICADO
  3. 🔒 IMPORTANTE: Agrega este archivo a .gitignore si usas Git
  4. NUNCA subas este archivo a GitHub o lo compartas públicamente

✅ CHECKPOINT 4: Connection String listo

Verifica que tu connection string tenga:

  • ✅ Tu usuario: biblioteca_admin
  • ✅ Tu contraseña SIN los símbolos < >
  • ✅ El nombre del cluster: bibliotecacluster.xxxxx
  • ✅ El nombre de la BD: /biblioteca_logs
  • ✅ Guardado en un archivo .txt seguro
🧪 TEST: Verificar Connection String
# Puedes probar la conexión con este comando Python:
python -c "from pymongo import MongoClient; client = MongoClient('TU_CONNECTION_STRING'); print('✅ Conexión exitosa:', client.server_info()['version'])"

# Si ves "✅ Conexión exitosa: 7.x.x" → Todo bien!
# Si ves error → Revisa usuario, contraseña o IP whitelist

⚠️ Seguridad del Connection String:

  • 🔒 Contiene credenciales sensibles (usuario y contraseña en texto plano)
  • NUNCA lo incluyas directamente en tu código fuente
  • USA variables de entorno en producción
  • Agrega a .gitignore: *.txt o mongodb_credentials.txt
  • 🔄 Rota contraseñas cada 3-6 meses en producción

📋 Paso 6: Crear Múltiples Bases de Datos en el Cluster (NUEVA SECCIÓN 2026)

🏗️ Cómo Organizar Bases de Datos en Tu Único Cluster Gratuito

Como solo puedes tener 1 cluster M0, es fundamental organizar correctamente tus bases de datos dentro de él.

📊 Estrategias de Organización 2026:
Estrategia Cuándo Usarla Ejemplo
Por Proyecto Tienes múltiples proyectos Django biblioteca_db, tienda_db, blog_db
Por Funcionalidad Un solo proyecto grande biblioteca_catalogo, biblioteca_logs, biblioteca_analytics
Por Ambiente Desarrollo vs Testing biblioteca_dev, biblioteca_test, biblioteca_prod
1 Crear Base de Datos Desde Django (Método Recomendado)

MongoDB Atlas crea bases de datos automáticamente cuando insertas el primer documento. No necesitas crearlas manualmente en Atlas.

Python - Crear bases de datos dinámicamente
from pymongo import MongoClient

# Connection string SIN nombre de BD específico:
MONGO_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.xxxxx.mongodb.net/?retryWrites=true&w=majority"

client = MongoClient(MONGO_URI)

# ✅ Método 1: Acceder a múltiples bases de datos
db_logs = client['biblioteca_logs']             # Para logs del sistema
db_catalogo = client['biblioteca_catalogo']     # Para catálogo de libros
db_usuarios = client['biblioteca_usuarios']     # Para datos de usuarios
db_estadisticas = client['biblioteca_estadisticas']  # Para analytics y reportes

# ✅ Método 2: Usar notación punto (alternativa)
db_logs = client.biblioteca_logs
db_catalogo = client.biblioteca_catalogo
db_usuarios = client.biblioteca_usuarios
db_estadisticas = client.biblioteca_estadisticas

# 🗄️ Insertar dato en cada BD (esto las crea automáticamente):
db_logs['logs_actividad'].insert_one({
    "evento": "inicio", 
    "fecha": "2026-01-30",
    "usuario": "admin"
})

db_catalogo['libros'].insert_one({
    "libro_id": 1,
    "titulo": "Cien Años de Soledad", 
    "isbn": "978-0307474728",
    "autor": {"nombre": "Gabriel García Márquez"}
})

db_usuarios['usuarios'].insert_one({
    "nombre": "Admin", 
    "rol": "admin",
    "email": "admin@biblioteca.com"
})

db_estadisticas['visitas'].insert_one({
    "fecha": "2026-01-30",
    "total_visitas": 120,
    "libros_prestados": 15
})

# 📋 Ver todas las bases de datos creadas:
print("Bases de datos en el cluster:", client.list_database_names())
# Output: ['admin', 'biblioteca_logs', 'biblioteca_catalogo', 'biblioteca_usuarios', 'biblioteca_estadisticas', 'local']
2 Ver Bases de Datos en MongoDB Atlas (Verificación)
  1. 🌐 Ve a cloud.mongodb.com
  2. 🔍 Haz clic en tu cluster "BibliotecaCluster"
  3. 📊 Haz clic en "Browse Collections"
  4. 👁️ Verás TODAS las bases de datos de tu cluster:
    • biblioteca_logs → logs_actividad (colección)
    • biblioteca_catalogo → libros (colección)
    • biblioteca_usuarios → usuarios (colección)
    • biblioteca_estadisticas → visitas (colección)
  5. ✅ Cada base de datos aparece como un nodo expandible
  6. 💡 Al expandir cada BD verás sus colecciones internas
3 Configuración Django Multi-BD (settings.py)

Para proyectos grandes, configura múltiples conexiones en Django:

biblioteca_project/settings.py
# Configuración para usar múltiples BD en el mismo cluster

from pymongo import MongoClient

# Connection string BASE (sin nombre de BD específico):
MONGODB_BASE_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.xxxxx.mongodb.net/?retryWrites=true&w=majority"

# Cliente MongoDB compartido:
mongo_client = MongoClient(MONGODB_BASE_URI)

# Acceso a diferentes bases de datos:
MONGODB_DATABASES = {
    'logs': mongo_client['biblioteca_logs'],
    'catalogo': mongo_client['biblioteca_catalogo'],
    'usuarios': mongo_client['biblioteca_usuarios'],
    'estadisticas': mongo_client['biblioteca_estadisticas'],
}

# Uso en views.py:
# from django.conf import settings
# db_logs = settings.MONGODB_DATABASES['logs']
# db_catalogo = settings.MONGODB_DATABASES['catalogo']
# db_estadisticas = settings.MONGODB_DATABASES['estadisticas']

💡 Buenas Prácticas para Organizar BDs en el Mismo Cluster:

  • Nomenclatura consistente: Usa prefijos comunes (biblioteca_*)
  • Separación lógica: Una BD por módulo funcional
  • Documentación: Mantén un README indicando qué guarda cada BD
  • Monitoreo: Revisa el uso de almacenamiento en Atlas (max 512 MB total)
  • Limpieza: Elimina BDs de prueba que ya no uses
Python - Ver tamaño de cada BD
from pymongo import MongoClient

client = MongoClient(MONGO_URI)

for db_name in client.list_database_names():
    if 'biblioteca' in db_name:  # Solo nuestras BDs
        db = client[db_name]
        stats = db.command("dbStats")
        size_mb = stats['dataSize'] / (1024 * 1024)
        print(f"📊 {db_name}: {size_mb:.2f} MB")

# Output ejemplo:
# 📊 biblioteca_logs: 0.05 MB
# 📊 biblioteca_catalogo: 2.34 MB
# 📊 biblioteca_usuarios: 0.12 MB
# 📊 biblioteca_estadisticas: 0.08 MB
# 📊 Total usado: ~2.59 MB / 512 MB (0.5% del límite gratuito)

✅ CHECKPOINT 5: Bases de datos organizadas

Deberías poder:

  • ✅ Conectar a tu cluster desde Django
  • ✅ Crear múltiples bases de datos programáticamente
  • ✅ Acceder a diferentes BDs usando client['nombre_bd']
  • ✅ Ver todas tus BDs en Atlas dashboard → Browse Collections
  • ✅ Monitorear uso de almacenamiento (debe estar bajo 512 MB)

📋 Paso 7: Crear la Estructura Completa de Bases de Datos y Colecciones

🎯 Objetivo: Crear las 4 Bases de Datos con sus Colecciones y Campos

Ahora vamos a crear la estructura completa del proyecto siguiendo la arquitectura definida:

1 Crear script de inicialización de MongoDB

Crea un archivo Python que generará toda la estructura de bases de datos:

biblioteca_project/init_mongodb.py
"""
Script para inicializar la estructura completa de MongoDB Atlas
Crea las 4 bases de datos con sus colecciones y documentos de ejemplo
"""

from pymongo import MongoClient
from datetime import datetime
from bson.objectid import ObjectId

# ========================================
# 1. CONEXIÓN A MONGODB ATLAS
# ========================================

# Reemplaza con tu connection string real
MONGO_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.xxxxx.mongodb.net/?retryWrites=true&w=majority"

print("🔌 Conectando a MongoDB Atlas...")
client = MongoClient(MONGO_URI)

# Probar conexión
try:
    client.admin.command('ping')
    print("✅ Conexión exitosa a MongoDB Atlas")
except Exception as e:
    print(f"❌ Error de conexión: {e}")
    exit(1)

# ========================================
# 2. BASE DE DATOS 1: biblioteca_logs
# ========================================

print("\n📁 Creando Base de Datos: biblioteca_logs")
db_logs = client['biblioteca_logs']

# Colección 1: logs_actividad
logs_actividad = db_logs['logs_actividad']
logs_actividad.insert_many([
    {
        "timestamp": datetime.now(),
        "usuario": "admin",
        "accion": "LOGIN",
        "ip": "192.168.1.100",
        "detalles": "Inicio de sesión exitoso"
    },
    {
        "timestamp": datetime.now(),
        "usuario": "admin",
        "accion": "PRESTAMO_CREADO",
        "libro_id": 1,  # ← Campo común con MySQL
        "detalles": "Préstamo registrado"
    }
])
print("  ✅ logs_actividad creada (2 documentos)")

# Colección 2: logs_errores
logs_errores = db_logs['logs_errores']
logs_errores.insert_one({
    "timestamp": datetime.now(),
    "nivel": "ERROR",
    "modulo": "prestamos",
    "mensaje": "Libro no disponible en stock",
    "libro_id": 5,
    "stack_trace": "..."
})
print("  ✅ logs_errores creada")

# Colección 3: logs_auditoria
logs_auditoria = db_logs['logs_auditoria']
logs_auditoria.insert_one({
    "timestamp": datetime.now(),
    "usuario": "admin",
    "tabla": "prestamos",
    "accion": "UPDATE",
    "registro_id": 10,
    "cambios": {
        "campo": "estado",
        "valor_anterior": "activo",
        "valor_nuevo": "devuelto"
    }
})
print("  ✅ logs_auditoria creada")

# ========================================
# 3. BASE DE DATOS 2: biblioteca_catalogo
# ========================================

print("\n📁 Creando Base de Datos: biblioteca_catalogo")
db_catalogo = client['biblioteca_catalogo']

# Colección 1: libros (⚠️ IMPORTANTE: Campo libro_id para conectar con MySQL)
libros = db_catalogo['libros']
libros_data = [
    {
        "libro_id": 1,  # ← CAMPO COMÚN con MySQL (INT)
        "titulo": "Cien Años de Soledad",
        "isbn": "978-0307474728",
        "autor": {  # Subdocumento
            "nombre": "Gabriel García Márquez",
            "pais": "Colombia",
            "fecha_nacimiento": datetime(1927, 3, 6)
        },
        "categorias": ["Realismo Mágico", "Literatura Latinoamericana"],
        "editorial": "Penguin Random House",
        "año_publicacion": 1967,
        "stock": 10,
        "precio": 15.99,
        "disponible": True,
        "portada_url": "https://example.com/cien-anos.jpg",
        "descripcion": "Historia de la familia Buendía...",
        "paginas": 417,
        "idioma": "Español"
    },
    {
        "libro_id": 2,  # ← CAMPO COMÚN
        "titulo": "Don Quijote de la Mancha",
        "isbn": "978-0060934347",
        "autor": {
            "nombre": "Miguel de Cervantes",
            "pais": "España",
            "fecha_nacimiento": datetime(1547, 9, 29)
        },
        "categorias": ["Clásicos", "Novela"],
        "editorial": "Editorial Planeta",
        "año_publicacion": 1605,
        "stock": 5,
        "precio": 18.99,
        "disponible": True,
        "formato_ebook": True,  # Campo flexible (no todos lo tienen)
        "paginas": 863,
        "idioma": "Español"
    },
    {
        "libro_id": 3,
        "titulo": "1984",
        "isbn": "978-0451524935",
        "autor": {
            "nombre": "George Orwell",
            "pais": "Reino Unido"
        },
        "categorias": ["Distopía", "Ciencia Ficción"],
        "stock": 8,
        "precio": 12.99,
        "disponible": True,
        "audiobook_disponible": True  # Otro campo flexible
    }
]
libros.insert_many(libros_data)
print(f"  ✅ libros creada ({len(libros_data)} documentos)")

# Colección 2: autores
autores = db_catalogo['autores']
autores.insert_many([
    {
        "nombre_completo": "Gabriel García Márquez",
        "pais": "Colombia",
        "biografia": "Premio Nobel de Literatura 1982...",
        "libros_escritos": [1]  # Referencias a libro_id
    },
    {
        "nombre_completo": "Miguel de Cervantes",
        "pais": "España",
        "biografia": "Autor del Quijote...",
        "libros_escritos": [2]
    }
])
print("  ✅ autores creada")

# Colección 3: categorias
categorias = db_catalogo['categorias']
categorias.insert_many([
    {"nombre": "Realismo Mágico", "descripcion": "Narrativa con elementos mágicos"},
    {"nombre": "Clásicos", "descripcion": "Obras literarias atemporales"},
    {"nombre": "Distopía", "descripcion": "Sociedades futuras opresivas"}
])
print("  ✅ categorias creada")

# Colección 4: reseñas
reseñas = db_catalogo['reseñas']
reseñas.insert_many([
    {
        "libro_id": 1,  # ← CAMPO COMÚN
        "usuario": "juan123",
        "calificacion": 5,
        "comentario": "Una obra maestra de la literatura",
        "fecha": datetime.now(),
        "likes": 15
    },
    {
        "libro_id": 2,
        "usuario": "maria456",
        "calificacion": 4,
        "comentario": "Clásico imprescindible",
        "fecha": datetime.now()
    }
])
print("  ✅ reseñas creada")

# ========================================
# 4. BASE DE DATOS 3: biblioteca_usuarios
# ========================================

print("\n📁 Creando Base de Datos: biblioteca_usuarios")
db_usuarios = client['biblioteca_usuarios']

# Colección 1: usuarios
usuarios = db_usuarios['usuarios']
usuarios.insert_many([
    {
        "usuario_id": 1,  # Referencia al auth_user de MySQL
        "username": "admin",
        "perfil": {
            "foto_url": "https://example.com/admin.jpg",
            "biografia": "Administrador del sistema",
            "generos_favoritos": ["Realismo Mágico", "Ciencia Ficción"]
        },
        "historial_lectura": [
            {"libro_id": 1, "fecha_inicio": datetime(2026, 1, 15), "terminado": True},
            {"libro_id": 3, "fecha_inicio": datetime(2026, 1, 20), "terminado": False}
        ]
    }
])
print("  ✅ usuarios creada")

# Colección 2: sesiones
sesiones = db_usuarios['sesiones']
sesiones.insert_one({
    "usuario_id": 1,
    "token": "abc123xyz789",
    "inicio": datetime.now(),
    "activa": True,
    "ip": "192.168.1.100"
})
print("  ✅ sesiones creada")

# Colección 3: preferencias
preferencias = db_usuarios['preferencias']
preferencias.insert_one({
    "usuario_id": 1,
    "notificaciones_email": True,
    "tema": "oscuro",
    "idioma": "es"
})
print("  ✅ preferencias creada")

# ========================================
# 5. BASE DE DATOS 4: biblioteca_estadisticas
# ========================================

print("\n📁 Creando Base de Datos: biblioteca_estadisticas")
db_estadisticas = client['biblioteca_estadisticas']

# Colección 1: visitas
visitas = db_estadisticas['visitas']
visitas.insert_many([
    {
        "fecha": datetime(2026, 1, 29),
        "total_visitas": 120,
        "paginas_vistas": 450,
        "usuarios_unicos": 85
    },
    {
        "fecha": datetime(2026, 1, 30),
        "total_visitas": 135,
        "paginas_vistas": 480,
        "usuarios_unicos": 95
    }
])
print("  ✅ visitas creada")

# Colección 2: prestamos (estadísticas de préstamos)
prestamos_stats = db_estadisticas['prestamos']
prestamos_stats.insert_many([
    {
        "fecha": datetime(2026, 1, 30),
        "libro_id": 1,  # ← CAMPO COMÚN
        "total_prestamos": 15,
        "promedio_dias_prestamo": 12
    },
    {
        "fecha": datetime(2026, 1, 30),
        "libro_id": 2,
        "total_prestamos": 8,
        "promedio_dias_prestamo": 10
    }
])
print("  ✅ prestamos (stats) creada")

# Colección 3: tendencias
tendencias = db_estadisticas['tendencias']
tendencias.insert_one({
    "periodo": "enero_2026",
    "libros_mas_prestados": [
        {"libro_id": 1, "cantidad": 15},
        {"libro_id": 3, "cantidad": 12},
        {"libro_id": 2, "cantidad": 8}
    ],
    "categorias_populares": ["Realismo Mágico", "Distopía"]
})
print("  ✅ tendencias creada")

# ========================================
# 6. RESUMEN FINAL
# ========================================

print("\n" + "="*60)
print("✅ INICIALIZACIÓN COMPLETADA")
print("="*60)

# Listar todas las bases de datos creadas
dbs = client.list_database_names()
print(f"\n📊 Bases de datos en el cluster: {len(dbs)}")
for db in dbs:
    if 'biblioteca' in db:
        collections = client[db].list_collection_names()
        print(f"  📁 {db}")
        for col in collections:
            count = client[db][col].count_documents({})
            print(f"     ├── {col} ({count} documentos)")

# Calcular uso de almacenamiento
print("\n💾 Uso de Almacenamiento:")
total_size = 0
for db_name in ['biblioteca_logs', 'biblioteca_catalogo', 
                 'biblioteca_usuarios', 'biblioteca_estadisticas']:
    stats = client[db_name].command("dbStats")
    size_mb = stats['dataSize'] / (1024 * 1024)
    total_size += size_mb
    print(f"  📊 {db_name}: {size_mb:.2f} MB")

print(f"\n  🎯 Total usado: {total_size:.2f} MB / 512 MB ({(total_size/512)*100:.1f}%)")

client.close()
print("\n🔒 Conexión cerrada")
2 Ejecutar el script de inicialización
PowerShell - Ejecutar script
# Asegúrate de estar en la carpeta del proyecto
cd C:\PRADODIAZ\biblioteca_project

# Ejecuta el script
python init_mongodb.py

Salida esperada:

Output del script
🔌 Conectando a MongoDB Atlas...
✅ Conexión exitosa a MongoDB Atlas

📁 Creando Base de Datos: biblioteca_logs
  ✅ logs_actividad creada (2 documentos)
  ✅ logs_errores creada
  ✅ logs_auditoria creada

📁 Creando Base de Datos: biblioteca_catalogo
  ✅ libros creada (3 documentos)
  ✅ autores creada
  ✅ categorias creada
  ✅ reseñas creada

📁 Creando Base de Datos: biblioteca_usuarios
  ✅ usuarios creada
  ✅ sesiones creada
  ✅ preferencias creada

📁 Creando Base de Datos: biblioteca_estadisticas
  ✅ visitas creada
  ✅ prestamos (stats) creada
  ✅ tendencias creada

============================================================
✅ INICIALIZACIÓN COMPLETADA
============================================================

📊 Bases de datos en el cluster: 7
  📁 biblioteca_logs
     ├── logs_actividad (2 documentos)
     ├── logs_errores (1 documentos)
     └── logs_auditoria (1 documentos)
  📁 biblioteca_catalogo
     ├── libros (3 documentos)
     ├── autores (2 documentos)
     ├── categorias (3 documentos)
     └── reseñas (2 documentos)
  📁 biblioteca_usuarios
     ├── usuarios (1 documentos)
     ├── sesiones (1 documentos)
     └── preferencias (1 documentos)
  📁 biblioteca_estadisticas
     ├── visitas (2 documentos)
     ├── prestamos (2 documentos)
     └── tendencias (1 documentos)

💾 Uso de Almacenamiento:
  📊 biblioteca_logs: 0.03 MB
  📊 biblioteca_catalogo: 0.05 MB
  📊 biblioteca_usuarios: 0.02 MB
  📊 biblioteca_estadisticas: 0.02 MB

  🎯 Total usado: 0.12 MB / 512 MB (0.0%)

🔒 Conexión cerrada
3 Verificar en MongoDB Atlas
  1. 🌐 Ve a cloud.mongodb.com
  2. 🔍 Haz clic en "Browse Collections" en tu cluster
  3. 👁️ Deberías ver las 4 bases de datos con todas sus colecciones:
    • 📁 biblioteca_logs → 3 colecciones
    • 📁 biblioteca_catalogo → 4 colecciones
    • 📁 biblioteca_usuarios → 3 colecciones
    • 📁 biblioteca_estadisticas → 3 colecciones
  4. ✅ Haz clic en biblioteca_catalogo > libros
  5. 👀 Verás los 3 libros con el campo libro_id (1, 2, 3)

✅ CHECKPOINT 6: MongoDB Atlas completamente configurado

En este punto tienes:

  • ✅ 4 bases de datos creadas en MongoDB Atlas
  • ✅ 13 colecciones con datos de ejemplo
  • ✅ Campo común libro_id en colección libros
  • ✅ Estructura lista para conectar con Django

🎉 ¡La parte de MongoDB Atlas está completa!

📍 Siguiente: Configurar Django + MySQL para el sistema híbrido


🐍 PARTE 2: DJANGO + MYSQL (SISTEMA HÍBRIDO)

🎯 Objetivo de Esta Sección:

Crear el proyecto Django que conectará MySQL (local) con MongoDB Atlas (nube) usando el campo común libro_id

Base de Datos ¿Qué Guarda? Tecnología
MySQL (Local) - Tabla: biblioteca_prestamo
- Campos: id, libro_id (INT), usuario_id, fecha_prestamo, fecha_devolucion, estado
Django ORM (models.py)
MongoDB Atlas - Colección: libros
- Campos: libro_id (INT), titulo, isbn, autor{}, categorias[], stock, precio
PyMongo (queries directas)
🔗 Campo Común: libro_id (INT)
MySQL: prestamo.libro_id ↔ MongoDB: libro.libro_id

📋 Paso 8: Configurar Proyecto Django con MySQL

1 Crear proyecto Django
PowerShell - Crear proyecto
# Crear carpeta del proyecto
mkdir C:\PRADODIAZ\biblioteca_project
cd C:\PRADODIAZ\biblioteca_project

# Crear entorno virtual
python -m venv venv

# Activar entorno virtual
.\venv\Scripts\Activate

# Instalar Django y dependencias
pip install django pymongo dnspython mysqlclient

# Crear proyecto Django
django-admin startproject biblioteca_project .

# Crear app
python manage.py startapp biblioteca
2 Configurar settings.py (MySQL + MongoDB)
biblioteca_project/settings.py
from pathlib import Path
import os
from pymongo import MongoClient

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-tu-secret-key-aqui'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'biblioteca',  # ← Tu app
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'biblioteca_project.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'biblioteca_project.wsgi.application'

# ========================================
# CONFIGURACIÓN MYSQL (BASE DE DATOS LOCAL)
# ========================================

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'biblioteca_db',  # Nombre de tu base de datos MySQL
        'USER': 'root',  # Usuario de MySQL
        'PASSWORD': '',  # Contraseña de MySQL (vacía en XAMPP por defecto)
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

# ========================================
# CONFIGURACIÓN MONGODB ATLAS (NUBE)
# ========================================

# ⚠️ IMPORTANTE: REEMPLAZA CON TU CONNECTION STRING REAL DE MONGODB ATLAS
# Obtén tu string desde: MongoDB Atlas → Connect → Drivers
# Debe verse así (con TU cluster real):
# mongodb+srv://usuario:password@cluster0.ab12cd.mongodb.net/

MONGODB_URI = "mongodb+srv://biblioteca_admin:TuPasswordReal@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"

# EJEMPLO REAL (cambia cluster0.ab12cd por tu cluster):
# MONGODB_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@cluster0.ab12cd.mongodb.net/?retryWrites=true&w=majority"

# Crear cliente MongoDB global
MONGO_CLIENT = MongoClient(MONGODB_URI)

# Diccionario de bases de datos de MongoDB
MONGODB_DATABASES = {
    'logs': MONGO_CLIENT['biblioteca_logs'],
    'catalogo': MONGO_CLIENT['biblioteca_catalogo'],
    'usuarios': MONGO_CLIENT['biblioteca_usuarios'],
    'estadisticas': MONGO_CLIENT['biblioteca_estadisticas'],
}

# Password validation
AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

# Internationalization
LANGUAGE_CODE = 'es-mx'
TIME_ZONE = 'America/Mexico_City'
USE_I18N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'

# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

⚠️ ERROR COMÚN: ConfigurationError - DNS query name does not exist

Si ves este error:

Error
pymongo.errors.ConfigurationError: The DNS query name does not exist: 
_mongodb._tcp.bibliotecacluster.xxxxx.mongodb.net.

🔴 CAUSA: Estás usando el connection string de EJEMPLO con .xxxxx. en lugar de tu cluster real.

✅ SOLUCIÓN - Obtener tu Connection String Real:
  1. Ve a MongoDB Atlas: https://cloud.mongodb.com
  2. Selecciona tu proyecto (ejemplo: "BibliotecaProject")
  3. Haz clic en "Connect" en tu cluster (Cluster0)
  4. Selecciona "Drivers"
  5. Copia el Connection String que aparece (similar a):
    Ejemplo de Connection String Real
    mongodb+srv://biblioteca_admin:<password>@cluster0.ab12cd.mongodb.net/?retryWrites=true&w=majority

    Nota: El código ab12cd es único para tu cluster. NO uses xxxxx

  6. Reemplaza <password> con tu contraseña real (ejemplo: Biblioteca2026!)
📝 Ejemplo de Configuración Correcta:
settings.py - Connection String CORRECTO
# ❌ INCORRECTO (con xxxxx):
MONGODB_URI = "mongodb+srv://user:pass@cluster.xxxxx.mongodb.net/"

# ✅ CORRECTO (con tu código real):
MONGODB_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@cluster0.ab12cd.mongodb.net/?retryWrites=true&w=majority"

# Donde 'ab12cd' es el código único de TU cluster

💡 TIP: Si olvidaste tu contraseña, puedes resetearla desde:

Database Access → Edit User → Edit Password

3 ✅ PRUEBA DE CONEXIÓN MONGODB (Opcional pero Recomendado)

Antes de continuar, es importante verificar que tu connection string funciona correctamente.

PowerShell - Crear archivo de prueba
# Crear archivo test_mongo.py en la raíz del proyecto
notepad test_mongo.py
test_mongo.py - Script de prueba
from pymongo import MongoClient

# ⚠️ IMPORTANTE: Pega TU connection string real aquí
# Obtén desde: MongoDB Atlas → Connect → Drivers
MONGODB_URI = "mongodb+srv://biblioteca_admin:TuPassword@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"

print("🔄 Intentando conectar a MongoDB Atlas...")
print(f"📡 URI: {MONGODB_URI[:50]}...")
print("-" * 60)

try:
    # Intentar conectar al cluster
    client = MongoClient(MONGODB_URI, serverSelectionTimeoutMS=5000)
    
    # Forzar la conexión con un comando
    client.admin.command('ping')
    
    print("✅ ¡CONEXIÓN EXITOSA!")
    print("-" * 60)
    
    # Listar todas las bases de datos disponibles
    databases = client.list_database_names()
    print(f"📚 Bases de datos encontradas ({len(databases)}):")
    for db in databases:
        print(f"   - {db}")
    
    print("-" * 60)
    
    # Verificar las 4 bases de datos del proyecto (si ya existen)
    expected_dbs = [
        "biblioteca_logs",
        "biblioteca_catalogo",
        "biblioteca_usuarios",
        "biblioteca_estadisticas"
    ]
    
    print("🔍 Verificando bases de datos del proyecto:")
    for db_name in expected_dbs:
        if db_name in databases:
            # Contar colecciones
            db = client[db_name]
            collections = db.list_collection_names()
            print(f"   ✅ {db_name}: {len(collections)} colecciones")
            if collections:
                for col in collections:
                    count = db[col].count_documents({})
                    print(f"      → {col}: {count} documentos")
        else:
            print(f"   ⚠️  {db_name}: No existe (se creará con init_mongodb.py)")
    
    print("-" * 60)
    print("✅ Prueba completada exitosamente")
    print("👉 Puedes continuar con las migraciones de Django")
    
except Exception as e:
    print("❌ ERROR DE CONEXIÓN:")
    print("-" * 60)
    print(str(e))
    print("-" * 60)
    print("\n🔧 SOLUCIONES POSIBLES:")
    print("1. Verifica que el connection string sea correcto")
    print("2. Reemplaza 'xxxxx' con el código de tu cluster")
    print("3. Reemplaza '<password>' con tu contraseña real")
    print("4. Verifica que tu IP esté en la lista blanca (0.0.0.0/0)")
    print("5. Verifica que el usuario tenga permisos de lectura/escritura")

📝 Cómo usar este script:

  1. Crea el archivo:
    PowerShell
    # Asegúrate de estar en la raíz del proyecto
    cd C:\PRADODIAZ\biblioteca_project
    
    # Crear el archivo test_mongo.py
    notepad test_mongo.py
  2. Copia y pega el código completo del script (mostrado arriba) en el archivo
  3. ⚠️ IMPORTANTE: Reemplaza el MONGODB_URI con tu connection string real:
    test_mongo.py (línea 5 - EDITAR ESTO)
    # ❌ NO USES ESTO (es un ejemplo):
    MONGODB_URI = "mongodb+srv://biblioteca_admin:TuPassword@cluster0.xxxxx.mongodb.net/..."
    
    # ✅ USA TU CONNECTION STRING REAL:
    # Ve a MongoDB Atlas → Connect → Drivers → Copia tu string
    MONGODB_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@cluster0.ab12cd.mongodb.net/?retryWrites=true&w=majority"
    #                                                                    ^^^^^^
    #                                                    Reemplaza con tu código real
  4. Guarda el archivo (Ctrl + S en Notepad)
  5. Cierra Notepad y ejecuta el script desde PowerShell
PowerShell - Ejecutar prueba
# Ejecutar el script de prueba
python test_mongo.py

✅ Resultado Esperado (Conexión Exitosa):

Salida esperada
🔄 Intentando conectar a MongoDB Atlas...
📡 URI: mongodb+srv://biblioteca_admin:***@cluster0.ab12...
------------------------------------------------------------
✅ ¡CONEXIÓN EXITOSA!
------------------------------------------------------------
📚 Bases de datos encontradas (3):
   - admin
   - local
   - sample_mflix
------------------------------------------------------------
🔍 Verificando bases de datos del proyecto:
   ⚠️  biblioteca_logs: No existe (se creará con init_mongodb.py)
   ⚠️  biblioteca_catalogo: No existe (se creará con init_mongodb.py)
   ⚠️  biblioteca_usuarios: No existe (se creará con init_mongodb.py)
   ⚠️  biblioteca_estadisticas: No existe (se creará con init_mongodb.py)
------------------------------------------------------------
✅ Prueba completada exitosamente
👉 Puedes continuar con las migraciones de Django

💡 Nota: Es normal que las bases de datos del proyecto no existan aún. Se crearán cuando ejecutes init_mongodb.py más adelante.

❌ Si obtienes error de conexión:

Revisa estos puntos:

  1. Connection String incorrecto: Verifica que no contenga xxxxx
  2. Contraseña incorrecta: Usa la contraseña que configuraste en Database Access
  3. IP no permitida: En MongoDB Atlas → Network Access → Add IP Address → Allow Access from Anywhere (0.0.0.0/0)
  4. Usuario sin permisos: En Database Access → Edit User → Built-in Role: Atlas admin
  5. Firewall/Antivirus: Puede estar bloqueando la conexión al puerto 27017
4 Crear base de datos MySQL
MySQL - Crear base de datos
-- Abre phpMyAdmin (http://localhost/phpmyadmin/)
-- O usa la consola MySQL:

mysql -u root -p

-- Crear base de datos:
CREATE DATABASE biblioteca_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Verificar:
SHOW DATABASES;

-- Salir:
EXIT;
5 Crear modelos Django (MySQL)
biblioteca/models.py
from django.db import models
from django.contrib.auth.models import User

class Prestamo(models.Model):
    """
    Modelo para préstamos (se guarda en MySQL)
    ⚠️ IMPORTANTE: libro_id es el campo común con MongoDB
    """
    
    # Campo común con MongoDB (INT)
    libro_id = models.IntegerField(
        verbose_name="ID del Libro",
        help_text="ID del libro en MongoDB (colección libros)"
    )
    
    # Relación con usuario de Django
    usuario = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        verbose_name="Usuario"
    )
    
    # Fechas del préstamo
    fecha_prestamo = models.DateTimeField(
        auto_now_add=True,
        verbose_name="Fecha de Préstamo"
    )
    
    fecha_devolucion = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name="Fecha de Devolución"
    )
    
    # Estado del préstamo
    ESTADOS = [
        ('ACTIVO', 'Activo'),
        ('DEVUELTO', 'Devuelto'),
        ('VENCIDO', 'Vencido'),
    ]
    
    estado = models.CharField(
        max_length=10,
        choices=ESTADOS,
        default='ACTIVO',
        verbose_name="Estado"
    )
    
    # Días de préstamo (se calculará desde MongoDB)
    dias_prestamo = models.IntegerField(
        default=14,
        verbose_name="Días de Préstamo"
    )
    
    class Meta:
        db_table = 'biblioteca_prestamo'
        verbose_name = 'Préstamo'
        verbose_name_plural = 'Préstamos'
        ordering = ['-fecha_prestamo']
    
    def __str__(self):
        return f"Préstamo #{self.id} - Libro ID: {self.libro_id}"


class Multa(models.Model):
    """Modelo para multas por retraso en devolución"""
    
    prestamo = models.ForeignKey(
        Prestamo,
        on_delete=models.CASCADE,
        verbose_name="Préstamo"
    )
    
    monto = models.DecimalField(
        max_digits=6,
        decimal_places=2,
        verbose_name="Monto"
    )
    
    pagada = models.BooleanField(
        default=False,
        verbose_name="Pagada"
    )
    
    fecha_creacion = models.DateTimeField(
        auto_now_add=True,
        verbose_name="Fecha de Creación"
    )
    
    class Meta:
        db_table = 'biblioteca_multa'
        verbose_name = 'Multa'
        verbose_name_plural = 'Multas'
    
    def __str__(self):
        return f"Multa ${self.monto} - Préstamo #{self.prestamo.id}"

⚠️ ERROR COMÚN: NameError: name 'BASE_DIR' is not defined

Si ves este error al ejecutar migraciones:

Error
File "settings.py", line 38, in <module>
    'DIRS': [os.path.join(BASE_DIR, 'templates')],
                          ^^^^^^^^
NameError: name 'BASE_DIR' is not defined

✅ SOLUCIÓN: Agrega estas líneas al INICIO de settings.py:

biblioteca_project/settings.py (AGREGAR AL INICIO)
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

Explicación:

  • BASE_DIR es la ruta base del proyecto Django
  • Se usa para definir rutas absolutas (templates, static files, etc.)
  • Debe estar definido ANTES de usarlo en TEMPLATES
6 Ejecutar migraciones
PowerShell - Crear tablas en MySQL
# Crear archivos de migración
python manage.py makemigrations

# Aplicar migraciones a MySQL
python manage.py migrate

# Crear superusuario
python manage.py createsuperuser
# Username: admin
# Email: admin@biblioteca.com
# Password: admin123

Resultado esperado en MySQL:

  • ✅ Tabla biblioteca_prestamo creada
  • ✅ Tabla biblioteca_multa creada
  • ✅ Tablas de Django (auth_user, etc.) creadas

✅ CHECKPOINT 7: MySQL configurado

En este punto tienes:

  • ✅ Proyecto Django creado
  • ✅ MySQL configurado con base de datos biblioteca_db
  • ✅ Modelos Prestamo y Multa con campo libro_id
  • ✅ MongoDB configurado en settings.py
  • ✅ Listo para crear views que usen ambas BDs

📋 Paso 7: Crear Views Híbridas (MySQL + MongoDB)

🎯 Views que Conectan Ambas Bases de Datos

Ahora crearemos views que usan el campo libro_id para combinar datos de MySQL y MongoDB

1 Crear archivo de views híbridas
biblioteca/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.conf import settings
from django.utils import timezone
from .models import Prestamo, Multa
from datetime import datetime, timedelta

# ========================================
# 1. LISTAR LIBROS (Solo MongoDB)
# ========================================

def listar_libros(request):
    """
    Lista todos los libros desde MongoDB Atlas
    """
    # Obtener base de datos de MongoDB desde settings
    db_catalogo = settings.MONGODB_DATABASES['catalogo']
    
    # Buscar parámetro de búsqueda
    query = request.GET.get('q', '')
    
    if query:
        # Búsqueda con regex (case-insensitive)
        libros = db_catalogo.libros.find({
            "$or": [
                {"titulo": {"$regex": query, "$options": "i"}},
                {"autor.nombre": {"$regex": query, "$options": "i"}},
            ]
        })
    else:
        # Obtener todos los libros
        libros = db_catalogo.libros.find()
    
    # Convertir cursor a lista
    libros_list = list(libros)
    
    context = {
        'libros': libros_list,
        'query': query,
        'total_libros': len(libros_list)
    }
    
    return render(request, 'biblioteca/listar_libros.html', context)


# ========================================
# 2. DETALLE DE LIBRO (MongoDB + MySQL)
# ========================================

def detalle_libro(request, libro_id):
    """
    Muestra detalle de un libro combinando datos de:
    - MongoDB: Información del libro (titulo, autor, etc.)
    - MySQL: Préstamos activos de este libro
    """
    # 1. Obtener información del libro desde MongoDB
    db_catalogo = settings.MONGODB_DATABASES['catalogo']
    libro = db_catalogo.libros.find_one({"libro_id": libro_id})
    
    if not libro:
        messages.error(request, "Libro no encontrado")
        return redirect('listar_libros')
    
    # 2. Obtener préstamos activos de este libro desde MySQL
    prestamos_activos = Prestamo.objects.filter(
        libro_id=libro_id,
        estado='ACTIVO'
    ).select_related('usuario')
    
    # 3. Obtener reseñas desde MongoDB
    reseñas = db_catalogo.reseñas.find({"libro_id": libro_id})
    
    context = {
        'libro': libro,  # MongoDB
        'prestamos_activos': prestamos_activos,  # MySQL
        'reseñas': list(reseñas),  # MongoDB
        'disponible': libro.get('stock', 0) > 0
    }
    
    return render(request, 'biblioteca/detalle_libro.html', context)


# ========================================
# 3. PRESTAR LIBRO (MySQL + MongoDB)
# ========================================

@login_required
def prestar_libro(request, libro_id):
    """
    Crea un préstamo en MySQL y actualiza stock en MongoDB
    ⚠️ Esta es la operación crítica del sistema híbrido
    """
    if request.method == 'POST':
        db_catalogo = settings.MONGODB_DATABASES['catalogo']
        db_logs = settings.MONGODB_DATABASES['logs']
        db_estadisticas = settings.MONGODB_DATABASES['estadisticas']
        
        # 1. Buscar libro en MongoDB
        libro = db_catalogo.libros.find_one({"libro_id": libro_id})
        
        if not libro:
            messages.error(request, "Libro no encontrado")
            return redirect('listar_libros')
        
        # 2. Verificar stock disponible (MongoDB)
        if libro.get('stock', 0) <= 0:
            messages.error(request, f"El libro '{libro['titulo']}' no tiene stock disponible")
            return redirect('detalle_libro', libro_id=libro_id)
        
        # 3. Verificar que el usuario no tenga préstamos activos del mismo libro
        prestamo_existente = Prestamo.objects.filter(
            libro_id=libro_id,
            usuario=request.user,
            estado='ACTIVO'
        ).exists()
        
        if prestamo_existente:
            messages.warning(request, "Ya tienes un préstamo activo de este libro")
            return redirect('detalle_libro', libro_id=libro_id)
        
        try:
            # 4. Crear préstamo en MySQL
            prestamo = Prestamo.objects.create(
                libro_id=libro_id,  # ← CAMPO COMÚN
                usuario=request.user,
                dias_prestamo=14
            )
            
            # 5. Actualizar stock en MongoDB
            db_catalogo.libros.update_one(
                {"libro_id": libro_id},
                {"$inc": {"stock": -1}}  # Restar 1 al stock
            )
            
            # 6. Registrar log en MongoDB
            db_logs.logs_actividad.insert_one({
                "timestamp": datetime.now(),
                "usuario": request.user.username,
                "accion": "PRESTAMO_CREADO",
                "libro_id": libro_id,  # ← CAMPO COMÚN
                "prestamo_id": prestamo.id,
                "detalles": f"Préstamo del libro '{libro['titulo']}'"
            })
            
            # 7. Actualizar estadísticas en MongoDB
            db_estadisticas.prestamos.insert_one({
                "fecha": datetime.now(),
                "libro_id": libro_id,  # ← CAMPO COMÚN
                "usuario": request.user.username,
                "accion": "PRESTADO"
            })
            
            messages.success(
                request, 
                f"✅ Préstamo registrado exitosamente. Fecha de devolución: "
                f"{(timezone.now() + timedelta(days=14)).strftime('%d/%m/%Y')}"
            )
            
            return redirect('mis_prestamos')
            
        except Exception as e:
            # Si hay error, registrar en logs de MongoDB
            db_logs.logs_errores.insert_one({
                "timestamp": datetime.now(),
                "nivel": "ERROR",
                "modulo": "prestamos",
                "mensaje": str(e),
                "usuario": request.user.username,
                "libro_id": libro_id
            })
            
            messages.error(request, f"Error al crear préstamo: {str(e)}")
            return redirect('detalle_libro', libro_id=libro_id)
    
    return redirect('detalle_libro', libro_id=libro_id)


# ========================================
# 4. DEVOLVER LIBRO (MySQL + MongoDB)
# ========================================

@login_required
def devolver_libro(request, prestamo_id):
    """
    Marca préstamo como devuelto en MySQL y actualiza stock en MongoDB
    """
    prestamo = get_object_or_404(Prestamo, id=prestamo_id, usuario=request.user)
    
    if prestamo.estado != 'ACTIVO':
        messages.warning(request, "Este préstamo ya fue devuelto")
        return redirect('mis_prestamos')
    
    db_catalogo = settings.MONGODB_DATABASES['catalogo']
    db_logs = settings.MONGODB_DATABASES['logs']
    
    try:
        # 1. Actualizar préstamo en MySQL
        prestamo.estado = 'DEVUELTO'
        prestamo.fecha_devolucion = timezone.now()
        prestamo.save()
        
        # 2. Restaurar stock en MongoDB
        db_catalogo.libros.update_one(
            {"libro_id": prestamo.libro_id},  # ← CAMPO COMÚN
            {"$inc": {"stock": 1}}  # Sumar 1 al stock
        )
        
        # 3. Registrar devolución en logs
        db_logs.logs_actividad.insert_one({
            "timestamp": datetime.now(),
            "usuario": request.user.username,
            "accion": "DEVOLUCION",
            "libro_id": prestamo.libro_id,  # ← CAMPO COMÚN
            "prestamo_id": prestamo.id
        })
        
        messages.success(request, "✅ Libro devuelto exitosamente")
        
    except Exception as e:
        messages.error(request, f"Error al devolver libro: {str(e)}")
    
    return redirect('mis_prestamos')


# ========================================
# 5. MIS PRÉSTAMOS (Consulta Híbrida)
# ========================================

@login_required
def mis_prestamos(request):
    """
    Lista préstamos del usuario combinando datos de MySQL y MongoDB
    """
    # 1. Obtener préstamos del usuario desde MySQL
    prestamos = Prestamo.objects.filter(usuario=request.user).order_by('-fecha_prestamo')
    
    # 2. Obtener base de datos MongoDB
    db_catalogo = settings.MONGODB_DATABASES['catalogo']
    
    # 3. Para cada préstamo, obtener datos del libro desde MongoDB
    prestamos_completos = []
    
    for prestamo in prestamos:
        # Buscar libro en MongoDB usando el campo común
        libro = db_catalogo.libros.find_one({"libro_id": prestamo.libro_id})
        
        # Combinar datos
        prestamos_completos.append({
            'prestamo': prestamo,  # MySQL
            'libro': libro if libro else {'titulo': 'Libro no encontrado'}  # MongoDB
        })
    
    context = {
        'prestamos_completos': prestamos_completos,
        'total_prestamos': prestamos.count()
    }
    
    return render(request, 'biblioteca/mis_prestamos.html', context)


# ========================================
# 6. DASHBOARD (Estadísticas Híbridas)
# ========================================

def dashboard(request):
    """
    Dashboard con estadísticas de MySQL y MongoDB
    """
    db_catalogo = settings.MONGODB_DATABASES['catalogo']
    db_estadisticas = settings.MONGODB_DATABASES['estadisticas']
    
    # Estadísticas de MySQL
    total_prestamos_activos = Prestamo.objects.filter(estado='ACTIVO').count()
    total_prestamos_historico = Prestamo.objects.count()
    
    # Estadísticas de MongoDB
    total_libros = db_catalogo.libros.count_documents({})
    libros_disponibles = db_catalogo.libros.count_documents({"disponible": True})
    
    # Libros más prestados (MongoDB estadísticas)
    tendencia = db_estadisticas.tendencias.find_one(
        {"periodo": "enero_2026"}
    )
    
    context = {
        'total_prestamos_activos': total_prestamos_activos,
        'total_prestamos_historico': total_prestamos_historico,
        'total_libros': total_libros,
        'libros_disponibles': libros_disponibles,
        'tendencia': tendencia
    }
    
    return render(request, 'biblioteca/dashboard.html', context)
🔍 EXPLICACIÓN DEL FLUJO HÍBRIDO:

Cuando un usuario pide un libro prestado:

  1. MySQL: Crea registro en tabla biblioteca_prestamo con libro_id=5
  2. MongoDB: Busca libro con {"libro_id": 5} en colección libros
  3. MongoDB: Actualiza stock: -1 del libro
  4. MongoDB: Registra log en colección logs_actividad
  5. MongoDB: Registra estadística en colección prestamos

Cuando lista "Mis Préstamos":

  1. MySQL: Obtiene préstamos con Prestamo.objects.filter(usuario=user)
  2. Para cada préstamo:
    • Obtiene libro_id del préstamo (MySQL)
    • Busca datos del libro en MongoDB con ese libro_id
    • Combina: fecha_prestamo (MySQL) + titulo/autor (MongoDB)

📋 Paso 8: Configurar URLs

1 Configurar URLs del proyecto principal
biblioteca_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('biblioteca.urls')),  # URLs de la app
]
2 Crear URLs de la app biblioteca
biblioteca/urls.py (CREAR ESTE ARCHIVO)
from django.urls import path
from . import views

urlpatterns = [
    # Dashboard
    path('', views.dashboard, name='dashboard'),
    
    # Libros (MongoDB)
    path('libros/', views.listar_libros, name='listar_libros'),
    path('libros/<int:libro_id>/', views.detalle_libro, name='detalle_libro'),
    
    # Préstamos (MySQL + MongoDB)
    path('prestamos/crear/<int:libro_id>/', views.prestar_libro, name='prestar_libro'),
    path('prestamos/devolver/<int:prestamo_id>/', views.devolver_libro, name='devolver_libro'),
    path('prestamos/mis-prestamos/', views.mis_prestamos, name='mis_prestamos'),
]

📋 Paso 9: Crear Templates HTML

1 Crear estructura de carpetas de templates
PowerShell - Crear carpetas
# Desde la carpeta del proyecto
mkdir biblioteca\templates\biblioteca
2 Crear template base
biblioteca/templates/biblioteca/base.html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Biblioteca - Sistema Híbrido{% endblock %}</title>
    
    <!-- Bootstrap 5 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
    
    <style>
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        .container {
            background: white;
            border-radius: 15px;
            padding: 30px;
            margin-top: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .badge-mysql {
            background: #00758f;
        }
        .badge-mongodb {
            background: #00ed64;
            color: #000;
        }
    </style>
    
    {% block extra_css %}{% endblock %}
</head>
<body>
    <!-- Navbar -->
    <nav class="navbar navbar-expand-lg navbar-dark" style="background: rgba(0,0,0,0.3);">
        <div class="container-fluid">
            <a class="navbar-brand" href="{% url 'dashboard' %}">
                <i class="bi bi-book"></i> Biblioteca Híbrida
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'dashboard' %}">Dashboard</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'listar_libros' %}">
                            <span class="badge badge-mongodb">MongoDB</span> Libros
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'mis_prestamos' %}">
                            <span class="badge badge-mysql">MySQL</span> Mis Préstamos
                        </a>
                    </li>
                    {% if user.is_authenticated %}
                    <li class="nav-item">
                        <span class="nav-link">👤 {{ user.username }}</span>
                    </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>
    
    <!-- Mensajes -->
    <div class="container mt-3">
        {% if messages %}
            {% for message in messages %}
            <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                {{ message }}
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
            {% endfor %}
        {% endif %}
    </div>
    
    <!-- Contenido -->
    <div class="container">
        {% block content %}{% endblock %}
    </div>
    
    <!-- Footer -->
    <footer class="text-center text-white mt-5 pb-3">
        <p>
            <span class="badge badge-mysql">MySQL</span> + 
            <span class="badge badge-mongodb">MongoDB Atlas</span> 
            Sistema Híbrido 2026
        </p>
    </footer>
    
    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

⚠️ IMPORTANTE: Templates Completos en Archivo Separado

El código del template base.html mostrado arriba es solo una vista previa.

Para ver y copiar el código completo de TODOS los templates necesarios para el sistema híbrido, consulta el archivo:

📄 Archivo de Templates Completos:
MONGODB_VIEWS_TEMPLATES_COMPLETOS.html
📂 Ubicación: En la misma carpeta que este archivo

📋 Contenido del archivo:
├── ✅ base.html (Template base con navbar y estilos)
├── ✅ dashboard.html (Vista principal con estadísticas)
├── ✅ listar_libros.html (Catálogo de MongoDB)
└── ✅ mis_prestamos.html (Vista híbrida MySQL + MongoDB)

🎯 Total: 4 templates completos listos para copiar y usar
📝 Instrucciones:
  1. Abre el archivo: MONGODB_VIEWS_TEMPLATES_COMPLETOS.html
  2. Busca cada template por su nombre (base.html, dashboard.html, etc.)
  3. Copia el código completo de cada template
  4. Pégalo en tu proyecto Django en la carpeta correspondiente:
    Estructura de carpetas
    biblioteca_project/
    ├── biblioteca/
    │   └── templates/
    │       └── biblioteca/
    │           ├── base.html           ← Copiar aquí
    │           ├── dashboard.html      ← Copiar aquí
    │           ├── listar_libros.html  ← Copiar aquí
    │           └── mis_prestamos.html  ← Copiar aquí
  5. Verifica que todos los archivos estén creados correctamente

⚠️ NOTA: No omitas este paso. Los templates son esenciales para que el sistema funcione correctamente.

✅ CHECKPOINT 8: Sistema híbrido completo

🎉 ¡FELICIDADES! Has completado el sistema híbrido:

  • MongoDB Atlas: 4 bases de datos con 13 colecciones
  • MySQL: Tabla prestamos con campo libro_id
  • Django Views: 6 views que conectan ambas BDs
  • URLs: Configuradas correctamente
  • Templates: Base template con Bootstrap 5
  • Campo común: libro_id conecta MySQL ↔ MongoDB

🚀 Para ejecutar el proyecto:

PowerShell - Ejecutar servidor
python manage.py runserver

# Abre en el navegador:
# http://localhost:8000/

# Verás:
# - Dashboard con estadísticas de ambas BDs
# - Lista de libros desde MongoDB
# - Sistema de préstamos usando MySQL + MongoDB

4. 💻 Saber Hacer - Implementación con Django

Paso 1: Instalar Dependencias

⏱️ FASE 3: Instalación de Dependencias Python (20 minutos)

Objetivo: Instalar PyMongo, Djongo, dnspython y verificar instalación

1 Crear y activar entorno virtual (si no lo tienes)
PowerShell - Crear entorno virtual
# Navega a tu carpeta de proyecto (ApellidoPaternoApellidoMaterno):
cd C:\PRADODIAZ\biblioteca_project

# Crea entorno virtual:
python -m venv venv

# Activa el entorno virtual:
.\venv\Scripts\Activate

# Deberías ver (venv) al inicio del prompt:
# (venv) PS C:\PRADODIAZ\biblioteca_project>
2 Instalar pymongo (Driver oficial de MongoDB)
PowerShell - Instalación de PyMongo
pip install pymongo==4.6.0

# Deberías ver al final:
# Successfully installed pymongo-4.6.0

✅ Verificar instalación:

PowerShell - Verificar PyMongo
python -c "import pymongo; print('✅ PyMongo version:', pymongo.version)"

# Debe mostrar: ✅ PyMongo version: 4.6.0
3 Instalar dnspython (Requerido para Atlas)
PowerShell - Instalación de dnspython
pip install dnspython==2.4.2

# También instala el soporte SRV completo:
pip install "pymongo[srv]"

# Deberías ver:
# Successfully installed dnspython-2.4.2

✅ Verificar instalación:

PowerShell - Verificar dnspython
python -c "import dns; print('✅ dnspython instalado correctamente')"

# Debe mostrar: ✅ dnspython instalado correctamente
4 Instalar djongo (OPCIONAL - para integración con Django ORM)
PowerShell - Instalación de Djongo
# Nota: Djongo puede tener problemas de compatibilidad con Django 4.x
# Solo instálalo si quieres usar modelos Django con MongoDB

pip install djongo==1.3.6

# Alternativa moderna (usa PyMongo directamente):
# En lugar de djongo, puedes usar PyMongo en las views directamente

⚠️ Problemas conocidos con Djongo:

  • ❌ Djongo 1.3.6 tiene bugs con Django 4.x
  • Alternativa recomendada: Usa PyMongo directamente en las views
  • Ventaja: Más control, menos bugs, mejor rendimiento
  • 📖 En esta guía usaremos PyMongo directo (más estable)
5 Instalar Django REST Framework (para crear API)
PowerShell - Instalación de DRF
pip install djangorestframework==3.14.0

# Deberías ver:
# Successfully installed djangorestframework-3.14.0
6 Guardar dependencias en requirements.txt
PowerShell - Guardar requirements
pip freeze > requirements.txt

# Verifica el contenido:
cat requirements.txt

# Deberías ver (entre otras):
# Django==4.2.x
# pymongo==4.6.0
# dnspython==2.4.2
# djangorestframework==3.14.0

✅ CHECKPOINT 5: Dependencias instaladas correctamente

Verifica que TODO esté instalado con este comando:

PowerShell - Verificación completa
pip list | Select-String "pymongo|dnspython|django|djangorestframework"

# Deberías ver:
# Django                 4.2.x
# djangorestframework    3.14.0
# dnspython              2.4.2
# pymongo                4.6.0

✅ Si ves TODAS esas líneas: ¡Perfecto! Continúa al siguiente paso

❌ Si falta alguna: Vuelve atrás e instálala con pip install [paquete]

🧪 TEST: Probar conexión a MongoDB Atlas

Antes de continuar con Django, verifica que puedes conectarte a Atlas:

Python - test_connection.py
# Crea un archivo test_connection.py en la raíz del proyecto:

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure
import sys

# ⚠️ REEMPLAZA con tu connection string real:
MONGO_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.xxxxx.mongodb.net/biblioteca_logs?retryWrites=true&w=majority"

print("🔍 Intentando conectar a MongoDB Atlas...")
print(f"📡 URI: {MONGO_URI[:50]}...")

try:
    # Crear cliente con timeout de 5 segundos
    client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
    
    # Forzar conexión (lazy connection por defecto)
    server_info = client.server_info()
    
    print("✅ ¡CONEXIÓN EXITOSA!")
    print(f"📊 MongoDB Version: {server_info['version']}")
    print(f"🗄️ Bases de datos: {client.list_database_names()}")
    
    # Probar escritura
    db = client['biblioteca_logs']
    test_collection = db['test']
    result = test_collection.insert_one({'test': 'Conexión exitosa', 'timestamp': '2026-01-20'})
    print(f"✅ Documento de prueba insertado con ID: {result.inserted_id}")
    
    # Limpiar test
    test_collection.delete_one({'_id': result.inserted_id})
    print("🧹 Documento de prueba eliminado")
    
    print("\n🎉 ¡Todo funciona correctamente! Puedes continuar con Django.")
    
except ConnectionFailure as e:
    print(f"❌ ERROR DE CONEXIÓN: {e}")
    print("\n🔧 POSIBLES SOLUCIONES:")
    print("   1. Verifica tu connection string (usuario, contraseña)")
    print("   2. Revisa Network Access en Atlas (IP whitelist)")
    print("   3. Verifica que el cluster esté 'Active' (no 'Paused')")
    sys.exit(1)

except Exception as e:
    print(f"❌ ERROR: {e}")
    sys.exit(1)

finally:
    client.close()
    print("🔌 Conexión cerrada")

Ejecuta el test:

PowerShell - Ejecutar test
python test_connection.py

# Salida esperada:
# 🔍 Intentando conectar a MongoDB Atlas...
# ✅ ¡CONEXIÓN EXITOSA!
# 📊 MongoDB Version: 7.0.5
# 🗄️ Bases de datos: ['admin', 'biblioteca_logs', 'local']
# ✅ Documento de prueba insertado con ID: 65a1b2c3d4e5f6789...
# 🧹 Documento de prueba eliminado
# 🎉 ¡Todo funciona correctamente! Puedes continuar con Django.
PowerShell - Instalación de bibliotecas (RESUMEN RÁPIDO)
# Si solo quieres copiar y pegar todo de una vez:
.\venv\Scripts\Activate
pip install pymongo==4.6.0
pip install dnspython==2.4.2
pip install "pymongo[srv]"
pip install djangorestframework==3.14.0
pip freeze > requirements.txt

# Verificar:
pip list | Select-String "pymongo|dnspython|django"

Paso 2: Configurar Django con MongoDB

biblioteca_project/settings.py
import os  # Para manejar variables de entorno

# Configuración de múltiples bases de datos (MySQL + MongoDB)
DATABASES = {
    'default': {  # Base de datos principal (MySQL)
        'ENGINE': 'django.db.backends.mysql',  # Motor de MySQL
        'NAME': 'biblioteca_db',  # Nombre de la base de datos MySQL
        'USER': 'root',  # Usuario de MySQL
        'PASSWORD': 'password',  # Contraseña de MySQL
        'HOST': 'localhost',  # Host de MySQL (local)
        'PORT': '3306',  # Puerto por defecto de MySQL
    },
    'mongodb': {  # Base de datos secundaria (MongoDB Atlas)
        'ENGINE': 'djongo',  # Motor Djongo para MongoDB
        'NAME': 'biblioteca_logs',  # Nombre de la base de datos en MongoDB
        'ENFORCE_SCHEMA': False,  # Permite esquemas flexibles (NoSQL)
        'CLIENT': {
            'host': os.environ.get(  # Obtiene connection string de variable de entorno
                'MONGODB_URI',
                'mongodb+srv://usuario:password@cluster.mongodb.net/biblioteca_logs?retryWrites=true&w=majority'
            )  # Connection string de Atlas (reemplazar con el tuyo)
        }
    }
}

# Router para distribuir modelos entre bases de datos
DATABASE_ROUTERS = ['libros.db_router.MongoDBRouter']  # Enrutador personalizado para decidir qué BD usar

# En producción, configurar variables de entorno:
# MONGODB_URI=mongodb+srv://usuario:password@cluster.mongodb.net/

5. 🚀 Proyecto: Sistema Híbrido MySQL + MongoDB

Arquitectura del Sistema

💡 Estrategia Híbrida:

  • MySQL: Datos estructurados (Libros, Autores, Usuarios)
  • MongoDB: Logs, Analytics, Datos no estructurados

Paso 1: Crear Modelos para MongoDB

libros/models.py
from djongo import models  # Importa models de djongo en lugar de django.db
from django.contrib.auth.models import User  # Usuario de Django

class LogActividad(models.Model):
    """
    Modelo para registrar actividad de usuarios en MongoDB
    No requiere migraciones, es totalmente flexible
    """
    
    usuario = models.CharField(max_length=100)  # Nombre del usuario que realizó la acción
    accion = models.CharField(max_length=50)  # Tipo de acción (CREATE, UPDATE, DELETE, VIEW)
    modelo = models.CharField(max_length=50)  # Nombre del modelo afectado (Libro, Autor, etc)
    objeto_id = models.IntegerField()  # ID del objeto afectado
    detalles = models.JSONField()  # Información adicional en formato JSON flexible
    timestamp = models.DateTimeField(auto_now_add=True)  # Fecha y hora automática de creación
    ip_address = models.GenericIPAddressField()  # Dirección IP del usuario
    user_agent = models.TextField(blank=True)  # Información del navegador/cliente
    
    class Meta:
        db_table = 'logs_actividad'  # Nombre de la colección en MongoDB
        managed = False  # Django no intentará crear migraciones para este modelo
        
    def __str__(self):
        return f"{self.usuario} - {self.accion} en {self.modelo} - {self.timestamp}"


class EstadisticaLibro(models.Model):
    """
    Estadísticas de visualizaciones y préstamos de libros
    Almacenadas en MongoDB para análisis
    """
    
    libro_id = models.IntegerField()  # ID del libro en MySQL
    titulo = models.CharField(max_length=300)  # Título del libro (desnormalizado para rapidez)
    visualizaciones = models.IntegerField(default=0)  # Cantidad de veces visto
    prestamos = models.IntegerField(default=0)  # Cantidad de veces prestado
    calificacion_promedio = models.FloatField(default=0.0)  # Promedio de calificaciones
    
    # Datos de popularidad por mes (array de objetos)
    estadisticas_mensuales = models.JSONField(default=list)  # [{mes: 1, views: 50, prestamos: 10}, ...]
    
    ultima_actualizacion = models.DateTimeField(auto_now=True)  # Se actualiza automáticamente
    
    class Meta:
        db_table = 'estadisticas_libros'  # Colección en MongoDB
        managed = False  # Sin migraciones

Paso 2: Crear Router de Bases de Datos

libros/db_router.py
class MongoDBRouter:
    """
    Router para dirigir ciertos modelos a MongoDB
    y otros a MySQL (base de datos por defecto)
    """
    
    # Lista de modelos que deben usar MongoDB
    mongodb_models = ['LogActividad', 'EstadisticaLibro']  # Modelos que van a MongoDB
    
    def db_for_read(self, model, **hints):
        """Determina qué base de datos usar para lectura"""
        if model.__name__ in self.mongodb_models:  # Si el modelo está en la lista
            return 'mongodb'  # Usa MongoDB para leer
        return 'default'  # Usa MySQL por defecto
    
    def db_for_write(self, model, **hints):
        """Determina qué base de datos usar para escritura"""
        if model.__name__ in self.mongodb_models:
            return 'mongodb'  # Escribe en MongoDB
        return 'default'  # Escribe en MySQL
    
    def allow_relation(self, obj1, obj2, **hints):
        """Permite relaciones solo dentro de la misma BD"""
        db1 = self.db_for_read(obj1.__class__)  # Base de datos del objeto 1
        db2 = self.db_for_read(obj2.__class__)  # Base de datos del objeto 2
        return db1 == db2  # Solo permite si están en la misma BD
    
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """Controla qué migraciones se aplican a cada BD"""
        if db == 'mongodb':
            return False  # MongoDB no necesita migraciones
        return True  # MySQL sí necesita migraciones

6. 🔧 Operaciones CRUD con MongoDB

Usando Django ORM con Djongo

Ejemplos de operaciones CRUD
from libros.models import LogActividad, EstadisticaLibro

# ===== CREATE (Crear documento) =====
log = LogActividad.objects.create(  # Crea y guarda en MongoDB
    usuario='admin',
    accion='CREATE',
    modelo='Libro',
    objeto_id=123,
    detalles={'titulo': 'Cien Años de Soledad', 'autor': 'García Márquez'},  # JSON flexible
    ip_address='192.168.1.1'
)

# ===== READ (Leer documentos) =====
# Obtener todos los logs
todos_logs = LogActividad.objects.all()  # SELECT * equivalente

# Filtrar por usuario
logs_admin = LogActividad.objects.filter(usuario='admin')  # WHERE usuario = 'admin'

# Filtrar por múltiples condiciones
logs_recientes = LogActividad.objects.filter(
    accion='DELETE',  # Acción específica
    timestamp__gte='2026-01-01'  # Mayor o igual a fecha (gte = greater than or equal)
).order_by('-timestamp')  # Ordenar por timestamp descendente

# Obtener un documento por ID
log = LogActividad.objects.get(pk=log_id)  # Busca por primary key (_id en MongoDB)

# ===== UPDATE (Actualizar) =====
# Actualizar un documento
log = LogActividad.objects.get(pk=log_id)
log.detalles['actualizado'] = True  # Modifica el JSON
log.save()  # Guarda cambios en MongoDB

# Actualizar múltiples documentos
LogActividad.objects.filter(
    usuario='test'
).update(  # UPDATE WHERE usuario = 'test'
    user_agent='Updated'
)

# ===== DELETE (Eliminar) =====
# Eliminar un documento
log = LogActividad.objects.get(pk=log_id)
log.delete()  # Elimina de MongoDB

# Eliminar múltiples documentos
LogActividad.objects.filter(
    timestamp__lt='2025-01-01'  # Menores a fecha (lt = less than)
).delete()  # DELETE WHERE timestamp < '2025-01-01'

# ===== AGREGACIONES =====
from django.db.models import Count, Avg

# Contar logs por usuario
conteo = LogActividad.objects.values('usuario').annotate(  # GROUP BY usuario
    total=Count('id')  # COUNT(*)
)

# Promedio de visualizaciones
promedio = EstadisticaLibro.objects.aggregate(  # Agregación
    Avg('visualizaciones')  # AVG(visualizaciones)
)

Usando PyMongo Directamente

libros/mongodb_service.py
import pymongo
from django.conf import settings
from datetime import datetime

class MongoDBService:
    """Servicio para operaciones avanzadas con MongoDB"""
    
    def __init__(self):
        # Conectar a MongoDB Atlas
        self.client = pymongo.MongoClient(  # Crea cliente de MongoDB
            settings.DATABASES['mongodb']['CLIENT']['host']  # Usa connection string de settings
        )
        self.db = self.client['biblioteca_logs']  # Selecciona base de datos
    
    def registrar_actividad(self, usuario, accion, modelo, objeto_id, detalles, ip):
        """Registra una actividad del usuario"""
        documento = {  # Crea documento JSON
            'usuario': usuario,
            'accion': accion,
            'modelo': modelo,
            'objeto_id': objeto_id,
            'detalles': detalles,  # Puede ser cualquier estructura JSON
            'timestamp': datetime.now(),  # Fecha actual
            'ip_address': ip
        }
        resultado = self.db.logs_actividad.insert_one(documento)  # Inserta en colección
        return str(resultado.inserted_id)  # Retorna ID generado
    
    def obtener_estadisticas_usuario(self, usuario):
        """Obtiene estadísticas agregadas de un usuario"""
        pipeline = [  # Pipeline de agregación de MongoDB
            {'$match': {'usuario': usuario}},  # Filtrar por usuario (WHERE)
            {'$group': {  # Agrupar (GROUP BY)
                '_id': '$accion',  # Agrupar por acción
                'total': {'$sum': 1},  # Contar (COUNT)
                'ultima_vez': {'$max': '$timestamp'}  # Fecha más reciente (MAX)
            }}
        ]
        resultado = self.db.logs_actividad.aggregate(pipeline)  # Ejecuta agregación
        return list(resultado)  # Convierte cursor a lista
    
    def limpiar_logs_antiguos(self, dias=30):
        """Elimina logs más antiguos que X días"""
        from datetime import timedelta
        fecha_limite = datetime.now() - timedelta(days=dias)  # Calcula fecha límite
        
        resultado = self.db.logs_actividad.delete_many({  # Elimina múltiples documentos
            'timestamp': {'$lt': fecha_limite}  # Timestamp menor que fecha_limite
        })
        return resultado.deleted_count  # Retorna cantidad eliminada

6.5 🎨 Templates HTML (INTERFAZ DE USUARIO COMPLETA)

📄

Views y Templates Completos Disponibles

¡TODAS las 7 views + 6 templates HTML en un solo documento!

Incluye explicación completa de cómo se conectan MySQL y MongoDB

📚 Abrir Documento Completo

✅ Código completo listo para copiar | ✅ Explicación detallada | ✅ Sistema híbrido MySQL + MongoDB

⏱️ FASE 4: Crear Interfaz de Usuario (30 minutos)

Objetivo: Crear páginas HTML para visualizar y gestionar libros en MongoDB

Archivos a crear: 5 templates HTML + 1 archivo base

Estructura de Templates

Crea esta estructura de carpetas:

Estructura de Carpetas
biblioteca_project/
├── libros/
│   ├── templates/
│   │   └── libros/
│   │       ├── base.html           # ← Template base con header/footer
│   │       ├── inicio.html         # ← Página de inicio
│   │       ├── lista_libros.html   # ← Catálogo de libros
│   │       ├── detalle_libro.html  # ← Vista de un libro específico
│   │       ├── crear_libro.html    # ← Formulario para agregar libro
│   │       └── estadisticas.html   # ← Dashboard con gráficas
│   ├── static/
│   │   └── libros/
│   │       └── css/
│   │           └── estilos.css     # ← Estilos personalizados

📄 Template 1: base.html (Template Base)

libros/templates/libros/base.html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Biblioteca UTH{% endblock %}</title>
    
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Font Awesome Icons -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
    
    <style>
        :root {
            --primary-color: #f093fb;
            --secondary-color: #f5576c;
            --dark-color: #2c3e50;
            --light-gray: #ecf0f1;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .main-container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        
        .navbar {
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important;
        }
        
        .navbar-brand, .navbar-nav .nav-link {
            color: white !important;
        }
        
        .navbar-nav .nav-link:hover {
            background: rgba(255,255,255,0.2);
            border-radius: 5px;
        }
        
        .content-wrapper {
            padding: 40px;
        }
        
        .card {
            border: none;
            border-radius: 12px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 25px rgba(0,0,0,0.2);
        }
        
        .btn-primary {
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
            border: none;
        }
        
        .btn-primary:hover {
            background: linear-gradient(135deg, #f5576c 0%, #f093fb 100%);
        }
        
        footer {
            background: var(--dark-color);
            color: white;
            text-align: center;
            padding: 30px;
            margin-top: 50px;
        }
    </style>
    
    {% block extra_css %}{% endblock %}
</head>
<body>
    <div class="main-container">
        <!-- Navigation Bar -->
        <nav class="navbar navbar-expand-lg">
            <div class="container-fluid">
                <a class="navbar-brand" href="{% url 'inicio' %}">
                    <i class="fas fa-book-open"></i> Biblioteca UTH
                </a>
                
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav ms-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'inicio' %}">
                                <i class="fas fa-home"></i> Inicio
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'lista_libros' %}">
                                <i class="fas fa-books"></i> Catálogo
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'crear_libro' %}">
                                <i class="fas fa-plus-circle"></i> Agregar Libro
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'estadisticas' %}">
                                <i class="fas fa-chart-bar"></i> Estadísticas
                            </a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
        
        <!-- Messages (Django Messages Framework) -->
        {% if messages %}
            <div class="container mt-3">
                {% for message in messages %}
                    <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                    </div>
                {% endfor %}
            </div>
        {% endif %}
        
        <!-- Main Content -->
        <div class="content-wrapper">
            {% block content %}{% endblock %}
        </div>
        
        <!-- Footer -->
        <footer>
            <p>© 2026 Biblioteca UTH - Proyecto MongoDB + Django</p>
            <p><small>Desarrollado con ❤️ usando MongoDB Atlas, Django y Bootstrap</small></p>
        </footer>
    </div>
    
    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    
    {% block extra_js %}{% endblock %}
</body>
</html>

📄 Template 2: inicio.html (Página de Inicio)

libros/templates/libros/inicio.html
{% extends 'libros/base.html' %}

{% block title %}Inicio - Biblioteca UTH{% endblock %}

{% block content %}
<div class="text-center mb-5">
    <h1 class="display-3"><i class="fas fa-database"></i> Bienvenido a Biblioteca UTH</h1>
    <p class="lead">Sistema de gestión de libros con MongoDB Atlas y Django</p>
</div>

<div class="row mb-4">
    <!-- Card: Total Libros -->
    <div class="col-md-4 mb-3">
        <div class="card text-center">
            <div class="card-body">
                <i class="fas fa-books fa-3x text-primary mb-3"></i>
                <h3 class="card-title">{{ total_libros }}</h3>
                <p class="card-text">Libros Registrados</p>
                <a href="{% url 'lista_libros' %}" class="btn btn-primary">Ver Catálogo</a>
            </div>
        </div>
    </div>
    
    <!-- Card: Agregar Libro -->
    <div class="col-md-4 mb-3">
        <div class="card text-center">
            <div class="card-body">
                <i class="fas fa-plus-circle fa-3x text-success mb-3"></i>
                <h3 class="card-title">Agregar</h3>
                <p class="card-text">Nuevo Libro</p>
                <a href="{% url 'crear_libro' %}" class="btn btn-success">Crear Libro</a>
            </div>
        </div>
    </div>
    
    <!-- Card: Estadísticas -->
    <div class="col-md-4 mb-3">
        <div class="card text-center">
            <div class="card-body">
                <i class="fas fa-chart-bar fa-3x text-warning mb-3"></i>
                <h3 class="card-title">Analytics</h3>
                <p class="card-text">Ver Estadísticas</p>
                <a href="{% url 'estadisticas' %}" class="btn btn-warning">Dashboard</a>
            </div>
        </div>
    </div>
</div>

<div class="alert alert-info">
    <h4><i class="fas fa-info-circle"></i> Información del Sistema</h4>
    <ul>
        <li><strong>Base de Datos:</strong> MongoDB Atlas (Cloud)</li>
        <li><strong>Framework:</strong> Django {{ django_version }}</li>
        <li><strong>Driver:</strong> PyMongo {{ pymongo_version }}</li>
    </ul>
</div>
{% endblock %}

✅ CHECKPOINT 6: Templates base creados

Archivos creados hasta ahora:

  • base.html - Template base con navbar y estilos
  • inicio.html - Página principal con dashboard

Siguiente: Crear templates para listar y gestionar libros

📝 NOTA: Views correspondientes

Para que estos templates funcionen, necesitas crear las views en libros/views.py

Ver sección completa de código de views más adelante en la guía

7. 🛡️ Mejores Prácticas y Seguridad

🔐 Seguridad en MongoDB Atlas

❌ NUNCA hagas esto:

  1. Hardcodear connection strings con credenciales en código
  2. Usar contraseña débil para usuario de BD (123456, admin, password)
  3. Permitir acceso desde "0.0.0.0/0" (cualquier IP) en producción
  4. Subir archivos .env con credenciales a GitHub público
  5. Compartir cluster entre proyectos sin segregación de datos
  6. Deshabilitar autenticación en desarrollo

✅ SÍ haz esto:

  1. Variables de Entorno:
    .env (en raíz del proyecto)
    MONGODB_URI=mongodb+srv://usuario:password@cluster0.mongodb.net/biblioteca?retryWrites=true&w=majority
    MONGODB_DB_NAME=biblioteca_db
    settings.py (Seguro)
    from decouple import config
    
    MONGODB_SETTINGS = {
        'URI': config('MONGODB_URI'),  # Lee de .env
        'DB_NAME': config('MONGODB_DB_NAME', default='biblioteca_db')
    }
    
    # Verificar que esté configurado
    if not MONGODB_SETTINGS['URI']:
        raise ValueError("MONGODB_URI no configurada en .env")
    .gitignore (CRÍTICO)
    .env
    *.pyc
    __pycache__/
    venv/
    db.sqlite3
    /staticfiles/
    *.log
  2. Contraseña Fuerte:
    • Mínimo 16 caracteres
    • Mayúsculas + minúsculas + números + símbolos
    • Usar generador de contraseñas
    • Ejemplo: xK9$mP2@vL5#nQ8&wR4
  3. Whitelist de IPs en Atlas:
    • Network Access → Add IP Address
    • Desarrollo: Tu IP pública actual
    • Producción: IP del servidor (AWS, Heroku, etc.)
    • ⚠️ Evitar "Allow Access from Anywhere" (0.0.0.0/0)
  4. Roles y Permisos:
    Database Access (MongoDB Atlas)
    // Usuario solo lectura (reportes)
    Role: "read"
    Database: "biblioteca_db"
    
    // Usuario lectura-escritura (app)
    Role: "readWrite"
    Database: "biblioteca_db"
    
    // Administrador (DBA)
    Role: "dbAdmin"
    Database: "biblioteca_db"

🚀 Optimización de Rendimiento

1. Índices Estratégicos

Crear Índices en MongoDB
# Índice simple (1 campo)
db.libros.create_index([("isbn", 1)])  # 1 = ascendente, -1 = descendente

# Índice compuesto (múltiples campos)
db.libros.create_index([
    ("categoria", 1),
    ("precio", -1)
])

# Índice de texto (búsqueda full-text)
db.libros.create_index([("titulo", "text"), ("descripcion", "text")])

# Índice único (sin duplicados)
db.libros.create_index([("isbn", 1)], unique=True)

# Ver índices existentes
db.libros.get_indexes()

2. Proyecciones (Solo Campos Necesarios)

Reducir Datos Transferidos
# ❌ Malo: Traer todos los campos (documento completo)
libros = db.libros.find({})

# ✅ Bueno: Solo campos necesarios
libros = db.libros.find(
    {},  # Filtro vacío = todos los documentos
    {"titulo": 1, "precio": 1, "_id": 0}  # 1 = incluir, 0 = excluir
)
# Resultado: [{"titulo": "...", "precio": 15.99}, ...]

3. Paginación con skip y limit

Paginar Resultados
def obtener_libros_paginados(pagina=1, por_pagina=20):
    skip = (pagina - 1) * por_pagina
    
    libros = db.libros.find().skip(skip).limit(por_pagina)
    total = db.libros.count_documents({})
    
    return {
        'libros': list(libros),
        'pagina': pagina,
        'total': total,
        'paginas': (total + por_pagina - 1) // por_pagina
    }

4. Connection Pooling

Reutilizar Conexiones
from pymongo import MongoClient

# ✅ Singleton: Una sola instancia de cliente
class MongoDBConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.client = MongoClient(
                settings.MONGODB_URI,
                maxPoolSize=50,  # Max conexiones en pool
                minPoolSize=10   # Min conexiones siempre abiertas
            )
        return cls._instance
    
    def get_db(self):
        return self.client[settings.MONGODB_DB_NAME]

📊 Modelado de Datos en MongoDB

Reglas de Oro:

  1. Embedding para relaciones 1-a-pocos:
    • Libro → Reseñas (1 libro tiene 10-20 reseñas)
    • Usuario → Direcciones (1 usuario tiene 2-3 direcciones)
  2. Referencing para relaciones muchos-a-muchos:
    • Libros ↔ Categorías (1000+ libros en 1 categoría)
    • Usuarios ↔ Libros Favoritos
  3. Límite de 16 MB por documento:
    • No almacenar archivos grandes (usar GridFS o S3)
    • Limitar arrays anidados (max 100-200 elementos)
  4. Diseñar para queries comunes:
    • Si siempre consultas libro+autor juntos → embedding
    • Si actualizas autor frecuentemente → referencing

8. 💡 Casos de Uso Avanzados

1. Agregaciones Complejas (Pipeline)

Objetivo: Obtener estadísticas de libros por categoría.

Aggregation Pipeline
pipeline = [
    {
        "$unwind": "$categorias"  # Descomponer array de categorías
    },
    {
        "$group": {  # Agrupar por categoría
            "_id": "$categorias",
            "total_libros": { "$sum": 1 },
            "precio_promedio": { "$avg": "$precio" },
            "precio_max": { "$max": "$precio" },
            "precio_min": { "$min": "$precio" }
        }
    },
    {
        "$sort": { "total_libros": -1 }  # Ordenar descendente
    },
    {
        "$limit": 10  # Top 10 categorías
    }
]

resultado = db.libros.aggregate(pipeline)

# Resultado:
# [
#   {"_id": "Literatura", "total_libros": 150, "precio_promedio": 18.50, ...},
#   {"_id": "Ficción", "total_libros": 120, "precio_promedio": 16.75, ...},
#   ...
# ]

2. Búsqueda Full-Text

Objetivo: Buscar libros por palabras clave en título y descripción.

Text Search
# Crear índice de texto
db.libros.create_index([
    ("titulo", "text"),
    ("descripcion", "text")
])

# Buscar libros que contengan "amor" o "soledad"
resultados = db.libros.find({
    "$text": {
        "$search": "amor soledad"
    }
})

# Buscar frase exacta
resultados = db.libros.find({
    "$text": {
        "$search": "\"Cien Años de Soledad\""
    }
})

# Búsqueda con score (relevancia)
resultados = db.libros.find(
    { "$text": { "$search": "amor" } },
    { "score": { "$meta": "textScore" } }
).sort([("score", {"$meta": "textScore"})])

3. Transacciones Multi-Documento (MongoDB 4.0+)

Objetivo: Asegurar consistencia en operaciones que afectan múltiples colecciones.

Transacciones ACID
from pymongo import MongoClient

client = MongoClient(MONGODB_URI)
session = client.start_session()

try:
    with session.start_transaction():
        # Reducir stock del libro
        db.libros.update_one(
            {"_id": libro_id},
            {"$inc": {"stock": -1}},
            session=session
        )
        
        # Crear orden de compra
        db.ordenes.insert_one({
            "libro_id": libro_id,
            "usuario_id": usuario_id,
            "fecha": datetime.now()
        }, session=session)
        
        # Si todo OK, commit
        session.commit_transaction()
        print("✅ Compra exitosa")
        
except Exception as e:
    # Si hay error, rollback
    session.abort_transaction()
    print(f"❌ Error: {e}")
    
finally:
    session.end_session()

🆘 SOLUCIÓN DE PROBLEMAS (TROUBLESHOOTING)

📋 Guía Rápida de Diagnóstico

Si algo no funciona, sigue este orden:

  1. 🔍 Lee el mensaje de error COMPLETO
  2. 📝 Identifica el tipo de error (conexión, autenticación, sintaxis, etc.)
  3. 🔧 Busca la solución específica en las secciones de abajo
  4. Verifica el fix ejecutando el código de nuevo
  5. Si persiste, revisa logs y contacta soporte

🔴 Problema 1: Error de Conexión a MongoDB Atlas

❌ Síntomas:

Error en PowerShell/Python
pymongo.errors.ServerSelectionTimeoutError: 
    localhost:27017: [Errno 111] Connection refused, 
    Timeout: 30s, Topology Description: 
    <TopologyDescription id: ... >

O también:

pymongo.errors.ConfigurationError: 
    The "dnspython" module must be installed to use mongodb+srv:// URIs

O también:

pymongo.errors.OperationFailure: 
    bad auth : authentication failed

✅ Soluciones (prueba en orden):

Solución 1: Verificar Connection String
  1. 📋 Abre tu archivo mongodb_credentials.txt
  2. 🔍 Verifica que NO tenga los símbolos <password>
  3. 🔑 Asegúrate de que la contraseña esté CORRECTA (sin espacios extra)
  4. 🌐 Verifica que diga mongodb+srv:// (NO mongodb://)
✅ Connection String CORRECTO
# BIEN ✅ (sin < > y con contraseña real):
mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.abc123.mongodb.net/biblioteca_logs?retryWrites=true&w=majority

# MAL ❌ (con < > literal):
mongodb+srv://biblioteca_admin:<password>@bibliotecacluster.abc123.mongodb.net/
Solución 2: Instalar dnspython
PowerShell - Instalar dependencia faltante
pip install dnspython==2.4.2
# O versión específica compatible:
pip install "pymongo[srv]"
Solución 3: Verificar IP Whitelist en Atlas
  1. 🌐 Ve a cloud.mongodb.com
  2. 🔍 Abre tu proyecto → "Network Access" (menú izquierdo)
  3. 👁️ Verifica que tu IP esté en la lista o que esté 0.0.0.0/0
  4. 🟢 Si NO está, haz clic en "Add IP Address"
  5. ✅ Agrega 0.0.0.0/0 (permite todas las IPs - solo para desarrollo)
  6. ⏳ Espera 1-2 minutos a que se aplique el cambio
Solución 4: Verificar credenciales de usuario
  1. 🔐 Ve a Atlas → Proyecto → "Database Access"
  2. 👤 Verifica que el usuario biblioteca_admin exista
  3. 🔑 Si no recuerdas la contraseña: Haz clic en "Edit""Edit Password"
  4. 🆕 Genera nueva contraseña y actualiza tu connection string
Solución 5: Probar conexión manualmente
Python - Test de conexión
from pymongo import MongoClient
import sys

# Reemplaza con tu connection string real:
MONGO_URI = "mongodb+srv://biblioteca_admin:Biblioteca2026!@bibliotecacluster.abc123.mongodb.net/biblioteca_logs?retryWrites=true&w=majority"

try:
    client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
    client.server_info()  # Forzar conexión
    print("✅ Conexión EXITOSA a MongoDB Atlas!")
    print(f"📊 Versión MongoDB: {client.server_info()['version']}")
    print(f"🗄️ Bases de datos disponibles: {client.list_database_names()}")
except Exception as e:
    print(f"❌ Error de conexión: {e}")
    sys.exit(1)
finally:
    client.close()

🔴 Problema 2: Error "No module named 'pymongo'" o "No module named 'djongo'"

❌ Síntomas:

Error al ejecutar servidor Django
ModuleNotFoundError: No module named 'pymongo'
ModuleNotFoundError: No module named 'djongo'
ModuleNotFoundError: No module named 'dnspython'

✅ Soluciones:

Solución 1: Verificar que el entorno virtual esté ACTIVADO
PowerShell - Verificar y activar venv
# Verifica si el prompt tiene (venv) al inicio:
# (venv) PS C:\proyecto> ← ✅ ACTIVADO
# PS C:\proyecto>       ← ❌ NO ACTIVADO

# Si NO está activado:
.\venv\Scripts\Activate

# Ahora deberías ver:
# (venv) PS C:\proyecto>
Solución 2: Instalar todas las dependencias de nuevo
PowerShell - Reinstalar paquetes
# Asegúrate de estar en el entorno virtual:
.\venv\Scripts\Activate

# Instala TODO de nuevo:
pip install pymongo==4.6.0
pip install djongo==1.3.6
pip install dnspython==2.4.2
pip install "pymongo[srv]"

# Verifica instalación:
pip list | Select-String "pymongo|djongo|dnspython"

# Deberías ver:
# djongo         1.3.6
# dnspython      2.4.2
# pymongo        4.6.0
Solución 3: Usar pip3 si pip no funciona
PowerShell - Alternativa con pip3
pip3 install pymongo djongo dnspython

🔴 Problema 3: Error "Authentication failed" o "bad auth"

❌ Síntomas:

Error de autenticación
pymongo.errors.OperationFailure: bad auth : authentication failed
pymongo.errors.OperationFailure: Authentication failed.

✅ Soluciones:

Solución 1: Verificar usuario y contraseña
  1. 🔍 Verifica que el usuario en el connection string coincida con el de Atlas
  2. 🔑 Verifica que la contraseña NO tenga caracteres especiales problemáticos
  3. 🌐 Ve a Atlas → "Database Access" → Verifica el usuario
Caracteres especiales que pueden causar problemas
# Si tu contraseña tiene estos caracteres, puede fallar:
# @ : / ? # [ ] % &

# Ejemplo de contraseña PROBLEMÁTICA:
# MiPass@123#  ← El @ puede confundirse con el separador del host

# Solución 1: Cambiar contraseña a una sin caracteres especiales
# Ejemplo: MiPassword2026

# Solución 2: URL-encode los caracteres especiales
# @ → %40
# # → %23
# : → %3A
Solución 2: Resetear contraseña en Atlas
  1. 🌐 Ve a cloud.mongodb.com
  2. 🔐 Proyecto → "Database Access"
  3. ✏️ Busca tu usuario → "Edit"
  4. 🔑 "Edit Password" → Genera nueva contraseña SIMPLE (sin símbolos)
  5. 💾 Guarda y actualiza tu connection string

🔴 Problema 4: Django no encuentra modelos de MongoDB (djongo)

❌ Síntomas:

Error al hacer migraciones
django.db.utils.OperationalError: (2003, "Can't connect to MySQL server on 'localhost' ([Errno 111] Connection refused)")

O también:

RuntimeError: Model class ... doesn't declare an explicit app_label

✅ Soluciones:

Solución 1: Configurar Meta.managed = False en modelos MongoDB
libros/models.py - Configuración correcta
from djongo import models

class LogActividad(models.Model):
    usuario = models.CharField(max_length=100)
    accion = models.CharField(max_length=50)
    
    class Meta:
        db_table = 'logs_actividad'
        managed = False  # ← IMPORTANTE: Django no intentará migraciones
        app_label = 'libros'  # ← Especifica la app explícitamente
Solución 2: Usar database router correctamente
libros/db_router.py
class MongoDBRouter:
    """
    Router para dirigir operaciones de modelos específicos a MongoDB
    """
    
    mongodb_models = ['LogActividad', 'EstadisticaLibro']  # Modelos que usan MongoDB
    
    def db_for_read(self, model, **hints):
        if model.__name__ in self.mongodb_models:
            return 'mongodb'
        return 'default'
    
    def db_for_write(self, model, **hints):
        if model.__name__ in self.mongodb_models:
            return 'mongodb'
        return 'default'
    
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if model_name in [m.lower() for m in self.mongodb_models]:
            return db == 'mongodb'
        return db == 'default'
Solución 3: Migrar solo MySQL, no MongoDB
PowerShell - Migraciones selectivas
# Migrar SOLO la base de datos MySQL (default):
python manage.py makemigrations
python manage.py migrate --database=default

# NO ejecutes migrate para MongoDB (se maneja directo con PyMongo)
# MongoDB NO necesita migraciones (NoSQL es schema-less)

🔴 Problema 5: "BSON library not found" o errores de serialización

❌ Síntomas:

Error al insertar/leer documentos
ImportError: cannot import name 'BSON' from 'bson'
TypeError: Object of type ObjectId is not JSON serializable

✅ Soluciones:

Solución 1: Convertir ObjectId a string en serialización
libros/views.py - Serializar correctamente
from bson import ObjectId
import json

def listar_libros(request):
    libros = list(db.libros.find())
    
    # Convertir ObjectId a string para JSON:
    for libro in libros:
        libro['_id'] = str(libro['_id'])  # ← Conversión importante
    
    return JsonResponse({'libros': libros}, safe=False)
Solución 2: Usar JSONEncoder personalizado
libros/utils.py - Encoder para MongoDB
from bson import ObjectId
from datetime import datetime
import json

class MongoJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, ObjectId):
            return str(obj)
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

# Uso en views:
from django.http import JsonResponse
import json

def listar_libros(request):
    libros = list(db.libros.find())
    libros_json = json.dumps(libros, cls=MongoJSONEncoder)
    return JsonResponse(json.loads(libros_json), safe=False)

🔴 Problema 6: Cluster de Atlas en estado "Paused" o "Stopped"

❌ Síntomas:

  • El cluster aparece con estado "Paused" en Atlas dashboard
  • Error: "Server selection timeout" después de 30 segundos
  • No se puede conectar aunque las credenciales sean correctas

✅ Solución:

  1. 🌐 Ve a cloud.mongodb.com
  2. 🔍 Localiza tu cluster en el dashboard
  3. ▶️ Si dice "Paused": Haz clic en los 3 puntitos → "Resume"
  4. ⏳ Espera 2-3 minutos a que el cluster se active
  5. ✅ El estado debe cambiar a "Active" (bolita verde)
  6. 🔄 Intenta conectar nuevamente

ℹ️ ¿Por qué se pausa?

Los clusters gratuitos M0 se pausan automáticamente después de 60 días de inactividad para ahorrar recursos.

Solución permanente: Usa el cluster regularmente (al menos una vez por semana).

📞 ¿Aún tienes problemas?

🛠️ Pasos adicionales de debugging:

  1. Revisa logs completos de Django:
    PowerShell
    python manage.py runserver --noreload
  2. Verifica versiones instaladas:
    PowerShell
    pip list | Select-String "django|pymongo|djongo|dnspython"
  3. Consulta documentación oficial:
  4. Contacta a tu instructor: Proporciona screenshot del error y los pasos que ya intentaste

✅ Checklist Final de Verificación:

Item ¿Cómo verificar? Estado
Connection String correcto Sin <password>, con contraseña real
Usuario existe en Atlas Database Access → Ver lista de usuarios
IP en whitelist Network Access → Ver IPs permitidas
Cluster activo Dashboard → Estado "Active" (verde)
PyMongo instalado pip list | Select-String pymongo
dnspython instalado pip list | Select-String dnspython
Entorno virtual activado Prompt tiene (venv) al inicio

9. 📚 Recursos Adicionales y Referencias

📖 Documentación Oficial

🎓 Cursos Recomendados (Gratuitos)

  • M001: MongoDB Basics (MongoDB University)
  • M121: The MongoDB Aggregation Framework (MongoDB University)
  • M220P: MongoDB for Python Developers (MongoDB University)
  • freeCodeCamp: MongoDB Crash Course (YouTube)

🛠️ Herramientas Útiles

Herramienta Descripción Uso
MongoDB Compass GUI oficial de MongoDB Explorar datos visualmente, probar queries
Studio 3T IDE avanzado para MongoDB Queries complejas, SQL to MongoDB, import/export
Robo 3T (Robomongo) Cliente ligero de MongoDB Desarrollo rápido, shell embebido
mongodump/mongorestore Herramientas CLI de backup Backups y restauración de datos
Postman Cliente de APIs REST Probar endpoints de tu API Django

💼 Proyectos de Ejemplo para Inspiración

  1. Sistema de Gestión de Contenidos (CMS)
    • Artículos con metadatos dinámicos
    • Categorización flexible
    • Búsqueda full-text
  2. E-commerce con Catálogo Flexible
    • Productos con atributos variables
    • Reseñas de usuarios
    • Recomendaciones basadas en historial
  3. Sistema de Logs y Analytics
    • Almacenar millones de eventos
    • Agregaciones para estadísticas
    • Dashboards en tiempo real
  4. Red Social o Foro
    • Posts con comentarios anidados
    • Likes, shares, mentions
    • Timeline personalizado

7. 📖 Glosario de Términos

Términos de MongoDB y NoSQL

NoSQL:

Base de datos "Not Only SQL" - diseñadas para datos no estructurados o semi-estructurados con esquemas flexibles.

Document:

Unidad básica de datos en MongoDB, equivalente a una fila en SQL. Se almacena en formato BSON (Binary JSON).

Collection:

Grupo de documentos en MongoDB, equivalente a una tabla en SQL. No requiere esquema predefinido.

BSON:

Binary JSON - formato binario que extiende JSON con tipos adicionales como Date, ObjectId, Binary data, etc.

ObjectId:

Identificador único de 12 bytes generado automáticamente por MongoDB para cada documento (_id).

Embedding:

Técnica de almacenar documentos relacionados dentro de un documento principal (documentos anidados).

Referencing:

Técnica de almacenar referencias a documentos en otras colecciones, similar a foreign keys en SQL.

Sharding:

Particionamiento horizontal de datos entre múltiples servidores para mejorar escalabilidad.

Replica Set:

Grupo de instancias MongoDB que mantienen el mismo conjunto de datos para alta disponibilidad.

Aggregation Pipeline:

Framework para procesamiento de datos que permite transformar y analizar documentos en múltiples etapas.

Index:

Estructura de datos que mejora la velocidad de las consultas, similar a índices en SQL.

Djongo:

Biblioteca Python que permite usar MongoDB como backend de Django manteniendo el ORM de Django.

📦 LO QUE DEBE CONTENER EL PROYECTO

📋 Checklist del Proyecto

Asegúrate de cumplir con TODOS estos requisitos antes de entregar:

✅ 1. Código Fuente Completo

Estructura de carpetas requerida:

Estructura del Proyecto Final
biblioteca_project/                         # ← Raíz del proyecto
├── manage.py
├── test_mongo.py                           # ✅ Script de prueba de conexión
├── init_mongodb.py                         # ✅ Script de inicialización de MongoDB
├── requirements.txt                        # ✅ Dependencias del proyecto        
├── .gitignore                              # ✅ Archivos a ignorar en Git
├── db.sqlite3                              # Base de datos MySQL local
│
├── biblioteca_project/                     # ← Carpeta de configuración
│   ├── __init__.py
│   ├── settings.py                         # ✅ Configuración MySQL + MongoDB Atlas
│   ├── urls.py                             # ✅ URLs principales del proyecto
│   ├── wsgi.py
│   └── asgi.py
│
├── biblioteca/                             # ← Aplicación Django
│   ├── __init__.py
│   ├── models.py                           # ✅ Modelos MySQL (Prestamo, Multa)
│   ├── views.py                            # ✅ 7 vistas híbridas (MySQL + MongoDB)
│   ├── urls.py                             # ✅ Rutas de la aplicación
│   ├── admin.py                            # ✅ Configuración del panel admin
│   ├── apps.py
│   ├── tests.py
│   │
│   ├── migrations/                         # ← Migraciones de MySQL
│   │   └── __init__.py
│   │
│   └── templates/                          # ← Carpeta de templates HTML
│       └── biblioteca/                     # ← Subcarpeta con nombre de app
│           ├── base.html                   # ✅ Template base (Bootstrap 5)
│           ├── inicio.html                 # ✅ Página de inicio
│           ├── dashboard.html              # ✅ Dashboard con gráficas
│           ├── listar_libros.html          # ✅ Catálogo completo (MongoDB)
│           ├── lista_libros.html           # ✅ Vista alternativa de libros
│           ├── detalle_libro.html          # ✅ Detalle de libro individual
│           └── mis_prestamos.html          # ✅ Vista híbrida (MySQL+MongoDB) ⭐
│
└── venv/                                   # ← Entorno virtual de Python

## 🎯 Archivos Principales del Sistema:

### 1. Configuración (biblioteca_project/)
  - settings.py → Conexiones MySQL + MongoDB Atlas
  - urls.py → Incluye path('', include('biblioteca.urls'))

### 2. Aplicación (biblioteca/)
  - models.py → Prestamo, Multa (MySQL con libro_id)
  - views.py → 7 vistas que usan ambas bases de datos
  - urls.py → Todas las rutas (dashboard, libros, prestamos)
  - admin.py → Panel de administración

### 3. Templates (biblioteca/templates/biblioteca/)
  - base.html → Plantilla base con Bootstrap 5 + navbar
  - dashboard.html → Estadísticas híbridas con Chart.js
  - mis_prestamos.html → Consulta híbrida usando libro_id

### 4. Scripts de Utilidad
  - init_mongodb.py → Crea 4 bases de datos en Atlas
  - test_mongo.py → Verifica conexión a MongoDB Atlas

## 🔗 Campo Común que Conecta Ambas BDs:
  libro_id → Enlaza registros entre MySQL y MongoDB Atlas
## 📄 Licencia Este proyecto es para fines educativos - UTH

✅ 4. Archivo requirements.txt

requirements.txt - Ejemplo
asgiref==3.7.2
Django==4.2.9
dnspython==2.4.2
pymongo==4.6.0
sqlparse==0.4.4
tzdata==2023.4
djangorestframework==3.14.0

# NOTA: Genera el tuyo con:
# pip freeze > requirements.txt

✅ 5. Archivo .gitignore

.gitignore
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Django
*.log
db.sqlite3
db.sqlite3-journal
/media
/staticfiles

# Credentials (IMPORTANTE)
mongodb_credentials.txt
.env
*.key
*.pem

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

✅ 6. Template de Credenciales (SIN contraseñas reales)

mongodb_credentials_TEMPLATE.txt
=================================================
MONGODB ATLAS - CREDENTIALS TEMPLATE
=================================================

# ⚠️ NUNCA subas este archivo con credenciales reales
# Copia este template a mongodb_credentials.txt y completa

CLUSTER NAME: BibliotecaCluster
DATABASE NAME: biblioteca_logs
USERNAME: biblioteca_admin
PASSWORD: [TU_CONTRASEÑA_AQUÍ]

CONNECTION STRING:
mongodb+srv://biblioteca_admin:[PASSWORD]@bibliotecacluster.xxxxx.mongodb.net/biblioteca_logs?retryWrites=true&w=majority

# Reemplaza:
# - [PASSWORD] con tu contraseña real
# - xxxxx con tu identificador de cluster

=================================================
INSTRUCCIONES:
=================================================
1. Copia este archivo a: mongodb_credentials.txt
2. Completa [PASSWORD] con tu contraseña
3. Actualiza settings.py con el connection string
4. NUNCA hagas commit de mongodb_credentials.txt

✅ 7. Documento de Reporte (Word/PDF)

📄 Contenido del Reporte Escrito

  1. Portada:
    • Nombre completo, matrícula, carrera
    • Título del proyecto
    • Materia: Servicios Web RESTful
    • Fecha de entrega
  2. Introducción:
    • Objetivo del proyecto
    • Tecnologías utilizadas
    • Alcance del sistema
  3. Marco Teórico:
    • ¿Qué es MongoDB?
    • Diferencias SQL vs NoSQL
    • ¿Qué es MongoDB Atlas?
    • Ventajas de usar PyMongo con Django
  4. Desarrollo:
    • Proceso de configuración de Atlas
    • Instalación de dependencias
    • Estructura del proyecto Django
    • Modelos creados
    • Views implementadas
    • Templates diseñados
  5. Pruebas y Resultados:
    • Capturas de pantalla con explicación
    • Código más relevante (snippets)
    • Problemas encontrados y soluciones
  6. Conclusiones:
    • Aprendizajes obtenidos
    • Ventajas del sistema híbrido
    • Posibles mejoras futuras
  7. Referencias:
    • MongoDB Docs, Django Docs, etc.

⚠️ ERRORES COMUNES QUE DEBES EVITAR:

🎉 ¡Felicidades si completaste todo!

Has desarrollado un sistema completo que:

Este proyecto demuestra tu capacidad para: