← Volver al Índice Principal

🗺️ API de Geolocalización

Implementación con Django, MySQL y Google Maps

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

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

🏆 Objetivo del Proyecto:

Crear un Sistema Completo de Gestión de Bibliotecas con Geolocalización Automática que permita:

  • Registrar bibliotecas ingresando solo la dirección postal
  • Geocodificar automáticamente: Convertir direcciones en coordenadas GPS (latitud/longitud) usando Google Maps API
  • Almacenar en MySQL: Guardar toda la información de bibliotecas con sus coordenadas
  • Mapa interactivo: Mostrar las bibliotecas en un mapa de Google Maps con marcadores clicables
  • API REST completa: Crear endpoints para listar, crear, editar y eliminar bibliotecas
  • Búsqueda de cercanas: Encontrar bibliotecas próximas a una ubicación específica
  • Panel de administración: Gestionar bibliotecas desde interfaz visual de Django
  • Enlaces directos a Google Maps: Botones para abrir cada biblioteca en la app de Google Maps

🎬 ¿Cómo Funciona el Sistema Completo?

Flujo de Uso Final:

  1. Administrador ingresa al panel: Accede a http://localhost:8000/admin/
  2. Crea una biblioteca: Ingresa nombre, dirección, ciudad, país, teléfono, horario
  3. Django geocodifica automáticamente: Llama a Google Maps API y obtiene:
    • Latitud y Longitud
    • Place ID único de Google
    • URL de Google Maps
  4. Datos se guardan en MySQL: Información completa almacenada
  5. Usuario final ve el mapa: Accede a http://localhost:8000/api/mapa/
  6. Mapa carga bibliotecas: Hace petición a la API REST
  7. Marcadores aparecen: Cada biblioteca tiene un pin en su ubicación exacta
  8. Usuario hace clic en marcador: Ventana emergente muestra información detallada
  9. Usuario abre en Google Maps: Botón abre navegación en app de Google Maps

⚙️ ¿Qué Hace Cada Tecnología?

Componente Función Específica en el Sistema ¿Por qué lo usamos?
Django Framework backend que gestiona lógica de negocio, rutas, modelos y conexión a BD Ahorra código: incluye admin, ORM, autenticación listos
MySQL Base de datos relacional que almacena bibliotecas y coordenadas Rápida, robusta, soporta geoespacial, compatible con Django
Google Maps Geocoding API Convierte "Av. Universidad 3000, CDMX" en latitud/longitud Precisión mundial, base de datos actualizada, 40,000 peticiones/mes gratis
Google Maps JavaScript API Renderiza mapa interactivo en el navegador con zoom, marcadores, popups Líder del mercado, familiar para usuarios, bien documentada
Django REST Framework Crea API REST con endpoints JSON para listar/crear bibliotecas Interfaz web automática, serialización, paginación incluidas
googlemaps (Python) Cliente Python que facilita llamadas a Google Maps API Simplifica código: una línea en lugar de configurar HTTP/JSON

🛠️ Tecnologías que usaremos (EXPLICADAS):

Tecnología Para qué se usa Versión
Python 3.8+ Lenguaje de programación del backend (servidor) 3.8+
Django 4.2 Framework web que maneja rutas, modelos, admin, ORM 4.2
MySQL 8.0 Base de datos relacional para almacenar bibliotecas y coordenadas 8.0+
mysqlclient Conector Python-MySQL (permite a Django comunicarse con MySQL) Latest
googlemaps 4.10 Cliente Python oficial de Google Maps API (geocodificación backend) 4.10.0
Google Maps Geocoding API Servicio cloud que convierte direcciones en coordenadas GPS v3
Google Maps JavaScript API Renderiza mapas interactivos en el navegador (frontend) Latest
Django REST Framework 3.14 Crea API REST con endpoints JSON automáticos 3.14+
HTML5/CSS3/JavaScript Frontend: mapa interactivo, estilos, interactividad ES6+

📊 Arquitectura del Sistema (Diagrama de Componentes)

┌─────────────────────┐                    ┌──────────────────────┐
│   NAVEGADOR         │                    │   GOOGLE MAPS API    │
│   (Frontend)        │                    │   (Cloud)            │
│                     │                    │                      │
│  - HTML/CSS         │                    │  - Geocoding API     │
│  - JavaScript       │                    │  - JavaScript API    │
│  - Google Maps SDK  │                    │  - Places API        │
└──────────┬──────────┘                    └──────────┬───────────┘
           │                                          │
           │ HTTP GET /api/mapa/                     │ HTTPS
           │ HTTP GET /api/bibliotecas/              │ Geocode Request
           │                                          │
           ▼                                          ▼
┌─────────────────────────────────────────────────────────────────┐
│   SERVIDOR DJANGO (Backend)                                     │
│                                                                  │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────────┐  │
│  │   views.py   │  │ api_views.py │  │   services.py       │  │
│  │              │  │              │  │                     │  │
│  │ mapa_        │  │ Biblioteca   │  │ GoogleMapsService   │  │
│  │ bibliotecas  │  │ ViewSet      │  │ .geocodificar()     │  │
│  └──────┬───────┘  └──────┬───────┘  └──────┬──────────────┘  │
│         │                  │                  │                  │
│         │                  │                  │                  │
│  ┌──────▼──────────────────▼──────────────────▼──────────────┐ │
│  │              models.py (Biblioteca)                        │ │
│  │  - nombre, direccion, ciudad, pais                        │ │
│  │  - latitud, longitud, place_id                            │ │
│  │  - google_maps_url, telefono, horario                     │ │
│  └──────┬─────────────────────────────────────────────────────┘ │
│         │ Django ORM                                             │
└─────────┼────────────────────────────────────────────────────────┘
          │ SQL Queries
          ▼
┌──────────────────────────┐
│   MYSQL DATABASE         │
│                          │
│  Tabla: libros_biblioteca│
│  - id (PK)               │
│  - nombre                │
│  - direccion             │
│  - latitud               │
│  - longitud              │
│  - place_id              │
│  - google_maps_url       │
│  - telefono              │
│  - horario               │
└──────────────────────────┘

FLUJO DE DATOS:
1. Usuario → Navegador → Django: GET /api/mapa/
2. Django → Navegador: Envía mapa.html
3. JavaScript → Django: GET /api/bibliotecas/ (petición AJAX)
4. Django → MySQL: SELECT * FROM libros_biblioteca
5. MySQL → Django: Datos de bibliotecas
6. Django → JavaScript: JSON con bibliotecas
7. JavaScript → Google Maps SDK: Renderizar mapa
8. Google Maps SDK → Navegador: Mapa interactivo visible

GEOCODIFICACIÓN (cuando se crea biblioteca):
1. Admin → Django: POST biblioteca (solo dirección)
2. Django → Google Geocoding API: "Av. Universidad 3000, CDMX"
3. Google → Django: {lat: 19.3326, lng: -99.1876, place_id: "ChIJ..."}
4. Django → MySQL: INSERT biblioteca con coordenadas
5. MySQL → Django: Confirmación
6. Django → Admin: Biblioteca creada ✅
                    

⏱️ Tiempo Estimado Total: 2-3 horas

Fase Tiempo Qué harás
PASO 1: API Key 15 min Crear cuenta Google Cloud, obtener API Key, habilitar facturación ($200 gratis)
PASO 2: Proyecto Django 20 min Crear proyecto, entorno virtual, instalar dependencias, configurar settings.py
PASO 3: MySQL 15 min Crear base de datos, configurar conexión, probar conectividad
PASO 4: Código Backend 40 min Crear models.py, services.py, admin.py, migraciones
PASO 5: Panel Admin 10 min Registrar modelos, personalizar admin, crear superusuario
PASO 6: API REST 25 min Serializers, ViewSets, URLs, probar endpoints
PASO 7: Mapa Frontend 30 min HTML, JavaScript, integración Google Maps SDK, marcadores interactivos
Pruebas y Verificación 15 min Crear bibliotecas, verificar coordenadas, probar mapa, Muestras de pantalla

💡 Antes de Empezar:

  • 📝 Lee toda la guía primero para entender el flujo completo
  • 🖥️ Abre 2 ventanas: una para la guía y otra para trabajar y 1 que sera donde te ves en el video
  • ⚠️ Si algo no funciona, NO te saltes pasos, vuelve a revisar
  • 🆘 Si tienes errores, lee las secciones de advertencias (⚠️)

PASOS 1-7: Práctica Guiada Completa

📋 ORDEN DE EJECUCIÓN - Sigue estos pasos EN ORDEN:

Esta sección contiene TODOS los pasos prácticos para crear el proyecto.

Los pasos están numerados y organizados. NO te saltes ninguno.

  1. PASO 1: Obtener API Key de Google Maps (encuentra este título abajo ⬇️)
  2. PASO 2: Crear Proyecto Django
  3. PASO 3: Configurar MySQL
  4. PASO 4: Escribir el Código (models, services, admin)
  5. PASO 5: Panel de Administración
  6. PASO 6: Crear API REST (continúa en la siguiente sección principal)
  7. PASO 7: Mapa Interactivo (continúa en sección Frontend)

👇 Empieza con el PASO 1 que está justo debajo de esta advertencia

2. 🌍 5 Bibliotecas/APIs de Geolocalización Más Populares

📚 En esta sección aprenderás sobre:

  • ✅ Las 5 plataformas de geolocalización líderes en el mercado
  • ✅ Comparación de características, precios y casos de uso
  • ✅ Ejemplos de código funcionales para cada una
  • ✅ Cuándo usar cada biblioteca según tu proyecto

1. 🗺️ Google Maps Platform (La Más Completa)

Descripción: Plataforma líder mundial con APIs para mapas, geocodificación, rutas, lugares y más.

✅ Ventajas:

  • Cobertura global: Datos precisos en todo el mundo
  • Ecosistema completo: Geocoding, Directions, Places, Street View
  • Documentación excelente: Miles de ejemplos y tutoriales
  • Familiaridad: Los usuarios conocen la interfaz de Google Maps
  • Gratis para empezar: $200 USD/mes de créditos + 40,000 geocodificaciones gratis

⚠️ Desventajas:

  • Costoso para alto volumen: Después de los créditos gratis, puede ser caro
  • Requiere tarjeta de crédito: Incluso para la versión gratuita
  • Lock-in: Difícil migrar a otra plataforma después
API Funcionalidad Casos de Uso
Maps JavaScript API Mapas interactivos en web Mostrar ubicaciones, marcadores
Geocoding API Convertir direcciones ↔ coordenadas Búsqueda de direcciones
Geolocation API Ubicación por WiFi/Cell Detectar ubicación del usuario
Places API Información de lugares Buscar restaurantes, hoteles
Directions API Calcular rutas Navegación, estimación de tiempo
Distance Matrix API Distancias entre múltiples puntos Logística, delivery
Ejemplo Python: Geocodificar con Google Maps
import googlemaps

# Inicializar cliente (este es el que usamos en la práctica)
gmaps = googlemaps.Client(key='TU_API_KEY')

# Geocodificar dirección → coordenadas
resultado = gmaps.geocode('Av. Universidad 3000, Ciudad de México')

if resultado:
    ubicacion = resultado[0]['geometry']['location']
    print(f"Latitud: {ubicacion['lat']}")  # 19.3326
    print(f"Longitud: {ubicacion['lng']}")  # -99.1876

# Geocodificación inversa: coordenadas → dirección
reverso = gmaps.reverse_geocode((19.4326, -99.1332))
direccion = reverso[0]['formatted_address']
print(direccion)  # "Ciudad de México, CDMX, México"

# Calcular ruta
ruta = gmaps.directions(
    origin='Zócalo, CDMX',
    destination='Chapultepec, CDMX',
    mode='driving'
)
distancia = ruta[0]['legs'][0]['distance']['text']
print(f"Distancia: {distancia}")  # "5.2 km"

💡 Cuándo usar Google Maps:

  • ✅ Proyectos empresariales con presupuesto
  • ✅ Apps que necesitan Street View
  • ✅ Cuando la precisión global es crítica
  • ✅ Proyectos donde los usuarios esperan la interfaz de Google

2. 🎨 Mapbox (La Más Personalizable)

Descripción: Plataforma de mapas con enfoque en diseño personalizado y rendimiento para apps móviles.

✅ Ventajas:

  • Diseño personalizado: Mapas con estilos únicos (oscuros, satélite, 3D)
  • Rendimiento excelente: Optimizado para móviles y web
  • Precio competitivo: Hasta 50,000 peticiones gratis/mes
  • Navegación turn-by-turn: Incluida en el plan gratuito
  • SDK nativo: Android, iOS, Unity, React Native

⚠️ Desventajas:

  • Menor cobertura POI: Menos puntos de interés que Google
  • Documentación compleja: Curva de aprendizaje más alta
  • Sin Street View: No tiene vista de calle
Ejemplo JavaScript: Mapa con Mapbox
// HTML: <div id="map" style="height: 500px;"></div>
// Cargar: <script src='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js'></script>

mapboxgl.accessToken = 'TU_MAPBOX_TOKEN';

// Crear mapa con estilo personalizado
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/dark-v11',  // Estilo oscuro
    center: [-99.1332, 19.4326],  // [lng, lat] CDMX
    zoom: 12
});

// Agregar marcador personalizado
const marker = new mapboxgl.Marker({
    color: '#FF0000',  // Color rojo
    draggable: true  // Marcador movible
})
    .setLngLat([-99.1332, 19.4326])
    .setPopup(new mapboxgl.Popup().setHTML('<h3>Ciudad de México</h3>'))
    .addTo(map);

// Escuchar cuando arrastran el marcador
marker.on('dragend', () => {
    const lngLat = marker.getLngLat();
    console.log(`Nueva posición: ${lngLat.lng}, ${lngLat.lat}`);
});
Ejemplo Python: Geocodificación con Mapbox
import requests

MAPBOX_TOKEN = 'TU_MAPBOX_TOKEN'
direccion = 'Av. Universidad 3000, Ciudad de México'

# Geocodificar dirección
url = f'https://api.mapbox.com/geocoding/v5/mapbox.places/{direccion}.json'
params = {'access_token': MAPBOX_TOKEN}

response = requests.get(url, params=params)
data = response.json()

if data['features']:
    coords = data['features'][0]['geometry']['coordinates']
    print(f"Longitud: {coords[0]}, Latitud: {coords[1]}")

💡 Cuándo usar Mapbox:

  • ✅ Apps móviles con diseño único
  • ✅ Startups con presupuesto limitado
  • ✅ Juegos y aplicaciones 3D
  • ✅ Cuando necesitas mapas oscuros/satélite personalizados

3. 🆓 Leaflet + OpenStreetMap (La Más Libre)

Descripción: Biblioteca JavaScript open-source para mapas interactivos, usa datos de OpenStreetMap (colaborativo).

✅ Ventajas:

  • 100% Gratis: Sin límites de peticiones, sin tarjeta de crédito
  • Open Source: Código abierto, sin vendor lock-in
  • Ligero: Solo 42 KB de JavaScript
  • Plugins: Miles de extensiones de la comunidad
  • Datos comunitarios: OpenStreetMap es editado por millones de usuarios

⚠️ Desventajas:

  • Sin geocodificación nativa: Necesitas servicios externos (Nominatim)
  • Menos POIs: Datos de lugares menos completos que Google
  • Calidad variable: Datos dependen de la comunidad (mejor en Europa/USA)
  • Sin soporte oficial: Depende de la comunidad
Ejemplo HTML + JavaScript: Mapa con Leaflet
<!-- Cargar Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<!-- Contenedor del mapa -->
<div id="map" style="height: 500px;"></div>

<script>
// Crear mapa centrado en CDMX
const map = L.map('map').setView([19.4326, -99.1332], 13);

// Agregar capa de OpenStreetMap
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

// Agregar marcador
L.marker([19.4326, -99.1332])
    .addTo(map)
    .bindPopup('<b>Ciudad de México</b><br>Zócalo')
    .openPopup();

// Agregar círculo (radio de 1 km)
L.circle([19.4326, -99.1332], {
    color: 'red',
    fillColor: '#f03',
    fillOpacity: 0.3,
    radius: 1000  // metros
}).addTo(map);
</script>
Ejemplo Python: Geocodificar con Nominatim (OpenStreetMap)
from geopy.geocoders import Nominatim
from geopy.distance import geodesic

# Inicializar geocodificador (GRATIS)
geolocator = Nominatim(user_agent="mi_app_biblioteca")

# Geocodificar dirección
ubicacion = geolocator.geocode("Av. Universidad 3000, Ciudad de México")
print(f"Latitud: {ubicacion.latitude}")
print(f"Longitud: {ubicacion.longitude}")

# Geocodificación inversa
ubicacion_inversa = geolocator.reverse("19.4326, -99.1332")
print(ubicacion_inversa.address)

# Calcular distancia entre dos puntos (Haversine)
punto1 = (19.4326, -99.1332)  # Zócalo
punto2 = (19.4260, -99.1679)  # Chapultepec
distancia = geodesic(punto1, punto2).kilometers
print(f"Distancia: {distancia:.2f} km")  # 3.85 km

💡 Cuándo usar Leaflet + OpenStreetMap:

  • ✅ Proyectos open-source sin presupuesto
  • ✅ Apps educativas o sin fines de lucro
  • ✅ Proyectos donde no quieres depender de Google
  • ✅ Prototipos y MVPs rápidos

4. 🚗 HERE Maps (La Mejor para Logística)

Descripción: Plataforma de mapas de nivel empresarial, especializada en navegación y logística (usado por BMW, Mercedes).

✅ Ventajas:

  • Tráfico en tiempo real: Datos de tráfico muy precisos
  • Rutas optimizadas: Excelente para flotas y delivery
  • Mapas offline: Descarga mapas completos
  • Gratis hasta 250,000 peticiones/mes: Muy generoso
  • Cobertura automotriz: Datos de velocidad, restricciones de camiones

⚠️ Desventajas:

  • Menos conocido: Usuarios no están familiarizados
  • Documentación compleja: Enfocada a empresas
  • UI menos atractiva: Diseño más funcional que visual
Ejemplo JavaScript: Mapa con HERE Maps
// Cargar HERE SDK
// <script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>

const platform = new H.service.Platform({
    'apikey': 'TU_HERE_API_KEY'
});

// Obtener servicios de mapa
const defaultLayers = platform.createDefaultLayers();

// Crear mapa
const map = new H.Map(
    document.getElementById('map'),
    defaultLayers.vector.normal.map,
    {
        center: {lat: 19.4326, lng: -99.1332},
        zoom: 13
    }
);

// Calcular ruta con tráfico
const router = platform.getRoutingService(null, 8);
const routeParams = {
    'routingMode': 'fast',  // Ruta más rápida
    'transportMode': 'car',
    'origin': '19.4326,-99.1332',
    'destination': '19.4260,-99.1679',
    'return': 'polyline,summary'
};

router.calculateRoute(routeParams, (result) => {
    const route = result.routes[0];
    console.log(`Distancia: ${route.sections[0].summary.length} metros`);
    console.log(`Duración: ${route.sections[0].summary.duration} segundos`);
}, console.error);

💡 Cuándo usar HERE Maps:

  • ✅ Apps de delivery y logística
  • ✅ Rastreo de flotas de vehículos
  • ✅ Apps de navegación automotriz
  • ✅ Cuando necesitas datos de tráfico precisos

5. 🐍 Geopy (La Mejor para Python)

Descripción: Biblioteca Python que unifica múltiples servicios de geocodificación (Google, Nominatim, Bing, etc.).

✅ Ventajas:

  • Multi-proveedor: Un código para todos los servicios (Google, OSM, Bing, MapBox)
  • API consistente: Mismo código, diferentes backends
  • Funciones geoespaciales: Calcular distancias, rutas, áreas
  • Gratis con Nominatim: Sin límites usando OpenStreetMap
  • Fácil de usar: 3 líneas de código para geocodificar

⚠️ Desventajas:

  • Solo backend: No incluye mapas visuales (necesitas Leaflet/Folium)
  • Rate limiting: Nominatim limita 1 petición/segundo
  • No tiene todas las APIs: Falta Places, Directions avanzadas
Ejemplo Python: Comparar Múltiples Proveedores
from geopy.geocoders import Nominatim, GoogleV3, Bing
from geopy.distance import geodesic, great_circle
from geopy.exc import GeocoderTimedOut

direccion = "Av. Universidad 3000, Ciudad de México"

# 1. Geocodificar con OpenStreetMap (GRATIS)
geolocator_osm = Nominatim(user_agent="mi_app")
ubicacion_osm = geolocator_osm.geocode(direccion)
print(f"OSM: {ubicacion_osm.latitude}, {ubicacion_osm.longitude}")

# 2. Geocodificar con Google Maps (PAGO)
geolocator_google = GoogleV3(api_key="TU_GOOGLE_API_KEY")
ubicacion_google = geolocator_google.geocode(direccion)
print(f"Google: {ubicacion_google.latitude}, {ubicacion_google.longitude}")

# 3. Geocodificar con Bing Maps (PAGO)
geolocator_bing = Bing(api_key="TU_BING_API_KEY")
ubicacion_bing = geolocator_bing.geocode(direccion)
print(f"Bing: {ubicacion_bing.latitude}, {ubicacion_bing.longitude}")

# Calcular distancia (2 fórmulas diferentes)
punto1 = (19.4326, -99.1332)
punto2 = (19.4260, -99.1679)

distancia_geodesic = geodesic(punto1, punto2).kilometers  # Más preciso (elipsoide)
distancia_circle = great_circle(punto1, punto2).kilometers  # Más rápido (esfera)

print(f"Distancia (geodesic): {distancia_geodesic:.2f} km")
print(f"Distancia (círculo): {distancia_circle:.2f} km")
Ejemplo: Geocodificar en Batch (Múltiples Direcciones)
from geopy.geocoders import Nominatim
import time

geolocator = Nominatim(user_agent="mi_app")

direcciones = [
    "Biblioteca Vasconcelos, CDMX",
    "Biblioteca Central UNAM",
    "Biblioteca de México José Vasconcelos"
]

resultados = []

for direccion in direcciones:
    try:
        ubicacion = geolocator.geocode(direccion)
        if ubicacion:
            resultados.append({
                'direccion': direccion,
                'lat': ubicacion.latitude,
                'lng': ubicacion.longitude
            })
            print(f"✅ {direccion}: {ubicacion.latitude}, {ubicacion.longitude}")
        else:
            print(f"❌ No encontrado: {direccion}")
    
    except GeocoderTimedOut:
        print(f"⏱️ Timeout: {direccion}")
    
    time.sleep(1)  # ⚠️ Respetar límite de Nominatim (1 req/sec)

# Guardar en DataFrame (Pandas)
import pandas as pd
df = pd.DataFrame(resultados)
df.to_csv('bibliotecas_geocodificadas.csv', index=False)
print("✅ Guardado en CSV")

💡 Cuándo usar Geopy:

  • ✅ Scripts de análisis de datos (Jupyter, Pandas)
  • ✅ Geocodificación en batch de muchas direcciones
  • ✅ Backends Python que no necesitan mapas visuales
  • ✅ Proyectos donde quieres flexibilidad de proveedor

📊 Comparación Final: ¿Cuál elegir?

Plataforma Precio Límite Gratis Mejor Para Peor Para
Google Maps $$ (caro) $200/mes + 40K geocodes Apps empresariales, precisión global Startups sin presupuesto
Mapbox $ (moderado) 50K peticiones/mes Apps móviles, diseño custom Necesitas Street View
Leaflet + OSM GRATIS Ilimitado Proyectos open-source, prototipos Apps que necesitan POIs detallados
HERE Maps $$ (moderado) 250K peticiones/mes Logística, flotas, tráfico Apps B2C generales
Geopy Depende del proveedor Ilimitado (Nominatim) Análisis de datos Python Necesitas mapas visuales

🏆 Recomendación General:

  • Prototipo/MVP: Leaflet + Nominatim (gratis total)
  • Startup con presupuesto: Mapbox (buen balance precio/calidad)
  • Empresa grande: Google Maps (mejor cobertura y soporte)
  • Logística: HERE Maps (mejor tráfico y rutas)
  • Análisis backend: Geopy (flexible y potente)

📊 Ejemplo de Flujo de Información:

Usuario → Aplicación Web → Django Backend → Google Maps API → Respuesta JSON → Django → Frontend
  1. Usuario ingresa dirección en la aplicación
  2. Frontend envía petición a Django
  3. Django llama a Google Maps Geocoding API
  4. Google devuelve coordenadas en formato JSON
  5. Django guarda en MySQL y responde al frontend
  6. Frontend muestra el mapa con la ubicación

2.2 Datos que Intercambian las APIs (Explicación Detallada)

Las APIs de geolocalización intercambian información en formato JSON (JavaScript Object Notation), un formato de texto legible tanto para humanos como para máquinas.

Ejemplo COMPLETO de respuesta JSON de Geocoding API
{
  "results": [
    {
      // Dirección formateada por Google (estandarizada)
      "formatted_address": "Biblioteca Vasconcelos, Eje 1 Norte, Buenavista, 06350 Ciudad de México, CDMX, México",
      
      // Información geométrica (coordenadas y tipo de ubicación)
      "geometry": {
        "location": {
          "lat": 19.4395418,  // Latitud (Norte-Sur: -90 a +90)
          "lng": -99.1520794  // Longitud (Este-Oeste: -180 a +180)
        },
        // Tipo de precisión de la ubicación
        "location_type": "ROOFTOP",  // ROOFTOP (preciso) | RANGE_INTERPOLATED | GEOMETRIC_CENTER | APPROXIMATE
        
        // Área rectangular que contiene toda la ubicación
        "viewport": {
          "northeast": { "lat": 19.4408907, "lng": -99.1507304 },
          "southwest": { "lat": 19.4381927, "lng": -99.1534284 }
        },
        
        // Área precisa del lugar (opcional)
        "bounds": {
          "northeast": { "lat": 19.4408907, "lng": -99.1507304 },
          "southwest": { "lat": 19.4381927, "lng": -99.1534284 }
        }
      },
      
      // ID único de Google Maps para este lugar
      "place_id": "ChIJXxQBnbP_0YURW9JOEKiHlBM",  // Identificador permanente
      
      // Componentes individuales de la dirección
      "address_components": [
        {
          "long_name": "Biblioteca Vasconcelos",
          "short_name": "Biblioteca Vasconcelos",
          "types": ["establishment", "library", "point_of_interest"]
        },
        {
          "long_name": "Eje 1 Norte",
          "short_name": "Eje 1 Nte",
          "types": ["route"]
        },
        {
          "long_name": "Buenavista",
          "short_name": "Buenavista",
          "types": ["neighborhood", "political"]
        },
        {
          "long_name": "Ciudad de México",
          "short_name": "CDMX",
          "types": ["locality", "political"]
        },
        {
          "long_name": "México",
          "short_name": "MX",
          "types": ["country", "political"]
        },
        {
          "long_name": "06350",
          "short_name": "06350",
          "types": ["postal_code"]
        }
      ],
      
      // Tipos de este lugar (puede ser múltiples)
      "types": ["library", "point_of_interest", "establishment"]
    }
  ],
  
  // Estado de la respuesta
  "status": "OK"  // OK | ZERO_RESULTS | OVER_QUERY_LIMIT | REQUEST_DENIED | INVALID_REQUEST
}

📋 Códigos de Estado (status) Posibles:

Status Significado Qué hacer
OK Geocodificación exitosa ✅ Procesar resultados normalmente
ZERO_RESULTS No se encontró la dirección ⚠️ Verificar que la dirección sea válida y específica
OVER_QUERY_LIMIT Superaste el límite de peticiones 🛑 Esperar o habilitar facturación en Google Cloud
REQUEST_DENIED API Key inválida o API no habilitada 🔑 Verificar API Key y habilitar Geocoding API
INVALID_REQUEST Falta el parámetro "address" 🐛 Revisar código: asegurar que se envíe la dirección
UNKNOWN_ERROR Error del servidor (temporal) 🔄 Reintentar la petición después de unos segundos

🔍 Tipos de Precisión de Ubicación (location_type):

  • ROOFTOP: Precisión más alta (±10 metros)
    • Ubicación exacta del edificio
    • Ideal para direcciones con número de calle
    • Ejemplo: "Av. Universidad 3000, CDMX"
  • RANGE_INTERPOLATED: Interpolación (±50 metros)
    • Calculado entre dos puntos conocidos
    • Usado cuando el número exacto no está en Google Maps
    • Ejemplo: "Calle Falsa 123" (si no existe #123, calcula entre #120 y #130)
  • GEOMETRIC_CENTER: Centro geométrico (±100 metros)
    • Centro de una calle o zona
    • Usado cuando solo se proporciona nombre de calle
    • Ejemplo: "Av. Reforma, CDMX"
  • APPROXIMATE: Aproximado (±1 km o más)
    • Ubicación general de ciudad/región
    • Usado para búsquedas muy genéricas
    • Ejemplo: "Ciudad de México"

💻 ¿Cómo Usamos esta Información en Django?

Extracción de Datos en services.py
def geocodificar_direccion(direccion):
    # Llamar a Google Maps API
    resultado = gmaps.geocode(direccion)
    
    # Verificar que haya resultados
    if resultado and len(resultado) > 0:
        # Tomar el primer resultado (más relevante)
        primer_resultado = resultado[0]
        
        # Extraer coordenadas
        location = primer_resultado['geometry']['location']
        latitud = location['lat']    # 19.4395418
        longitud = location['lng']   # -99.1520794
        
        # Extraer Place ID (identificador único)
        place_id = primer_resultado['place_id']  # "ChIJ..."
        
        # Extraer dirección formateada
        direccion_formateada = primer_resultado['formatted_address']
        
        # Extraer tipo de precisión
        location_type = primer_resultado['geometry']['location_type']
        
        # Retornar diccionario con datos útiles
        return {
            'lat': Decimal(str(latitud)),
            'lng': Decimal(str(longitud)),
            'place_id': place_id,
            'formatted_address': direccion_formateada,
            'location_type': location_type
        }
    
    return None  # No se encontró

3. 🗺️ Google Maps API - Configuración

🚨 ¡ADVERTENCIA CRÍTICA!

SI NO COMPLETAS ESTE PASO 1 CORRECTAMENTE
❌ LAS COORDENADAS NO APARECERÁN ❌

📋 CHECKLIST ANTES DE CONTINUAR:

Obtener API Key (siguiendo los pasos de abajo)
Habilitar Geocoding API (en Google Cloud Console)
Configurar Facturación ($200 crédito gratis)
Guardar API Key en settings.py (al final del archivo)

❌ Error que verás si NO haces esto:

Error en Django Shell / Admin
ValueError: Must provide API key or enterprise credentials when creating client.

O también:

Error geocodificando: Must provide API key or enterprise credentials when creating client.

Y los campos quedarán vacíos:

  • Latitud: -
  • Longitud: -
  • Place id: (vacío)
  • Google maps url: (vacío)

✅ Sigue TODOS los pasos de abajo SIN SALTARTE NINGUNO

Paso 1: Obtener API Key (Este Paso 1, que contine 4 subpuntos pueden saltarlo, solo comenten como obtuvieron la API y porque fue Asi. En la Grabacion de su Video.

1 Crear proyecto en Google Cloud Console

Ve a console.cloud.google.com y crea un nuevo proyecto

2 Habilitar APIs necesarias
  • Maps JavaScript API
  • Geocoding API
  • Places API
  • Directions API
3 Crear credenciales

En "Credenciales", clic en "Crear credenciales" → "Clave de API"

⚠️ Seguridad de la API Key:

  • Nunca subas tu API Key a GitHub
  • Restringe la key por dominio o IP
  • Limita las APIs que puede usar
  • Usa variables de entorno
4 Configurar Facturación (IMPORTANTE)

⚠️ Google Maps API requiere facturación activa, aunque ofrece $200 USD gratis al mes.

  1. En el menú lateral, haz clic en "Facturación"
  2. Haz clic en "Vincular una cuenta de facturación"
  3. Sigue los pasos para agregar una tarjeta de crédito/débito
  4. Confirma la cuenta de facturación

📝 Nota: No te cobrarán si no superas los $200/mes. Para estudiantes y proyectos pequeños, esto es más que suficiente.

🔍 Verificar que todo esté configurado:

  1. Ve a "APIs y servicios""Panel"
  2. Deberías ver las APIs habilitadas: Geocoding API, Maps JavaScript API
  3. Ve a "Facturación""Resumen"
  4. Deberías ver: "$200.00 de crédito gratis restante"

4. 💻 Saber Hacer - Implementación con Django

Paso 1: Instalar Dependencias

PowerShell - Instalación de bibliotecas
# Activar entorno virtual
.\venv\Scripts\Activate

# Instalar googlemaps para Python
pip install googlemaps==4.10.0  # Cliente oficial de Google Maps

# Instalar geopy para geocodificación
pip install geopy==2.4.0  # Biblioteca de geocodificación

# Actualizar requirements.txt
pip freeze > requirements.txt  # Guarda todas las dependencias

Paso 2: Configurar Django

⚠️ IMPORTANTE - Este es el paso CRÍTICO

Aquí es donde configuras la API Key en Django.
Si NO haces este paso correctamente, tendrás el error:

Error que aparecerá
ValueError: Must provide API key or enterprise credentials when creating client.
2.1 Abre el archivo settings.py

Ubicación: BibliotecasApp/biblioteca_project/settings.py

PowerShell - Abrir en VS Code
# Si estás en la carpeta BibliotecasApp
code biblioteca_project/settings.py
2.2 Desplázate AL FINAL del archivo settings.py

⚠️ AL FINAL significa después de todas las configuraciones existentes.
Busca las últimas líneas del archivo y agrega DESPUÉS de ellas.

2.3 Agrega la configuración de la API Key
biblioteca_project/settings.py (AL FINAL)
# ================================
# GOOGLE MAPS API CONFIGURATION
# ================================
GOOGLE_MAPS_API_KEY = 'TU_API_KEY_AQUI'  # ⭐ Reemplaza con tu API Key real

❌ NO DEJES: 'TU_API_KEY_AQUI'
✅ REEMPLÁZALA con tu API Key real, ejemplo:

Ejemplo CORRECTO, Utiliza esta API: AIzaSyCIU4Zto7WpyZhQrKCchMJRcM81_g7FP6U
GOOGLE_MAPS_API_KEY = 'AIzaSyBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
2.4 Verifica que esté BIEN escrita

Checklist de verificación:

Las comillas están bien escritas (simples ' o dobles ")
No hay espacios antes o después de las comillas
Tu API Key empieza con AIza
No tiene saltos de línea en medio
Es tu API Key REAL (no el ejemplo TU_API_KEY_AQUI)
2.5 GUARDA el archivo settings.py

Presiona CTRL + S (Windows) o CMD + S (Mac)

✅ VERIFICACIÓN INMEDIATA - Prueba que funciona

Antes de continuar, verifica que la API Key esté configurada correctamente:

PowerShell - Abre Django Shell
python manage.py shell
Django Shell - Ejecuta esto
from django.conf import settings
print("API Key configurada:", hasattr(settings, 'GOOGLE_MAPS_API_KEY'))
print("Primeros 10 caracteres:", settings.GOOGLE_MAPS_API_KEY[:10])

# Para salir:
exit()

✅ Si ves:

Resultado esperado
API Key configurada: True
Primeros 10 caracteres: AIzaSyBxxx

¡PERFECTO! Puedes continuar al Paso 3.

❌ Si ves un error o False:

  • Verifica que guardaste el archivo settings.py (CTRL+S)
  • Verifica que agregaste la línea AL FINAL del archivo
  • Verifica que no hay errores de sintaxis (comillas, espacios)
  • Cierra y vuelve a abrir Django shell
OPCIONAL - Para Producción (Avanzado)
# Si quieres mayor seguridad, usa variables de entorno:
import os
GOOGLE_MAPS_API_KEY = os.environ.get('GOOGLE_MAPS_API_KEY', '')  # Lee de variable de entorno

# O usando python-decouple:
from decouple import config
GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY')  # Lee de archivo .env

Paso 3: Crear Modelos

libros/models.py - Modelo con Geolocalización
from django.db import models
from decimal import Decimal

class Biblioteca(models.Model):
    """Modelo para bibliotecas con ubicación geográfica"""
    
    nombre = models.CharField(max_length=200)  # Nombre de la biblioteca
    direccion = models.CharField(max_length=300)  # Dirección postal
    ciudad = models.CharField(max_length=100)  # Ciudad
    pais = models.CharField(max_length=100, default='México')  # País
    
    # Coordenadas geográficas (se llenan automáticamente)
    latitud = models.DecimalField(
        max_digits=10, 
        decimal_places=7, 
        null=True, 
        blank=True
    )  # Latitud con 7 decimales de precisión
    
    longitud = models.DecimalField(
        max_digits=10, 
        decimal_places=7, 
        null=True, 
        blank=True
    )  # Longitud con 7 decimales de precisión
    
    # Datos adicionales de Google Maps
    place_id = models.CharField(max_length=200, blank=True)  # ID único de Google
    google_maps_url = models.URLField(blank=True)  # URL de Google Maps
    
    telefono = models.CharField(max_length=20, blank=True)  # Teléfono
    horario = models.TextField(blank=True)  # Horario de atención
    
    class Meta:
        verbose_name = 'Biblioteca'
        verbose_name_plural = 'Bibliotecas'
        ordering = ['nombre']
    
    def __str__(self):
        return self.nombre
    
    @property
    def direccion_completa(self):
        """Retorna dirección formateada"""
        return f"{self.direccion}, {self.ciudad}, {self.pais}"  # Dirección completa
    
    def generar_google_maps_url(self):
        """
        Genera URL de Google Maps basada en las coordenadas
        
        Formatos disponibles:
        1. Con Place ID (más preciso): https://www.google.com/maps/place/?q=place_id:ChIJ...
        2. Con coordenadas: https://www.google.com/maps?q=lat,lng
        3. Con búsqueda: https://www.google.com/maps/search/?api=1&query=nombre+direccion
        """
        if self.place_id:
            # Opción 1: Usar Place ID (más preciso)
            return f"https://www.google.com/maps/place/?q=place_id:{self.place_id}"
        elif self.latitud and self.longitud:
            # Opción 2: Usar coordenadas
            return f"https://www.google.com/maps?q={self.latitud},{self.longitud}"
        else:
            # Opción 3: Búsqueda por texto
            import urllib.parse
            query = urllib.parse.quote(self.direccion_completa)
            return f"https://www.google.com/maps/search/?api=1&query={query}"
    
    def geocodificar(self):
        """Obtiene coordenadas de la dirección"""
        from .services import GoogleMapsService
        
        resultado = GoogleMapsService.geocodificar_direccion(self.direccion_completa)
        
        if resultado:
            self.latitud = resultado['lat']  # Guarda latitud
            self.longitud = resultado['lng']  # Guarda longitud
            self.place_id = resultado.get('place_id', '')  # Guarda Place ID
            self.google_maps_url = self.generar_google_maps_url()  # ⭐ Genera URL automáticamente
            self.save()  # Guarda en la base de datos
            return True
        return False

Paso 4: Crear Servicio de Google Maps

libros/services.py - Servicio de Geolocalización
import googlemaps
from django.conf import settings
from decimal import Decimal
import logging

logger = logging.getLogger(__name__)  # Logger para errores

class GoogleMapsService:
    """Servicio para interactuar con Google Maps API"""
    
    def __init__(self):
        self.client = googlemaps.Client(key=settings.GOOGLE_MAPS_API_KEY)  # Cliente de Google Maps
    
    @classmethod
    def geocodificar_direccion(cls, direccion):
        """
        Convierte dirección en coordenadas
        
        Args:
            direccion (str): Dirección completa
            
        Returns:
            dict: {'lat': float, 'lng': float, 'place_id': str}
        """
        try:
            service = cls()  # Instancia del servicio
            resultado = service.client.geocode(direccion)  # Llama a Geocoding API
            
            if resultado:  # Si encuentra resultados
                location = resultado[0]['geometry']['location']  # Obtiene ubicación
                place_id = resultado[0]['place_id']  # ID del lugar
                
                return {
                    'lat': Decimal(str(location['lat'])),  # Convierte a Decimal
                    'lng': Decimal(str(location['lng'])),  # Convierte a Decimal
                    'place_id': place_id,
                    'formatted_address': resultado[0]['formatted_address']  # Dirección formateada
                }
            
            return None  # No se encontró
            
        except Exception as e:
            logger.error(f"Error geocodificando: {e}")  # Registra error
            return None
    
    @classmethod
    def calcular_distancia(cls, origen, destino):
        """
        Calcula distancia entre dos puntos
        
        Args:
            origen (tuple): (lat, lng) del origen
            destino (tuple): (lat, lng) del destino
            
        Returns:
            dict: Información de distancia y duración
        """
        try:
            service = cls()
            resultado = service.client.distance_matrix(
                origins=[origen],  # Punto de inicio
                destinations=[destino],  # Punto de destino
                mode='driving',  # Modo: driving, walking, bicycling, transit
                language='es'  # Idioma español
            )
            
            if resultado['rows']:
                element = resultado['rows'][0]['elements'][0]
                
                if element['status'] == 'OK':
                    return {
                        'distancia_texto': element['distance']['text'],  # "15.2 km"
                        'distancia_metros': element['distance']['value'],  # 15200
                        'duracion_texto': element['duration']['text'],  # "25 mins"
                        'duracion_segundos': element['duration']['value']  # 1500
                    }
            
            return None
            
        except Exception as e:
            logger.error(f"Error calculando distancia: {e}")
            return None

5. 🚀 Proyecto Práctico: Sistema de Bibliotecas

Objetivo del Proyecto

Crear un sistema que permita registrar bibliotecas con su ubicación geográfica, mostrarlas en un mapa interactivo y calcular distancias entre ellas.

🎓 Práctica Guiada Paso a Paso (Para Principiantes)

📝 Antes de Empezar

¿Qué necesitas tener instalado en tu computadora?

  • Python 3.8 o superior - Descarga de python.org
  • MySQL Server - Descarga de mysql.com
  • Visual Studio Code o cualquier editor de código
  • Un navegador web moderno (Chrome, Firefox, Edge)
  • Cuenta de Google para obtener API Key

✅ ¡EMPIEZA AQUÍ LA PRÁCTICA!

Si completaste la sección de Requisitos Previos, ahora sí puedes empezar.

Sigue estos 7 PASOS EN ORDEN:

  1. PASO 1: Obtener API Key de Google Maps (⬇️ EMPIEZA ABAJO)
  2. PASO 2: Crear Proyecto Django
  3. PASO 3: Configurar MySQL
  4. PASO 4: Escribir el Código
  5. PASO 5: Panel de Administración
  6. PASO 6: Crear API REST
  7. PASO 7: Mapa Interactivo

⚠️ NO te saltes pasos - cada uno es necesario para el siguiente.

📋 PASO 1: Obtener tu API Key de Google Maps (15 minutos)

🎯 Objetivo de este paso:

Obtener una API Key (clave de API) de Google Maps que nos permitirá:

  • ✅ Convertir direcciones en coordenadas GPS (geocodificación)
  • ✅ Mostrar mapas interactivos en nuestra aplicación
  • ✅ Usar los servicios de Google Maps gratis (hasta $200 USD/mes)

⏱️ Tiempo estimado: 15 minutos

📸 Toma captura: De tu API Key cuando la obtengas (la necesitarás después)

1.1 Crear un proyecto en Google Cloud Console
  1. Abre tu navegador y ve a: https://console.cloud.google.com
  2. Inicia sesión con tu cuenta de Gmail
  3. En la parte superior, haz clic en "Seleccionar proyecto"
  4. Haz clic en "Proyecto nuevo"
  5. Escribe un nombre para tu proyecto: Bibliotecas-Geolocalización
  6. Haz clic en "Crear" y espera unos segundos
1.2 Habilitar las APIs necesarias
  1. En el menú lateral izquierdo, haz clic en "APIs y servicios""Biblioteca"
  2. Busca "Maps JavaScript API" y haz clic en ella
  3. Haz clic en el botón azul "HABILITAR"
  4. Regresa a la Biblioteca y busca "Geocoding API"
  5. Haz clic en "HABILITAR"
  6. Repite el proceso para "Places API" y "Directions API"
1.3 Crear tu API Key (Clave de API)
  1. En el menú lateral, haz clic en "APIs y servicios""Credenciales"
  2. Haz clic en "+ CREAR CREDENCIALES" en la parte superior
  3. Selecciona "Clave de API"
  4. Se generará una clave (algo como: AIzaSyD1234...)
  5. ¡MUY IMPORTANTE! Copia esta clave y guárdala en un lugar seguro (Notepad)
  6. Haz clic en "RESTRINGIR CLAVE" (para seguridad)
  7. En "Restricciones de API", selecciona las APIs que habilitaste
  8. Haz clic en "Guardar"

⚠️ ¡Atención!

¿Por qué necesitas una API Key?

La API Key es como una "contraseña" que le dice a Google: "Soy yo, permíteme usar tus mapas". Sin ella, no podrás usar Google Maps en tu proyecto.

⚠️ Nunca compartas tu API Key públicamente (no la subas a GitHub, no la pegues en redes sociales)

📋 PASO 2: Configurar tu Proyecto Django (20 minutos)

2.1 Crear carpeta del proyecto
  1. Abre la terminal de Windows (Windows PowerShell) presionando Windows + R, escribe powershell y presiona Enter
  2. Navega a donde quieres guardar el proyecto. Ejemplo:
PowerShell
# Ir a tu carpeta de Documentos
cd ~\Documents

# Crear carpeta para el proyecto
mkdir BibliotecasApp

# Entrar a la carpeta
cd BibliotecasApp
2.2 Crear entorno virtual (Virtual Environment)

¿Qué es un entorno virtual? Es como una "caja" donde instalamos las bibliotecas de Python solo para este proyecto, sin afectar otras aplicaciones.

PowerShell
# Crear entorno virtual llamado "venv"
python -m venv venv

# Activar el entorno virtual
.\venv\Scripts\Activate

# Si sale un error de permisos en PowerShell, ejecuta:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Luego intenta activar nuevamente
.\venv\Scripts\Activate

# Deberías ver (venv) al inicio de la línea, significa que está activo
2.3 Instalar Django y bibliotecas necesarias
PowerShell (con venv activado)
# Actualizar pip primero (IMPORTANTE)
python -m pip install --upgrade pip

# Instalar Django
pip install django==4.2

# Instalar Django REST Framework (para crear APIs)
pip install djangorestframework

# Instalar cliente de Google Maps
pip install googlemaps==4.10.0

# Instalar geopy (para geocodificación)
pip install geopy==2.4.0

# Verificar que se instaló todo correctamente
pip list

⚠️ Instalación de mysqlclient en Windows

La instalación de mysqlclient puede fallar en Windows. Sigue estos pasos:

Opción 1 - Usando archivo wheel (RECOMENDADO):

  1. Descarga el archivo wheel desde: https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient
  2. Selecciona la versión según tu Python:
    • Python 3.11 64-bit: mysqlclient-2.2.0-cp311-cp311-win_amd64.whl
    • Python 3.10 64-bit: mysqlclient-2.2.0-cp310-cp310-win_amd64.whl
    • Python 3.9 64-bit: mysqlclient-2.2.0-cp39-cp39-win_amd64.whl
  3. Guarda el archivo en la carpeta BibliotecasApp
  4. Instálalo con: pip install nombredelarchivo.whl

Opción 2 - Instalar Visual Studio Build Tools:

  1. Descarga: Visual C++ Build Tools
  2. Instala con la opción "Desarrollo de escritorio con C++"
  3. Reinicia la terminal
  4. Ejecuta: pip install mysqlclient

Opción 3 - Usar PyMySQL (alternativa más fácil):

PowerShell
# En lugar de mysqlclient, instala pymysql
pip install pymysql

# Luego, en settings.py agrega al inicio:
# import pymysql
# pymysql.install_as_MySQLdb()

¿Qué hace cada biblioteca?

  • django - El framework web para crear nuestro backend
  • mysqlclient/pymysql - Permite que Django se conecte a MySQL
  • djangorestframework - Herramienta para crear APIs REST fácilmente
  • googlemaps - Cliente oficial de Google Maps para Python
  • geopy - Biblioteca para trabajar con coordenadas geográficas
2.4 Crear proyecto Django
PowerShell
# Crear proyecto Django llamado "biblioteca_project"
django-admin startproject biblioteca_project .

# El punto (.) al final significa "crear en la carpeta actual"

# Crear aplicación llamada "libros"
python manage.py startapp libros

# Verificar estructura creada
dir

📁 Estructura de carpetas que deberías ver:

Estructura del Proyecto
BibliotecasApp/
│
├── venv/                      # Entorno virtual
├── biblioteca_project/        # Configuración del proyecto
│   ├── __init__.py
│   ├── settings.py           # ⭐ Configuraciones principales
│   ├── urls.py               # ⭐ Rutas/URLs del proyecto
│   └── wsgi.py
├── libros/                    # Nuestra aplicación
│   ├── migrations/
│   ├── __init__.py
│   ├── admin.py              # ⭐ Panel de administración
│   ├── models.py             # ⭐ Modelos de base de datos
│   ├── views.py              # ⭐ Vistas/lógica
│   └── tests.py
└── manage.py                  # ⭐ Comando principal de Django

📋 PASO 3: Configurar Base de Datos MySQL (15 minutos)

3.1 Crear base de datos en MySQL
  1. Abre MySQL Workbench o phpMyAdmin
  2. Si usas línea de comandos, abre una nueva terminal y ejecuta:
MySQL Terminal
-- Conectarse a MySQL (te pedirá tu contraseña)
mysql -u root -p

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

-- Crear usuario para la aplicación (recomendado por seguridad)
CREATE USER 'biblioteca_user'@'localhost' IDENTIFIED BY 'password123';

-- Dar permisos al usuario sobre la base de datos
GRANT ALL PRIVILEGES ON biblioteca_db.* TO 'biblioteca_user'@'localhost';

-- Aplicar cambios
FLUSH PRIVILEGES;

-- Salir de MySQL
EXIT;
3.2 Configurar Django para usar MySQL

Abre el archivo biblioteca_project/settings.py en tu editor de código:

📝 Si usaste PyMySQL (Opción 3)

Agrega estas líneas al inicio del archivo settings.py, después de los imports:

biblioteca_project/settings.py (inicio del archivo)
import pymysql
pymysql.install_as_MySQLdb()  # Hace que Django use PyMySQL como si fuera mysqlclient

Ahora busca la sección DATABASES (alrededor de la línea 75) y reemplázala:

biblioteca_project/settings.py
# Busca esta sección y reemplázala:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # Motor MySQL
        'NAME': 'biblioteca_db',              # Nombre de la base de datos
        'USER': 'biblioteca_user',            # Usuario de MySQL
        'PASSWORD': 'password123',            # Contraseña del usuario
        'HOST': 'localhost',                 # Servidor (local)
        'PORT': '3306',                      # Puerto por defecto de MySQL
        'OPTIONS': {
            'charset': 'utf8mb4',           # Soporte para emojis y caracteres especiales
        }
    }
}

# Más arriba en el archivo, busca INSTALLED_APPS y agrégale:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # ⭐ Agregar Django REST Framework
    'libros',  # ⭐ Agregar nuestra aplicación
]

# Al final del archivo, agregar tu API Key de Google Maps
GOOGLE_MAPS_API_KEY = 'AIzaSyD1234...'  # ⭐ Pega tu API Key aquí (la que copiaste en el PASO 1)

# Configuración de idioma español
LANGUAGE_CODE = 'es-mx'
TIME_ZONE = 'America/Mexico_City'  # Zona horaria de México

⚠️ Verificar antes de continuar:

  • ✅ Reemplazaste 'biblioteca_db' con el nombre de tu base de datos
  • ✅ Reemplazaste 'biblioteca_user' con tu usuario de MySQL
  • ✅ Reemplazaste 'password123' con tu contraseña de MySQL
  • ✅ Agregaste 'rest_framework' y 'libros' en INSTALLED_APPS
  • ✅ Pegaste tu API Key de Google Maps (sin las comillas)

💡 Consejo de Seguridad

Nunca subas tu API Key a GitHub. En proyectos reales, usa variables de entorno:

  1. Crea un archivo .env en la raíz del proyecto
  2. Instala python-decouple: pip install python-decouple
  3. En .env: GOOGLE_MAPS_API_KEY=tu_clave_aqui
  4. En settings.py: from decouple import config y GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY')
  5. Agrega .env a tu archivo .gitignore

📋 PASO 4: Crear los Modelos (15 minutos)

4.1 Entender qué es un Modelo

Un modelo en Django es una clase de Python que representa una tabla en la base de datos. Cada atributo del modelo es un campo (columna) en la tabla.

Ejemplo: Si queremos guardar bibliotecas, necesitamos campos como: nombre, dirección, ciudad, latitud, longitud, etc.

4.2 Crear archivo de servicios

Primero, crea un archivo nuevo libros/services.py (haz clic derecho en la carpeta libros → Nuevo archivo → services.py)

Este archivo contendrá el código para comunicarse con Google Maps API. Copia el código completo de la Sección 4, Paso 4 de esta guía.

4.3 Crear el modelo Biblioteca

Abre el archivo libros/models.py y copia el código completo de la Sección 4, Paso 3 de esta guía.

4.4 Crear las migraciones (convertir modelo en tabla)
PowerShell
# Crear archivos de migración (Django analiza tus modelos)
python manage.py makemigrations

# Deberías ver un mensaje como: "Migrations for 'libros': libros/migrations/0001_initial.py"

# Aplicar las migraciones (crear tablas en MySQL)
python manage.py migrate

# Deberías ver varios "OK" confirmando que se crearon las tablas

🎉 ¡Felicidades! Ya tienes la tabla libros_biblioteca creada en MySQL con todos los campos.

🚨 Si aparece un error al ejecutar migrate:

Error común: "Access denied for user"

  • Verifica que MySQL esté corriendo (abre MySQL Workbench o XAMPP)
  • Verifica usuario y contraseña en settings.py
  • Asegúrate de haber creado la base de datos biblioteca_db

Error: "No module named 'MySQLdb'"

  • Si usas PyMySQL, verifica que agregaste las líneas al inicio de settings.py
  • Si usas mysqlclient, reinstala: pip uninstall mysqlclient y luego pip install mysqlclient

Verificar que la tabla se creó:

MySQL Terminal o Workbench
-- Conectarse a la base de datos
USE biblioteca_db;

-- Ver todas las tablas
SHOW TABLES;

-- Deberías ver: libros_biblioteca

-- Ver estructura de la tabla
DESCRIBE libros_biblioteca;

📋 PASO 5: Configurar Panel de Administración (10 minutos)

5.1 Registrar el modelo en el admin

Abre libros/admin.py y agrega:

libros/admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Biblioteca

@admin.register(Biblioteca)
class BibliotecaAdmin(admin.ModelAdmin):
    """Panel de administración para Bibliotecas"""
    
    # Campos a mostrar en la lista
    list_display = ['nombre', 'ciudad', 'pais', 'latitud', 'longitud', 'ver_en_mapa']
    
    # Filtros en la barra lateral
    list_filter = ['ciudad', 'pais']
    
    # Campos de búsqueda
    search_fields = ['nombre', 'direccion', 'ciudad']
    
    # Campos solo de lectura (no editables)
    readonly_fields = ['latitud', 'longitud', 'place_id', 'google_maps_url', 'ver_en_mapa']
    
    # Acción personalizada para geocodificar
    actions = ['geocodificar_seleccionadas']
    
    def ver_en_mapa(self, obj):
        """Muestra un enlace para abrir en Google Maps"""
        if obj.google_maps_url:
            return format_html(
                '<a href="{}" target="_blank" style="color: #11998e; font-weight: bold;">🗺️ Abrir en Google Maps</a>',
                obj.google_maps_url
            )
        return '-'
    
    ver_en_mapa.short_description = 'Ver en Mapa'
    
    def geocodificar_seleccionadas(self, request, queryset):
        """Geocodifica las bibliotecas seleccionadas"""
        contador = 0
        for biblioteca in queryset:
            if biblioteca.geocodificar():
                contador += 1
        
        self.message_user(request, f'{contador} bibliotecas geocodificadas exitosamente.')
    
    geocodificar_seleccionadas.short_description = "Geocodificar bibliotecas seleccionadas"
5.2 Crear usuario administrador
PowerShell
# Crear superusuario (administrador)
python manage.py createsuperuser

# Te pedirá:
# Username: (escribe: admin)
# Email: (escribe tu correo o déjalo en blanco)
# Password: (escribe una contraseña segura, no se verá al escribir)
# Password (again): (repite la misma contraseña)
5.3 Iniciar servidor y probar el admin
PowerShell
# Iniciar servidor de desarrollo
python manage.py runserver

# Deberías ver algo como:
# Starting development server at http://127.0.0.1:8000/
# Quit the server with CTRL-BREAK.

✅ Paso a Paso - Probar el Admin:

  1. Abre tu navegador (Chrome, Firefox, Edge)
  2. Ve a: http://127.0.0.1:8000/admin/
  3. Deberías ver la página de login de Django (fondo azul)
  4. Ingresa:
    • Usuario: admin (el que creaste)
    • Contraseña: (la que pusiste)
  5. Haz clic en "Iniciar sesión"

🎯 Si todo salió bien, deberías ver:

  • ✅ Panel de administración de Django
  • ✅ Sección "AUTENTICACIÓN Y AUTORIZACIÓN" (Usuarios, Grupos)
  • ✅ Sección "LIBROS" con opción "Bibliotecas" ← ¡ESTO ES LO IMPORTANTE!

Ahora vamos a crear una biblioteca de prueba:

  1. Haz clic en "Bibliotecas"
  2. Haz clic en "Agregar Biblioteca +" (botón verde arriba a la derecha)
  3. Llena el formulario:
    Datos de Ejemplo
    Nombre: Biblioteca Central (UNAM)
    Dirección: Circuito Interior s/n, Ciudad Universitaria, C.P. 04510
    Ciudad: Ciudad de México
    País: México
    Teléfono: +504 2222-0000
    Horario: Lunes a Viernes: 8:00 AM - 6:00 PM
  4. Haz clic en "Guardar" (botón azul abajo)
  5. ⭐ IMPORTANTE: Los campos latitud, longitud y place_id aparecerán vacíos por ahora. Esto es normal.

Ahora geocodifiquemos la biblioteca:

  1. En la lista de bibliotecas, marca la casilla junto a "Biblioteca Central (UNAM)a"
  2. En el menú desplegable "Acción" (arriba), selecciona "Geocodificar bibliotecas seleccionadas"
  3. Haz clic en el botón "Ir"
  4. Deberías ver un mensaje verde: "1 bibliotecas geocodificadas exitosamente."
  5. Haz clic en la biblioteca para abrirla nuevamente
  6. ¡Ahora deberías ver las coordenadas!
    • Latitud: 14.0723 (aproximadamente)
    • Longitud: -87.1921 (aproximadamente)
    • Place ID: ChIJ...
    • Google Maps URL: https://www.google.com/maps/...
  7. Haz clic en el enlace "🗺️ Abrir en Google Maps" para verificar que funciona

✅ ¿Cómo se VE cuando SÍ funciona correctamente?

Cuando la geocodificación funciona, los campos se llenan AUTOMÁTICAMENTE así:

📍 Ejemplo: Biblioteca Vasconcelos

Campo Valor (aparece automáticamente)
Nombre: Biblioteca Vasconcelos
Dirección: Eje 1 Norte Mosqueta S/N
Ciudad: Ciudad de México
País: México
Latitud: 19.4395418
Longitud: -99.1520794
Place ID: ChIJXxQBnbP_0YURW9JOEKiHlBM
Google Maps URL: https://www.google.com/maps/place/?q=place_id:ChIJXxQBnbP_0YURW9JOEKiHlBM

⭐ Los campos marcados con estrella se llenan AUTOMÁTICAMENTE al guardar.

🎯 Más Ejemplos de Coordenadas Correctas - Bibliotecas de México

Biblioteca Latitud Longitud Ciudad
Biblioteca Nacional de México 19.3316 -99.1867 Ciudad de México
Biblioteca Central UNAM 19.3317 -99.1903 Ciudad de México
Biblioteca de México José Vasconcelos 19.4395 -99.1521 Ciudad de México
Biblioteca Iberoamericana Octavio Paz 19.3773 -99.2636 Ciudad de México
Biblioteca Palafoxiana 19.0414 -98.2063 Puebla
Biblioteca Pública del Estado de Jalisco 20.6767 -103.3475 Guadalajara
Biblioteca Universitaria Alfonso Reyes 25.7260 -100.3115 Monterrey

💡 Nota: Estas son coordenadas reales de bibliotecas importantes en México. Puedes usarlas para probar tu aplicación.

📸 Muestra en Pantalla

Cuando tus coordenadas aparezcan correctamente:

  1. Muestra la página del admin mostrando los campos llenos
  2. Haz clic en "🗺️ Abrir en Google Maps" y muestra el mapa

🚨 ¡PROBLEMA MÁS COMÚN! - Coordenadas No Aparecen

Si después de crear una biblioteca los campos quedan vacíos:

❌ Latitud: -

❌ Longitud: -

❌ Place id: (vacío)

❌ Google maps url: (vacío)

🔍 CAUSA #1: API Key No Configurada o Incorrecta (90% de los casos)

1 Verificar que la API Key esté en settings.py
Abre: biblioteca_project/settings.py
# Busca al FINAL del archivo settings.py
# Debe existir esta línea:

GOOGLE_MAPS_API_KEY = 'AIzaSyD1234567890abcdefghijklmnopqrstuvwxyz'  # ⭐ TU CLAVE AQUÍ

# ⚠️ VERIFICA:
# ✅ Que esté entre comillas simples o dobles
# ✅ Que sea TU API Key (no el ejemplo)
# ✅ Que no tenga espacios al inicio o final
# ✅ Que empiece con 'AIza'

Si no existe esa línea: Agrégala AL FINAL del archivo settings.py y reinicia el servidor.

🔍 CAUSA #2: Geocoding API No Habilitada

2 Verificar que Geocoding API esté activa en Google Cloud
  1. Ve a: Google Cloud Console - APIs
  2. Selecciona tu proyecto Bibliotecas-Geolocalización
  3. Verifica que aparezcan estas 2 APIs HABILITADAS:
    • Geocoding API
    • Maps JavaScript API
  4. Si NO están habilitadas:
    • Clic en "Biblioteca" (menú izquierdo)
    • Busca "Geocoding API"
    • Clic en "HABILITAR"
    • Espera 2-3 minutos

🔍 CAUSA #3: Facturación No Configurada

3 Verificar que la facturación esté activa
  1. Ve a: Google Cloud Console - Facturación
  2. Verifica que tu proyecto tenga una cuenta de facturación vinculada
  3. Debes ver: "$200.00 de crédito gratis"
  4. Si NO está vinculada:
    • Clic en "Vincular una cuenta de facturación"
    • Sigue el proceso (necesitas tarjeta, pero NO te cobrarán si no superas $200/mes)

🔍 CAUSA #4: Servidor Django No Reiniciado

4 Reiniciar el servidor después de cambiar settings.py
PowerShell
# En la terminal donde corre el servidor:
# 1. Presiona CTRL + C para detenerlo

# 2. Inicia nuevamente:
python manage.py runserver

# 3. Espera a que diga "Starting development server..."

# 4. Intenta crear una biblioteca nuevamente

🔍 CAUSA #5: Error en el Código de Geocodificación

5 Probar la geocodificación manualmente en Django Shell
PowerShell (nueva terminal)
# Abre Django shell
python manage.py shell

# Dentro del shell, ejecuta:
from libros.services import GoogleMapsService

# Probar geocodificación
resultado = GoogleMapsService.geocodificar_direccion("Biblioteca Vasconcelos, Ciudad de México, México")

print(resultado)

# Si funciona, deberías ver:
# {'lat': Decimal('19.4395'), 'lng': Decimal('-99.1520'), 'place_id': 'ChIJ...', ...}

# Si NO funciona, verás:
# None
# o un error con el mensaje específico

# Para salir del shell:
exit()

Si ves None o un error:

  • El error te dirá qué está mal (API Key, API no habilitada, facturación, etc.)
  • Consulta el archivo SOLUCION_COORDENADAS_NO_APARECEN.md para más detalles

⚠️ ERRORES MÁS COMUNES al Probar en Django Shell

❌ ERROR #1: "Must provide API key"

Error completo
ValueError: Must provide API key or enterprise credentials when creating client.

🔧 SOLUCIÓN:

  1. Abre biblioteca_project/settings.py
  2. Verifica que AL FINAL del archivo exista:
    settings.py (al final)
    GOOGLE_MAPS_API_KEY = 'AIzaSyD1234567890abcdefghijklmnopqrstuvwxyz'
  3. Si NO existe: Agrégala con TU API Key (la que copiaste en el PASO 1)
  4. Si SÍ existe: Verifica que:
    • ✓ No tenga espacios antes o después de las comillas
    • ✓ Las comillas estén bien escritas (simples ' o dobles ")
    • ✓ Sea tu API Key real (no el ejemplo)
    • ✓ Empiece con AIza
  5. GUARDA el archivo settings.py
  6. SALE del Django shell: exit()
  7. REINICIA el servidor:
    PowerShell
    # Detén el servidor con CTRL + C
    python manage.py runserver
  8. VUELVE A INTENTAR en el Django shell

❌ ERROR #2: "This API project is not authorized"

Error completo
googlemaps.exceptions.ApiError: This API project is not authorized to use this API.

🔧 SOLUCIÓN:

  1. Ve a: Habilitar Geocoding API
  2. Selecciona tu proyecto Bibliotecas-Geolocalización
  3. Clic en "HABILITAR"
  4. Espera 2-3 minutos
  5. Vuelve a intentar en Django shell

❌ ERROR #3: "You must enable Billing"

Error completo
googlemaps.exceptions.ApiError: You must enable Billing on the Google Cloud Project

🔧 SOLUCIÓN:

  1. Ve a: Google Cloud - Facturación
  2. Vincula una cuenta de facturación (necesitas tarjeta)
  3. Obtienes $200 USD de crédito gratis
  4. NO te cobrarán si no superas ese límite

❌ ERROR #4: "ZERO_RESULTS"

Si recibes None sin error, significa que Google no encontró la dirección.

🔧 SOLUCIÓN:

  1. Verifica que la dirección sea completa y correcta
  2. Incluye ciudad y país siempre: "Biblioteca Vasconcelos, Ciudad de México, México"
  3. Prueba con una dirección conocida (usa los ejemplos de la tabla arriba)

💡 PRUEBA RÁPIDA - Copia y Pega

Para verificar que todo funciona, copia EXACTAMENTE este código en el Django shell:

Django Shell - Prueba Completa
from django.conf import settings
import googlemaps

# Ver si la API Key está configurada
print("API Key configurada:", hasattr(settings, 'GOOGLE_MAPS_API_KEY'))

# Si dice True, continúa:
print("API Key (primeros 10 caracteres):", settings.GOOGLE_MAPS_API_KEY[:10])

# Probar conexión
gmaps = googlemaps.Client(key=settings.GOOGLE_MAPS_API_KEY)
resultado = gmaps.geocode("Biblioteca Vasconcelos, Ciudad de México, México")

# Si funciona, verás un array con resultados
print("Latitud:", resultado[0]['geometry']['location']['lat'])
print("Longitud:", resultado[0]['geometry']['location']['lng'])

✅ Si ves latitud y longitud, TODO FUNCIONA CORRECTAMENTE

✅ SOLUCIÓN RÁPIDA - Checklist Completo

Verifica EN ORDEN:

Listo Verificación Cómo verificar
[ ] API Key en settings.py Abre settings.py, busca GOOGLE_MAPS_API_KEY al final
[ ] Geocoding API habilitada Google Cloud Console → APIs → Geocoding API (debe estar ENABLED)
[ ] Facturación activa Google Cloud Console → Facturación (debe mostrar $200 crédito)
[ ] Servidor reiniciado Detener con CTRL+C y ejecutar python manage.py runserver
[ ] Código correcto Probar en python manage.py shell (comando arriba)
[ ] Conexión a Internet Google Maps API necesita Internet para funcionar

📖 Documentación Completa de Soluciones

Para TODAS las causas posibles y soluciones detalladas, consulta el archivo:

📄 SOLUCION_COORDENADAS_NO_APARECEN.md

Este archivo contiene:

  • ✅ 8 causas comunes de errores
  • ✅ Soluciones paso a paso para cada una
  • ✅ Comandos de diagnóstico
  • ✅ Ejemplos de mensajes de error y qué significan

🚨 ¿No aparecen las coordenadas después de geocodificar?

Revisa estos puntos:

  1. Abre la consola de Django (donde corre python manage.py runserver)
  2. Busca mensajes de error como:
    • ERROR: Error geocodificando: Invalid API key → API Key incorrecta
    • This API project is not authorized → Geocoding API no habilitada
    • You must enable Billing → Facturación no configurada
  3. Consulta el archivo: SOLUCION_COORDENADAS_NO_APARECEN.md para más ayuda

Prueba rápida de la API Key:

PowerShell (nueva terminal)
# Activar entorno virtual
.\venv\Scripts\Activate

# Abrir shell de Django
python manage.py shell

# Ejecutar en el shell:
>>> import googlemaps
>>> from django.conf import settings
>>> gmaps = googlemaps.Client(key=settings.GOOGLE_MAPS_API_KEY)
>>> resultado = gmaps.geocode('Ciudad de México, México')
>>> print(resultado)

# Si ves un error, tu API Key o configuración tiene problemas
# Si ves datos JSON, ¡funciona correctamente!

✅ Checkpoint: ¿Qué has logrado hasta ahora?

  • ✅ Obtuviste tu API Key de Google Maps
  • ✅ Creaste un proyecto Django desde cero
  • ✅ Configuraste MySQL como base de datos
  • ✅ Creaste un modelo para guardar bibliotecas con geolocalización
  • ✅ Probaste la geocodificación automática en el admin
  • ✅ Se genera automáticamente el enlace de Google Maps

🚀 Siguiente paso: Crear una API REST y el frontend con mapas interactivos.

🗺️ ¿Cómo se genera el Google Maps URL?

El método generar_google_maps_url() crea automáticamente un enlace a Google Maps usando 3 estrategias:

  1. Opción 1 - Place ID (más preciso): https://www.google.com/maps/place/?q=place_id:ChIJ...

    Utiliza el identificador único de Google Maps. Es la opción más precisa.

  2. Opción 2 - Coordenadas: https://www.google.com/maps?q=19.4474100,-99.1508100

    Usa latitud y longitud directamente. Funciona cuando no hay Place ID.

  3. Opción 3 - Búsqueda por texto: https://www.google.com/maps/search/?api=1&query=dirección+ciudad+país

    Hace una búsqueda por texto. Se usa cuando no hay coordenadas.

Ejemplo de uso:

Ejemplo en Django Shell
from libros.models import Biblioteca

# Obtener una biblioteca
biblioteca = Biblioteca.objects.get(id=1)

# Ver el URL generado
print(biblioteca.google_maps_url)
# Output: https://www.google.com/maps/place/?q=place_id:ChIJ...

# Abrir directamente en el navegador desde la terminal
import webbrowser
webbrowser.open(biblioteca.google_maps_url)

📝 Formatos de URL de Google Maps

Formato Cuando se Usa Ejemplo
Place ID Cuando Google devuelve un Place ID ?q=place_id:ChIJ...
Coordenadas Cuando tienes lat/lng pero no Place ID ?q=14.0723,-87.1921
Búsqueda Cuando solo tienes dirección de texto search/?api=1&query=...
Directions Para rutas de A a B dir/?api=1&origin=...&destination=...

📋 PASO 6: Crear API REST (20 minutos)

🤔 ¿Qué es una API REST?

API = Forma en que dos programas se comunican

REST = Conjunto de reglas para crear APIs

En resumen: Es como un "menú de restaurante" que le dice al frontend (página web) qué datos puede pedir y cómo pedirlos.

Ejemplo: GET /api/bibliotecas/ → "Dame la lista de todas las bibliotecas"

6.1 Crear Serializer (Convertidor de datos)

Los serializers convierten objetos de Python en JSON (formato que entienden los navegadores).

Crea un archivo nuevo: libros/serializers.py

libros/serializers.py
from rest_framework import serializers
from .models import Biblioteca

class BibliotecaSerializer(serializers.ModelSerializer):
    """
    Serializer para convertir el modelo Biblioteca a JSON
    
    ¿Qué hace?
    - Convierte objetos Python → JSON (para enviar al frontend)
    - Convierte JSON → objetos Python (cuando recibe datos del frontend)
    """
    
    # Campo adicional calculado automáticamente
    direccion_completa = serializers.CharField(read_only=True)
    
    class Meta:
        model = Biblioteca  # ¿Qué modelo serializar?
        fields = '__all__'  # Incluir todos los campos del modelo
        
        # Campos que no se pueden editar (se calculan automáticamente)
        read_only_fields = ['latitud', 'longitud', 'place_id']
    
    def create(self, validated_data):
        """
        Se ejecuta cuando se crea una nueva biblioteca
        
        1. Crea la biblioteca en la base de datos
        2. Llama a geocodificar() para obtener las coordenadas
        3. Retorna la biblioteca con coordenadas
        """
        biblioteca = super().create(validated_data)  # Crear en BD
        biblioteca.geocodificar()  # Obtener coordenadas de Google Maps
        return biblioteca
6.2 Crear ViewSet (Controlador de la API)

Los ViewSets manejan las peticiones HTTP (GET, POST, PUT, DELETE).

Crea un archivo nuevo: libros/api_views.py

libros/api_views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Biblioteca
from .serializers import BibliotecaSerializer
from .services import GoogleMapsService

class BibliotecaViewSet(viewsets.ModelViewSet):
    """
    ViewSet que maneja todas las operaciones con Bibliotecas
    
    Endpoints que crea automáticamente:
    - GET    /api/bibliotecas/          → Listar todas
    - POST   /api/bibliotecas/          → Crear nueva
    - GET    /api/bibliotecas/{id}/     → Ver una específica
    - PUT    /api/bibliotecas/{id}/     → Actualizar
    - DELETE /api/bibliotecas/{id}/     → Eliminar
    """
    
    queryset = Biblioteca.objects.all()  # Todas las bibliotecas
    serializer_class = BibliotecaSerializer  # Usar este serializer
    
    # Endpoint personalizado: /api/bibliotecas/cercanas/?lat=14.0723&lng=-87.1921
    @action(detail=False, methods=['get'])
    def cercanas(self, request):
        """
        Obtiene bibliotecas cercanas a una ubicación
        
        Parámetros requeridos:
        - lat: Latitud del usuario
        - lng: Longitud del usuario
        
        Ejemplo: GET /api/bibliotecas/cercanas/?lat=14.0723&lng=-87.1921
        """
        
        # Obtener parámetros de la URL
        lat = request.query_params.get('lat')
        lng = request.query_params.get('lng')
        
        # Validar que vengan los parámetros
        if not lat or not lng:
            return Response(
                {'error': 'Se requieren parámetros lat y lng'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Filtrar solo bibliotecas con coordenadas
        bibliotecas = self.queryset.filter(
            latitud__isnull=False,
            longitud__isnull=False
        )
        
        # Convertir a JSON y retornar
        serializer = self.get_serializer(bibliotecas, many=True)
        return Response(serializer.data)
    
    # Endpoint personalizado: POST /api/bibliotecas/{id}/geocodificar/
    @action(detail=True, methods=['post'])
    def geocodificar(self, request, pk=None):
        """
        Geocodifica una biblioteca específica manualmente
        
        Ejemplo: POST /api/bibliotecas/1/geocodificar/
        """
        
        biblioteca = self.get_object()  # Obtener la biblioteca por ID
        
        if biblioteca.geocodificar():  # Intentar geocodificar
            return Response({
                'mensaje': 'Geocodificación exitosa',
                'nombre': biblioteca.nombre,
                'latitud': str(biblioteca.latitud),
                'longitud': str(biblioteca.longitud)
            })
        
        return Response(
            {'error': 'No se pudo geocodificar la dirección'},
            status=status.HTTP_400_BAD_REQUEST
        )
6.3 Configurar las URLs de la API

Crea un archivo nuevo: libros/urls.py

libros/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .api_views import BibliotecaViewSet

# Router crea automáticamente todas las rutas del ViewSet
router = DefaultRouter()
router.register(r'bibliotecas', BibliotecaViewSet, basename='biblioteca')

# Rutas generadas automáticamente:
# GET    /api/bibliotecas/
# POST   /api/bibliotecas/
# GET    /api/bibliotecas/1/
# PUT    /api/bibliotecas/1/
# DELETE /api/bibliotecas/1/
# GET    /api/bibliotecas/cercanas/?lat=14&lng=-87
# POST   /api/bibliotecas/1/geocodificar/

urlpatterns = [
    path('', include(router.urls)),
]
6.4 Conectar las URLs al proyecto principal

Abre biblioteca_project/urls.py y modifícalo:

biblioteca_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),  # Panel de administración
    path('api/', include('libros.urls')),  # ⭐ Nuestra API
]
6.5 Probar la API
PowerShell
# Asegúrate de que el servidor esté corriendo
python manage.py runserver
  1. Abre tu navegador y ve a: http://127.0.0.1:8000/api/bibliotecas/
  2. Verás una interfaz bonita de Django REST Framework
  3. Haz clic en "GET" para ver todas las bibliotecas en formato JSON
  4. Prueba crear una nueva desde el formulario HTML que aparece abajo
  5. Prueba también: http://127.0.0.1:8000/api/bibliotecas/1/ (ver una específica)

🎉 ¡API funcionando!

Ahora tienes una API REST completa que:

  • ✅ Lista todas las bibliotecas
  • ✅ Crea nuevas bibliotecas con geocodificación automática
  • ✅ Permite ver, editar y eliminar bibliotecas
  • ✅ Busca bibliotecas cercanas a una ubicación
  • ✅ Geocodifica bibliotecas manualmente
libros/api_views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Biblioteca
from .serializers import BibliotecaSerializer
from .services import GoogleMapsService

# (Ver código completo en PASO 6 arriba)

6. 🎨 Integración Frontend con Mapas

📋 PASO 7: Crear página con mapa interactivo (25 minutos)

🗺️ ¿Qué vamos a hacer?

Crear una página web HTML que:

  • ✅ Muestre un mapa de Google Maps
  • ✅ Cargue las bibliotecas desde nuestra API
  • ✅ Coloque un marcador (pin) en cada biblioteca
  • ✅ Muestre información al hacer clic en un marcador
7.1 Crear carpeta de templates
  1. En la carpeta libros, crea una nueva carpeta llamada templates
  2. Dentro de templates, crea otra carpeta llamada libros
  3. Tu estructura debe ser: libros/templates/libros/
7.2 Crear el archivo HTML del mapa

Dentro de libros/templates/libros/, crea un archivo llamado mapa.html

Código Completo del Mapa (con explicaciones paso a paso)

libros/templates/libros/mapa.html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mapa de Bibliotecas</title>
    
    <!-- Estilos CSS -->
    <style>
        /* Resetear estilos por defecto */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
        }
        
        /* Contenedor principal */
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        
        /* Encabezado */
        .header {
            background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        /* El mapa ocupará 600px de alto y todo el ancho */
        #map {
            height: 600px;
            width: 100%;
        }
        
        /* Panel de información */
        .info-panel {
            padding: 20px;
            background: #f8f9fa;
        }
        
        /* Botones */
        .btn {
            background: #11998e;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin: 5px;
        }
        
        .btn:hover {
            background: #0d7a6f;
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- Encabezado -->
        <div class="header">
            <h1>🗺️ Mapa de Bibliotecas de México</h1>
            <p>Encuentra la biblioteca más cercana</p>
        </div>
        
        <!-- Panel de controles -->
        <div class="info-panel">
            <button class="btn" onclick="obtenerMiUbicacion()">
                📍 Usar mi ubicación actual
            </button>
            <button class="btn" onclick="cargarBibliotecas()">
                🔄 Recargar bibliotecas
            </button>
            <span id="contador" style="margin-left: 20px; font-weight: bold;">
                Bibliotecas cargadas: 0
            </span>
        </div>
        
        <!-- Contenedor del mapa (aquí se mostrará Google Maps) -->
        <div id="map"></div>
    </div>
    
    <!-- Cargar la API de Google Maps -->
    <!-- IMPORTANTE: Reemplaza TU_API_KEY con tu clave real -->
    <script src="https://maps.googleapis.com/maps/api/js?key=TU_API_KEY&callback=initMap"
            async defer></script>
    
    <!-- JavaScript para controlar el mapa -->
    <script>
        // ========================================
        // VARIABLES GLOBALES
        // ========================================
        
        let map;  // Variable que guardará el objeto del mapa
        let markers = [];  // Array para guardar todos los marcadores
        
        // ========================================
        // FUNCIÓN 1: Inicializar el mapa
        // Se ejecuta automáticamente cuando carga Google Maps
        // ========================================
        
        function initMap() {
            console.log('🗺️ Inicializando mapa...');
            
            // Crear el mapa centrado en Ciudad de México, México
            map = new google.maps.Map(document.getElementById('map'), {
                center: { 
                    lat: 19.4326,   // Latitud de Ciudad de México
                    lng: -99.1332   // Longitud de Ciudad de México
                },
                zoom: 4,  // Nivel de zoom (1 = mundo, 20 = edificio)
                
                // Opciones adicionales del mapa
                mapTypeControl: true,  // Mostrar botón para cambiar tipo de mapa
                streetViewControl: true,  // Mostrar botón Street View
                fullscreenControl: true,  // Botón de pantalla completa
                zoomControl: true  // Botones de zoom +/-
            });
            
            // Cargar las bibliotecas automáticamente al iniciar
            cargarBibliotecas();
        }
        
        // ========================================
        // FUNCIÓN 2: Cargar bibliotecas desde la API
        // ========================================
        
        async function cargarBibliotecas() {
            console.log('📚 Cargando bibliotecas desde la API...');
            
            try {
                // Hacer petición GET a nuestra API
                const response = await fetch('/api/bibliotecas/');
                
                // Verificar si la petición fue exitosa
                if (!response.ok) {
                    throw new Error('Error en la petición: ' + response.status);
                }
                
                // Convertir la respuesta a JSON
                const data = await response.json();
                
                console.log('✅ Datos recibidos:', data);
                
                // Limpiar marcadores anteriores
                limpiarMarcadores();
                
                // Contador de bibliotecas
                let contador = 0;
                
                // Crear un marcador para cada biblioteca
                data.forEach(biblioteca => {
                    // Solo crear marcador si tiene coordenadas
                    if (biblioteca.latitud && biblioteca.longitud) {
                        crearMarcador(biblioteca);
                        contador++;
                    }
                });
                
                // Actualizar contador en la página
                document.getElementById('contador').textContent = 
                    `Bibliotecas cargadas: ${contador}`;
                
            } catch (error) {
                console.error('❌ Error cargando bibliotecas:', error);
                alert('Error al cargar las bibliotecas. Verifica la consola.');
            }
        }
        
        // ========================================
        // FUNCIÓN 3: Crear marcador en el mapa
        // ========================================
        
        function crearMarcador(biblioteca) {
            // Crear el marcador (pin) en el mapa
            const marker = new google.maps.Marker({
                position: {
                    lat: parseFloat(biblioteca.latitud),  // Convertir string a número
                    lng: parseFloat(biblioteca.longitud)
                },
                map: map,  // En qué mapa mostrarlo
                title: biblioteca.nombre,  // Texto al pasar el mouse
                animation: google.maps.Animation.DROP  // Animación de caída
            });
            
            // Crear ventana de información (popup)
            const infoWindow = new google.maps.InfoWindow({
                content: `
                    <div style="padding: 10px; max-width: 250px;">
                        <h3 style="margin: 0 0 10px 0; color: #11998e;">
                            ${biblioteca.nombre}
                        </h3>
                        <p style="margin: 5px 0;">
                            <strong>📍 Dirección:</strong><br>
                            ${biblioteca.direccion}<br>
                            ${biblioteca.ciudad}, ${biblioteca.pais}
                        </p>
                        <p style="margin: 5px 0;">
                            <strong>📞 Teléfono:</strong><br>
                            ${biblioteca.telefono || 'No disponible'}
                        </p>
                        <p style="margin: 5px 0;">
                            <strong>🕒 Horario:</strong><br>
                            ${biblioteca.horario || 'No disponible'}
                        </p>
                        <p style="margin: 10px 0 0 0; font-size: 0.9em; color: #666;">
                            <strong>Coordenadas:</strong><br>
                            ${biblioteca.latitud}, ${biblioteca.longitud}
                        </p>
                        ${biblioteca.google_maps_url ? `
                            <p style="margin: 10px 0 0 0; text-align: center;">
                                <a href="${biblioteca.google_maps_url}" 
                                   target="_blank" 
                                   style="display: inline-block; 
                                          background: #11998e; 
                                          color: white; 
                                          padding: 8px 15px; 
                                          text-decoration: none; 
                                          border-radius: 5px;
                                          font-weight: bold;">
                                    🗺️ Abrir en Google Maps
                                </a>
                            </p>
                        ` : ''}
                    </div>
                `
            });
            
            // Al hacer clic en el marcador, mostrar la información
            marker.addListener('click', () => {
                // Cerrar todas las ventanas abiertas
                markers.forEach(m => {
                    if (m.infoWindow) {
                        m.infoWindow.close();
                    }
                });
                
                // Abrir esta ventana
                infoWindow.open(map, marker);
                
                // Centrar el mapa en este marcador
                map.setCenter(marker.getPosition());
            });
            
            // Guardar referencia a la ventana de información
            marker.infoWindow = infoWindow;
            
            // Agregar el marcador al array
            markers.push(marker);
        }
        
        // ========================================
        // FUNCIÓN 4: Obtener ubicación del usuario
        // ========================================
        
        function obtenerMiUbicacion() {
            console.log('📍 Obteniendo ubicación del usuario...');
            
            // Verificar si el navegador soporta geolocalización
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(
                    // Éxito: se obtuvo la ubicación
                    (position) => {
                        const pos = {
                            lat: position.coords.latitude,
                            lng: position.coords.longitude
                        };
                        
                        console.log('✅ Ubicación obtenida:', pos);
                        
                        // Centrar el mapa en la ubicación del usuario
                        map.setCenter(pos);
                        map.setZoom(15);
                        
                        // Crear marcador "Estoy aquí"
                        new google.maps.Marker({
                            position: pos,
                            map: map,
                            title: 'Mi ubicación',
                            icon: {
                                url: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
                            }
                        });
                    },
                    // Error: no se pudo obtener la ubicación
                    (error) => {
                        console.error('❌ Error obteniendo ubicación:', error);
                        alert('No se pudo obtener tu ubicación. Verifica los permisos.');
                    }
                );
            } else {
                alert('Tu navegador no soporta geolocalización');
            }
        }
        
        // ========================================
        // FUNCIÓN 5: Limpiar todos los marcadores
        // ========================================
        
        function limpiarMarcadores() {
            // Remover cada marcador del mapa
            markers.forEach(marker => {
                marker.setMap(null);
            });
            
            // Vaciar el array
            markers = [];
        }
        
    </script>
</body>
</html>
7.3 Crear vista en Django para servir el HTML

Abre libros/views.py y agrega:

libros/views.py
from django.shortcuts import render

def mapa_bibliotecas(request):
    """
    Vista que muestra el mapa de bibliotecas
    """
    return render(request, 'libros/mapa.html')
7.4 Agregar la ruta en urls.py

Modifica libros/urls.py:

libros/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .api_views import BibliotecaViewSet
from .views import mapa_bibliotecas  # ⭐ Importar la vista

router = DefaultRouter()
router.register(r'bibliotecas', BibliotecaViewSet, basename='biblioteca')

urlpatterns = [
    path('', include(router.urls)),
    path('mapa/', mapa_bibliotecas, name='mapa'),  # ⭐ Ruta del mapa
]
7.5 ¡IMPORTANTE! Reemplazar la API Key en el HTML

Abre el archivo libros/templates/libros/mapa.html que acabas de crear:

  1. Busca la línea (alrededor de la línea 132):
    ANTES (línea a buscar):
    <script src="https://maps.googleapis.com/maps/api/js?key=TU_API_KEY&callback=initMap"
  2. Reemplaza TU_API_KEY con tu clave real de Google Maps (sin comillas):
    DESPUÉS (tu código):
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD1234567890abcdefghijklmnopqrstuvwxyz&callback=initMap"
  3. Guarda el archivo (Ctrl + S)

⚠️ Errores comunes:

  • ❌ Dejar TU_API_KEY sin reemplazar → El mapa no cargará
  • ❌ Poner la clave entre comillas: key="'AIza...'" → Error
  • ❌ Usar una API Key diferente a la de settings.py → Funciona, pero es confuso
  • Correcto: key=AIzaSyD1234... (sin comillas, tu clave real)
7.6 Probar el mapa interactivo
PowerShell
# Asegúrate de que el servidor esté corriendo
# Si lo detuviste, inícialo nuevamente:
python manage.py runserver

# Deberías ver: "Starting development server at http://127.0.0.1:8000/"

✅ Ahora sí, probemos el mapa:

  1. Abre tu navegador
  2. Ve a: http://127.0.0.1:8000/api/mapa/
  3. Espera unos segundos mientras carga Google Maps
  4. Deberías ver:
    • ✅ Un mapa de Google Maps centrado en Ciudad de México
    • ✅ Dos botones: "📍 Usar mi ubicación actual" y "🔄 Recargar bibliotecas"
    • ✅ Un contador: "Bibliotecas cargadas: X"
    • ✅ Marcadores (pins rojos) en las ubicaciones de tus bibliotecas
  5. Haz clic en un marcador → Deberías ver una ventana emergente con:
    • Nombre de la biblioteca
    • Dirección completa
    • Teléfono y horario
    • Coordenadas
    • Botón verde: "🗺️ Abrir en Google Maps"
  6. Haz clic en "📍 Usar mi ubicación actual"
    • El navegador te pedirá permiso → Haz clic en "Permitir"
    • El mapa se centrará en tu ubicación
    • Aparecerá un marcador azul: "Mi ubicación"

🚨 Problemas comunes al ver el mapa:

1. El mapa no carga (pantalla gris):

  • Presiona F12 para abrir la consola del navegador
  • Ve a la pestaña "Console"
  • Busca errores en rojo:
    • InvalidKeyMapError → API Key incorrecta o no reemplazada
    • ApiNotActivatedMapError → Maps JavaScript API no habilitada
    • RefererNotAllowedMapError → Restricciones de la API Key muy estrictas

2. No aparecen los marcadores:

  • Presiona F12 → Pestaña "Console"
  • Busca: Bibliotecas cargadas: 0
  • Verifica que hayas creado bibliotecas en el admin
  • Verifica que las bibliotecas tengan coordenadas (latitud/longitud)
  • Prueba: http://127.0.0.1:8000/api/bibliotecas/ → Deberías ver JSON con tus bibliotecas

3. Error: "Failed to fetch":

  • El servidor Django no está corriendo → Ejecuta python manage.py runserver
  • La URL es incorrecta → Asegúrate de usar http://127.0.0.1:8000/api/mapa/

4. "Mi ubicación" no funciona:

  • Algunos navegadores requieren HTTPS para geolocalización
  • Verifica que diste permiso al navegador
  • Si usas Firefox, ve a Configuración → Privacidad → Permisos → Ubicación

✅ Si todo funciona correctamente, deberías poder:

  • ✅ Ver el mapa de Google Maps
  • ✅ Ver marcadores en cada biblioteca
  • ✅ Hacer clic en los marcadores y ver información
  • ✅ Usar el zoom y moverse por el mapa
  • ✅ Abrir bibliotecas en Google Maps (nueva pestaña)
  • ✅ Ver tu ubicación actual en el mapa

🎉 ¡Proyecto Completo!

Felicidades, has creado un sistema completo con:

  • Backend: Django + MySQL + Google Maps API
  • API REST: Endpoints para crear, listar y geocodificar bibliotecas
  • Frontend: Mapa interactivo con Google Maps
  • Geolocalización: Coordenadas automáticas para cada biblioteca
  • Panel Admin: Gestión fácil de bibliotecas
  • Google Maps URL: Enlaces directos a cada ubicación

✅ Checklist Final - Verifica que todo funcione:

🔧 Backend y Base de Datos:

  • [ ] Django instalado: python -m django --version muestra 4.2.x
  • [ ] MySQL corriendo y base de datos biblioteca_db creada
  • [ ] Conexión a MySQL funciona: python manage.py dbshell (luego EXIT;)
  • [ ] Migraciones aplicadas: python manage.py showmigrations muestra [X]
  • [ ] Archivo services.py creado con el código de Google Maps
  • [ ] API Key de Google Maps configurada en settings.py

👨‍💼 Panel de Administración:

  • [ ] Superusuario creado: python manage.py createsuperuser
  • [ ] Admin accesible en: http://127.0.0.1:8000/admin/
  • [ ] Sección "Bibliotecas" visible en el admin
  • [ ] Puedes agregar una nueva biblioteca
  • [ ] Acción "Geocodificar bibliotecas" funciona
  • [ ] Coordenadas se llenan automáticamente al geocodificar
  • [ ] Campo "Ver en Mapa" muestra enlace clicable

🔌 API REST:

  • [ ] http://127.0.0.1:8000/api/bibliotecas/ muestra interfaz de Django REST
  • [ ] Puedes ver la lista de bibliotecas en formato JSON
  • [ ] Puedes crear una biblioteca desde la API
  • [ ] Endpoint /api/bibliotecas/1/ muestra una biblioteca específica
  • [ ] Endpoint /api/bibliotecas/cercanas/?lat=14&lng=-87 funciona

🗺️ Mapa Interactivo:

  • [ ] http://127.0.0.1:8000/api/mapa/ carga correctamente
  • [ ] Mapa de Google Maps se muestra (no pantalla gris)
  • [ ] Aparecen marcadores rojos en las bibliotecas
  • [ ] Contador muestra: "Bibliotecas cargadas: X" (X > 0)
  • [ ] Al hacer clic en un marcador, se muestra la información
  • [ ] Botón "Abrir en Google Maps" funciona (abre nueva pestaña)
  • [ ] Botón "Usar mi ubicación" funciona (si das permiso)

📊 Prueba Final (Flujo Completo):

  1. [ ] Crear biblioteca desde el admin con dirección real
  2. [ ] Geocodificar usando la acción del admin
  3. [ ] Verificar que se llenaron latitud, longitud y google_maps_url
  4. [ ] Hacer clic en "Ver en Mapa" → Abre Google Maps en la ubicación correcta
  5. [ ] Ir a /api/mapa/ → Ver el marcador en el mapa
  6. [ ] Hacer clic en el marcador → Ver toda la información
  7. [ ] Verificar en /api/bibliotecas/ que el JSON incluye todas las coordenadas

🚨 ¿Las Coordenadas No Aparecen?

Si al crear una biblioteca los campos Latitud, Longitud y Place ID aparecen vacíos (-), revisa el archivo:

📄 SOLUCION_COORDENADAS_NO_APARECEN.md

Este documento contiene 8 causas comunes y sus soluciones paso a paso:

  1. No se instaló la biblioteca googlemaps
  2. API Key no configurada o incorrecta
  3. Geocoding API no habilitada
  4. ⭐ Facturación no configurada (la más común)
  5. Archivo services.py no existe
  6. Método geocodificar() falta en el modelo
  7. Serializer no llama a geocodificar()
  8. Restricciones de la API Key

💡 Tip: El problema más frecuente es no tener configurada la facturación en Google Cloud, aunque Google ofrece $200 USD gratis al mes.

🚀 Mejoras que puedes agregar:

  1. Búsqueda por ubicación: Agregar un campo de búsqueda para encontrar bibliotecas por ciudad
  2. Calcular distancias: Mostrar la distancia desde tu ubicación a cada biblioteca
  3. Filtros: Filtrar bibliotecas por ciudad, horario, etc.
  4. Rutas: Mostrar cómo llegar a una biblioteca
  5. Compartir: Agregar botón para compartir la ubicación de una biblioteca

📋 Comandos Rápidos de Referencia

Iniciar/Detener Proyecto:

PowerShell
# Activar entorno virtual
.\venv\Scripts\Activate

# Iniciar servidor
python manage.py runserver

# Detener servidor
# Presiona: Ctrl + C

# Desactivar entorno virtual
deactivate

Trabajar con la Base de Datos:

PowerShell
# Crear migraciones después de cambiar models.py
python manage.py makemigrations

# Aplicar migraciones
python manage.py migrate

# Ver estado de migraciones
python manage.py showmigrations

# Abrir shell de Django (para pruebas)
python manage.py shell

# Conectarse a la base de datos MySQL
python manage.py dbshell

Gestión de Usuarios:

PowerShell
# Crear nuevo superusuario
python manage.py createsuperuser

# Cambiar contraseña de usuario
python manage.py changepassword admin

Pruebas con la API:

PowerShell
# Listar todas las bibliotecas (JSON)
Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/bibliotecas/"

# Ver una biblioteca específica
Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/bibliotecas/1/"

# Crear biblioteca (POST)
$body = @{
    nombre = "Test"
    direccion = "Circuito Interior s/n, Ciudad Universitaria"
    ciudad = " Ciudad de México"
    pais = "México"
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/bibliotecas/" `
                  -Method POST `
                  -Body $body `
                  -ContentType "application/json"

Solución de Problemas:

PowerShell
# Verificar versión de Python
python --version

# Verificar versión de Django
python -m django --version

# Listar paquetes instalados
pip list

# Ver configuración de Django
python manage.py check

# Reinstalar dependencias desde requirements.txt
pip install -r requirements.txt

# Limpiar cache de Python
python manage.py clean_pyc

# Verificar integridad de la base de datos
python manage.py check --database default

📚 URLs Importantes del Proyecto

URL Descripción
http://127.0.0.1:8000/ Página principal (puede mostrar error si no hay vista raíz)
http://127.0.0.1:8000/admin/ Panel de administración de Django
http://127.0.0.1:8000/api/bibliotecas/ API REST - Listar todas las bibliotecas
http://127.0.0.1:8000/api/bibliotecas/1/ API REST - Ver biblioteca con ID 1
http://127.0.0.1:8000/api/bibliotecas/cercanas/?lat=14&lng=-87 API REST - Buscar bibliotecas cercanas
http://127.0.0.1:8000/api/mapa/ Mapa interactivo con Google Maps

🎓 Entrega de Práctica - Evidencias Requeridas

Para completar tu práctica, muestra en pantalla lo siguiente:

  1. Muestra 1: Panel de admin mostrando la lista de bibliotecas con coordenadas
  2. Muestra 2: Formulario de una biblioteca con latitud, longitud y google_maps_url llenos
  3. Muestra 3: Mapa interactivo (/api/mapa/) mostrando marcadores
  4. Muestra 4: Popup de información al hacer clic en un marcador
  5. Muestra 5: Respuesta JSON de la API (/api/bibliotecas/)
  6. Muestra 6: Google Maps abierto desde el enlace (pestaña nueva)
  7. Muestra 7: Consola de Django mostrando el servidor corriendo sin errores

📝 Incluye también:

  • Archivo requirements.txt con todas las dependencias
  • Código de models.py, services.py, admin.py
  • Breve reporte explicando qué hace cada componente

7. 🔥 Funcionalidades Avanzadas

7.1 Búsqueda de Lugares Cercanos

libros/services.py - Búsqueda de lugares
@classmethod
def buscar_lugares_cercanos(cls, lat, lng, tipo='library', radio=5000):
    """
    Busca lugares cercanos a una ubicación
    
    Args:
        lat: Latitud
        lng: Longitud
        tipo: Tipo de lugar (library, restaurant, school, etc.)
        radio: Radio de búsqueda en metros
    """
    try:
        service = cls()
        resultado = service.client.places_nearby(
            location=(float(lat), float(lng)),  # Ubicación central
            radius=radio,  # Radio en metros
            type=tipo  # Tipo de lugar
        )
        
        lugares = []
        for place in resultado.get('results', []):
            lugares.append({
                'nombre': place.get('name'),
                'direccion': place.get('vicinity'),
                'rating': place.get('rating', 0),
                'place_id': place.get('place_id'),
                'lat': place['geometry']['location']['lat'],
                'lng': place['geometry']['location']['lng']
            })
        
        return lugares
        
    except Exception as e:
        logger.error(f"Error buscando lugares: {e}")
        return []

7.2 Calcular Ruta entre Dos Puntos

JavaScript - Mostrar ruta en el mapa
function mostrarRuta(origen, destino) {
    // Crear servicio de direcciones
    const directionsService = new google.maps.DirectionsService();
    const directionsRenderer = new google.maps.DirectionsRenderer();
    
    directionsRenderer.setMap(map);  // Asocia al mapa
    
    // Solicitar ruta
    directionsService.route({
        origin: origen,  // Punto de inicio
        destination: destino,  // Punto de destino
        travelMode: google.maps.TravelMode.DRIVING  // Modo: DRIVING, WALKING, BICYCLING, TRANSIT
    }, (response, status) => {
        if (status === 'OK') {
            directionsRenderer.setDirections(response);  // Dibuja la ruta
            
            // Obtener información de la ruta
            const route = response.routes[0].legs[0];
            console.log(`Distancia: ${route.distance.text}`);  // Muestra distancia
            console.log(`Duración: ${route.duration.text}`);  // Muestra tiempo
        } else {
            console.error('Error calculando ruta:', status);
        }
    });
}

8. 🛡️ Mejores Prácticas y Seguridad

🔐 Seguridad de API Keys

❌ NUNCA hagas esto:

  1. Subir API Keys a repositorios públicos (GitHub, GitLab)
  2. Hardcodear API Keys directamente en código JavaScript del cliente
  3. Compartir API Keys en foros, chats o correos electrónicos
  4. Usar la misma API Key para desarrollo y producción
  5. Dejar API Keys sin restricciones de uso

✅ SÍ haz esto:

  1. Variables de Entorno:
    settings.py (Opción Segura)
    import os
    from decouple import config  # pip install python-decouple
    
    # Leer de variable de entorno o archivo .env
    GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY', default='')
    
    # Verificar que esté configurada
    if not GOOGLE_MAPS_API_KEY:
        raise ValueError("GOOGLE_MAPS_API_KEY no configurada")
    .env (en raíz del proyecto)
    GOOGLE_MAPS_API_KEY=AIzaSyD1234567890abcdefghijklmnopqrstuvwxyz
    DEBUG=True
    SECRET_KEY=django-insecure-tu-secret-key-aqui
    .gitignore (IMPORTANTE)
    # Archivos a NO subir a GitHub
    .env
    *.pyc
    __pycache__/
    venv/
    db.sqlite3
    /staticfiles/
  2. Restricciones de API Key en Google Cloud:
    • Ve a Google Cloud Console → Credenciales
    • Clic en tu API Key → Editar
    • Restricciones de aplicación:
      • Sitios web: Restringir por dominio (tuapp.com/*)
      • Direcciones IP: Solo desde tu servidor (123.45.67.89)
    • Restricciones de API: Selecciona solo las APIs que uses
      • ✓ Geocoding API
      • ✓ Maps JavaScript API
      • ✗ (Desmarcar las demás)
  3. Proxy Backend para API Keys:
    Patrón Proxy (Seguro)
    # NUNCA exponer API Key en frontend JavaScript
    # En lugar de:
    # ❌ <script src="...maps/api/js?key=AIza..."></script>
    
    # Usar proxy en backend:
    # ✅ Frontend llama a tu API → Django llama a Google Maps
    
    # libros/views.py
    @api_view(['POST'])
    def geocodificar_proxy(request):
        direccion = request.data.get('direccion')
        # API Key está en settings.py (servidor), no en JavaScript
        resultado = GoogleMapsService.geocodificar_direccion(direccion)
        return Response(resultado)

🚀 Optimización de Rendimiento

1. Caché de Resultados de Geocodificación

models.py - Evitar Geocodificaciones Duplicadas
def save(self, *args, **kwargs):
    """Solo geocodificar si la dirección cambió"""
    if self.pk:  # Si ya existe (actualización)
        old = Biblioteca.objects.get(pk=self.pk)
        # Verificar si cambió la dirección
        direccion_cambio = (
            old.direccion != self.direccion or
            old.ciudad != self.ciudad or
            old.pais != self.pais
        )
        if direccion_cambio:
            self.geocodificar()  # Solo si cambió
    else:  # Nuevo registro
        self.geocodificar()
    
    super().save(*args, **kwargs)

2. Índices en Base de Datos

models.py - Índices para Búsquedas Rápidas
class Biblioteca(models.Model):
    nombre = models.CharField(max_length=200, db_index=True)  # Índice
    ciudad = models.CharField(max_length=100, db_index=True)  # Índice
    latitud = models.DecimalField(max_digits=10, decimal_places=7)
    longitud = models.DecimalField(max_digits=10, decimal_places=7)
    
    class Meta:
        # Índice compuesto para búsquedas geoespaciales
        indexes = [
            models.Index(fields=['latitud', 'longitud']),
            models.Index(fields=['ciudad', 'nombre']),
        ]

3. Paginación en API

settings.py - Limitar Resultados
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 50  # Máximo 50 bibliotecas por petición
}

4. Lazy Loading de Mapas

JavaScript - Cargar Solo Cuando Sea Necesario
// Solo cargar marcadores visibles en el viewport
function mostrarMarcadoresVisibles() {
    const bounds = map.getBounds();
    
    bibliotecas.forEach(bib => {
        const posicion = { lat: bib.latitud, lng: bib.longitud };
        
        // Solo crear marcador si está en el viewport actual
        if (bounds.contains(posicion)) {
            crearMarcador(bib);
        }
    });
}

📊 Monitoreo y Logging

services.py - Logging de Peticiones
import logging
logger = logging.getLogger(__name__)

@classmethod
def geocodificar_direccion(cls, direccion):
    logger.info(f"Geocodificando: {direccion}")
    
    try:
        service = cls()
        resultado = service.client.geocode(direccion)
        
        if resultado:
            logger.info(f"✅ Geocodificación exitosa: {direccion}")
            return {...}
        else:
            logger.warning(f"⚠️ No se encontró: {direccion}")
            return None
            
    except Exception as e:
        logger.error(f"❌ Error: {direccion} - {str(e)}")
        return None

9. 💡 Casos de Uso Avanzados

1. Búsqueda de Bibliotecas Cercanas con Radio

Objetivo: Encontrar bibliotecas dentro de un radio específico desde la ubicación del usuario.

libros/services.py
from math import radians, cos, sin, asin, sqrt

@classmethod
def buscar_cercanas(cls, lat_usuario, lng_usuario, radio_km=5):
    """
    Busca bibliotecas dentro de un radio específico
    
    Args:
        lat_usuario: Latitud del usuario
        lng_usuario: Longitud del usuario
        radio_km: Radio de búsqueda en kilómetros
    
    Returns:
        Lista de bibliotecas ordenadas por distancia
    """
    from .models import Biblioteca
    
    # Obtener todas las bibliotecas con coordenadas
    bibliotecas = Biblioteca.objects.filter(
        latitud__isnull=False,
        longitud__isnull=False
    )
    
    resultados = []
    
    for bib in bibliotecas:
        # Calcular distancia usando Haversine
        distancia = cls.calcular_distancia_haversine(
            lat_usuario, lng_usuario,
            float(bib.latitud), float(bib.longitud)
        )
        
        # Solo incluir si está dentro del radio
        if distancia <= radio_km:
            resultados.append({
                'biblioteca': bib,
                'distancia_km': round(distancia, 2)
            })
    
    # Ordenar por distancia (más cercano primero)
    resultados.sort(key=lambda x: x['distancia_km'])
    
    return resultados

@staticmethod
def calcular_distancia_haversine(lat1, lon1, lat2, lon2):
    """Calcula distancia entre dos coordenadas (fórmula Haversine)"""
    R = 6371  # Radio de la Tierra en km
    
    # Convertir a radianes
    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
    
    # Diferencias
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    
    # Fórmula Haversine
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a))
    
    return R * c  # Distancia en km

2. Ruta Optimizada entre Múltiples Bibliotecas

Objetivo: Calcular la ruta más eficiente para visitar varias bibliotecas.

libros/services.py
@classmethod
def calcular_ruta_optimizada(cls, ids_bibliotecas):
    """Calcula ruta óptima usando Google Directions API"""
    from .models import Biblioteca
    
    # Obtener bibliotecas
    bibliotecas = Biblioteca.objects.filter(id__in=ids_bibliotecas)
    
    if len(bibliotecas) < 2:
        return None
    
    service = cls()
    
    # Crear lista de waypoints
    waypoints = [
        (float(bib.latitud), float(bib.longitud)) 
        for bib in bibliotecas
    ]
    
    # Calcular ruta optimizada
    ruta = service.client.directions(
        origin=waypoints[0],  # Inicio
        destination=waypoints[-1],  # Fin
        waypoints=waypoints[1:-1],  # Puntos intermedios
        optimize_waypoints=True,  # ⭐ Optimizar orden
        mode='driving'
    )
    
    return ruta

3. Agrupación de Bibliotecas (Clustering)

Objetivo: Agrupar bibliotecas cercanas para mejorar rendimiento del mapa.

JavaScript - MarkerClusterer
// Cargar biblioteca de clustering
// <script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>

let markerCluster;

function crearCluster() {
    // Agrupar marcadores cercanos
    markerCluster = new markerClusterer.MarkerClusterer({
        map,
        markers,
        // Configuración del cluster
        gridSize: 50,  // Tamaño de la cuadrícula
        maxZoom: 15,  // Zoom máximo antes de separar
        algorithm: new markerClusterer.SuperClusterAlgorithm({
            radius: 100  // Radio de agrupación
        })
    });
}

10. 📚 Recursos Adicionales y Referencias

🎓 Tutoriales y Cursos Recomendados

  • Google Maps Platform Training: Cursos gratuitos de Google
  • Django for Beginners: djangoforbeginners.com
  • MDN Web Docs - Geolocation API: developer.mozilla.org
  • Real Python - Django Tutorials: realpython.com/tutorials/django/

🛠️ Herramientas Útiles

Herramienta Descripción URL
Postman Prueba de APIs REST postman.com
DB Browser for SQLite Explorar bases de datos sqlitebrowser.org
VS Code Extensions Python, Django, REST Client marketplace.visualstudio.com
Google Maps Styling Wizard Personalizar colores del mapa mapstyle.withgoogle.com
JSON Formatter Formatear y validar JSON jsonformatter.org

💼 Proyectos de Ejemplo para Inspiración

  1. Sistema de Delivery con Rutas Optimizadas
    • Geocodificar direcciones de clientes
    • Calcular rutas de entrega
    • Estimación de tiempos
  2. Buscador de Servicios Públicos
    • Hospitales, escuelas, parques
    • Filtrado por distancia
    • Comparación de horarios
  3. App de Eventos Locales
    • Mapa de eventos cercanos
    • Notificaciones geográficas
    • Compra de tickets integrada
  4. Sistema de Rastreo de Vehículos
    • Tracking en tiempo real
    • Historial de rutas
    • Alertas de geofencing

8. 📖 Glosario de Términos

Términos Técnicos de Geolocalización

API (Application Programming Interface):

Conjunto de funciones y procedimientos que permite la comunicación entre dos aplicaciones de software.

Coordenadas Geográficas:

Par de números (latitud, longitud) que identifica una ubicación exacta en la Tierra.

Geocodificación (Geocoding):

Proceso de convertir una dirección postal en coordenadas geográficas (lat, lng).

Geocodificación Inversa (Reverse Geocoding):

Proceso opuesto: convertir coordenadas geográficas en una dirección postal legible.

GPS (Global Positioning System):

Sistema de navegación por satélite que proporciona información de ubicación y tiempo.

Latitud:

Distancia angular medida en grados desde el ecuador (0°) hacia los polos (-90° a +90°).

Longitud:

Distancia angular medida en grados desde el meridiano de Greenwich (0°) (-180° a +180°).

Place ID:

Identificador único de Google Maps para un lugar específico en el mundo.

Marcador (Marker):

Icono o pin que se muestra en un mapa para indicar una ubicación específica.

InfoWindow:

Ventana emergente que muestra información adicional sobre un marcador en el mapa.

Distance Matrix API:

API que calcula distancias y tiempos de viaje entre múltiples orígenes y destinos.

Directions API:

API que proporciona direcciones de navegación entre ubicaciones.