Práctica de laboratorio · Independiente · ⏱ 90-120 min · 100% gratuito
Esta práctica es un laboratorio autocontenido: abres cuenta, configuras todo desde cero y terminas con 4 pruebas verificables que demuestran que tu app realmente lee y escribe en MySQL. Todos los nombres (repositorio, servicio, base de datos) se construyen con tus propios apellidos y nombre.
Cómo leer esta guía
Durante toda la práctica vas a nombrar repositorios, servicios y bases de datos. Para que el docente identifique tu trabajo de un vistazo, todo se nombra con tus apellidos y tu nombre, en ese orden, en minúsculas, sin espacios ni acentos.
Fórmula base
apellido-paterno-apellido-materno-nombre
Quita acentos y la letra ñ (cámbiala por "n"), usa solo tu primer nombre si tienes varios, y separa todo con guion medio.
| Recurso | Formato a usar | Ejemplo con alumno ficticio "Ana Gómez Pérez" |
|---|---|---|
| Repositorio en GitHub | apellidos-nombre-practica-mysql | gomez-perez-ana-practica-mysql |
| Web Service en Render | igual que el repositorio | gomez-perez-ana-practica-mysql |
| Servicio MySQL en Aiven | bd-apellidos-nombre | bd-gomez-perez-ana |
| Carpeta / proyecto Django | apellidos_nombre_practica (guion bajo) | gomez_perez_ana_practica |
| Nombre lógico de la base de datos | apellidos_nombre_bd (guion bajo) | gomez_perez_ana_bd |
Escribe aquí tu propio identificador antes de continuar:
[tu-apellido-paterno]-[tu-apellido-materno]-[tu-nombre]
De aquí en adelante, esta guía usa como ejemplo:
gomez-perez-ana
Cada vez que veas ese texto en un comando, reemplázalo por tu propio identificador.
Render es el hosting gratuito donde va a vivir tu aplicación. No pide tarjeta de crédito para el plan gratuito.
Paso 1.1 — Crear la cuenta
Paso 1.2 — Configurar tu perfil
Paso 1.3 — Entender el Dashboard
El panel principal de Render tiene 3 zonas que usarás constantemente en esta práctica:
"New +"
Botón para crear un Web Service, Static Site o Base de Datos nueva.
Lista de servicios
Cada proyecto que despliegues aparece aquí con un círculo de estado (gris = building, verde = live, rojo = failed).
Dentro de un servicio
Pestañas "Logs", "Environment", "Shell" y "Settings" — las usarás en los pasos de despliegue.
✅ Checkpoint de la sección Render
Tienes sesión iniciada en render.com, ligada a tu cuenta de GitHub, y ya identificas el botón "New +".
Render no ofrece MySQL gestionado gratis, por eso la base de datos vive en Aiven — un proveedor separado, también sin tarjeta de crédito, y tu app en Render se conecta a ella por internet.
Paso 2.1 — Crear la cuenta en Aiven
Paso 2.2 — Crear el servicio MySQL
Paso 2.3 — Copiar tus credenciales de conexión
Cuando el estado sea "Running", entra a la pestaña "Overview" y copia estos 5 datos a un lugar seguro (los necesitarás en el Paso 4):
| Host | algo así como mysql-xxxx.aivencloud.com |
| Port | un número de 5 dígitos, distinto para cada servicio |
| User | normalmente "avnadmin" |
| Password | cadena larga generada por Aiven |
| Database name | normalmente "defaultdb" |
Paso 2.4 — Verificar la conexión SSL
Aiven exige que toda conexión venga cifrada (SSL). En la pestaña "Overview" descarga o copia el certificado CA Certificate si tu conector lo requiere; con pymysql y la opción ssl-mode: REQUIRED (que verás en el Paso 9) es suficiente para esta práctica.
✅ Checkpoint de la sección MySQL
Tu servicio muestra estado "Running" y tienes los 5 datos de conexión copiados en un lugar seguro.
Un proyecto Django mínimo: una vista que consulta la hora del servidor MySQL y un modelo simple para probar que se pueden guardar datos reales.
Paso 3.1 — Crear carpeta, entorno virtual e instalar dependencias
mkdir gomez-perez-ana-practica-mysql
# Crea la carpeta raíz del proyecto, nombrada con tu identificador
cd gomez-perez-ana-practica-mysql
# Entra a esa carpeta
python -m venv venv
# Crea el entorno virtual aislado
# Windows (PowerShell):
venv\Scripts\Activate.ps1
# Ejecuta el script "activate" que vive dentro de venv; a partir de este comando, "python" y "pip" usan la copia aislada del proyecto
# Mac / Linux:
source venv/bin/activate
# "source" ejecuta ese mismo script activate dentro de la sesión actual de la terminal, activando el entorno aislado
pip install django pymysql python-decouple gunicorn whitenoise
# django = framework · pymysql = conector MySQL · python-decouple = lee el .env
# gunicorn = servidor de producción · whitenoise = sirve archivos estáticos en Render
Paso 3.2 — Crear el proyecto y la app (nombres con guion bajo)
django-admin startproject gomez_perez_ana_practica .
# Crea el proyecto Django; el "." final = "en esta misma carpeta"
python manage.py startapp conexion
# Crea la app "conexion", donde vivirá la vista y el modelo de prueba
Abre gomez_perez_ana_practica/settings.py y agrega tu app dentro de INSTALLED_APPS:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'conexion', # Registramos nuestra app para que Django la reconozca
]
Paso 3.3 — Guardar credenciales en .env (nunca en el código)
DB_NAME=defaultdb # Database name copiado de Aiven
DB_USER=avnadmin # User copiado de Aiven
DB_PASSWORD=tu-password # Password copiado de Aiven
DB_HOST=tu-host.aivencloud.com # Host copiado de Aiven
DB_PORT=12345 # Port copiado de Aiven
SECRET_KEY=escribe-una-cadena-larga-y-aleatoria
DEBUG=True # True solo mientras trabajas en tu computadora
IDENTIFICADOR=gomez-perez-ana # Tu identificador, usado dentro de la app para las pruebas
Crea también .gitignore:
.env
# Nunca subas tus credenciales reales a GitHub
venv/
# Tampoco subas la carpeta del entorno virtual
Paso 3.4 — Conectar Django a MySQL en settings.py
import pymysql
# Traemos el conector que traduce entre Django y MySQL
pymysql.install_as_MySQLdb()
# Hace que Django use pymysql como si fuera el driver oficial
from decouple import config
# Función para leer variables desde el archivo .env
SECRET_KEY = config('SECRET_KEY')
# La clave secreta se lee del .env, jamás escrita directo aquí
DEBUG = config('DEBUG', default=False, cast=bool)
# Convierte el texto "True"/"False" del .env en un booleano real de Python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # Motor: MySQL
'NAME': config('DB_NAME'), # Nombre de la base de datos
'USER': config('DB_USER'), # Usuario de conexión
'PASSWORD': config('DB_PASSWORD'), # Contraseña de conexión
'HOST': config('DB_HOST'), # Servidor donde vive la base de datos
'PORT': config('DB_PORT'), # Puerto de conexión
'OPTIONS': {'ssl': {'ssl-mode': 'REQUIRED'}}, # Aiven exige conexión cifrada
}
}
Paso 3.5 — Migrar por primera vez
python manage.py migrate
# Crea en MySQL las tablas internas de Django (usuarios, sesiones, permisos, etc.)
Paso 3.6 — Crear el modelo de prueba (models.py)
En conexion/models.py:
from django.db import models
# Herramientas de Django para describir tablas como clases de Python
class RegistroPrueba(models.Model):
# Cada clase que hereda de models.Model se convierte en una tabla en MySQL
identificador = models.CharField(max_length=100)
# Aquí guardamos tu identificador (apellidos-nombre) como dato de prueba
mensaje = models.CharField(max_length=200)
# Texto corto que describe la prueba realizada
creado_en = models.DateTimeField(auto_now_add=True)
# Django guarda automáticamente la fecha y hora del registro
def __str__(self):
# Define qué se muestra al "imprimir" este objeto (ej. en el panel admin)
return f"{self.identificador} - {self.mensaje}"
# f"..." arma un texto combinando los dos campos
En conexion/admin.py:
from django.contrib import admin
from .models import RegistroPrueba
admin.site.register(RegistroPrueba)
# Hace visible el modelo dentro del panel /admin
python manage.py makemigrations
# Prepara los cambios pendientes según el nuevo modelo
python manage.py migrate
# Crea la tabla "conexion_registroprueba" en tu base de datos MySQL real
python manage.py createsuperuser
# Crea tu usuario administrador para entrar a /admin
Paso 3.7 — Vista que prueba la conexión (views.py)
from django.db import connection
# connection = la conexión activa de Django hacia MySQL
from django.shortcuts import render
from decouple import config
from .models import RegistroPrueba
def probar_conexion(request):
identificador = config('IDENTIFICADOR')
# Leemos tu identificador desde el .env para usarlo en la prueba
try:
# try = "intenta hacer esto; si falla, no rompas la página, maneja el error"
with connection.cursor() as cursor:
# Abre un cursor: el objeto que ejecuta comandos SQL directos
cursor.execute("SELECT NOW();")
# Le pide a MySQL su fecha y hora actuales, la prueba más simple posible
hora_servidor = cursor.fetchone()[0]
# fetchone() trae el resultado; [0] toma el primer valor de esa fila
RegistroPrueba.objects.create(
identificador=identificador,
mensaje="Conexion exitosa desde probar_conexion()"
)
# Inserta un nuevo registro real en la tabla, probando también la ESCRITURA
contexto = {
'exito': True,
'hora_servidor': hora_servidor,
'identificador': identificador,
'registros': RegistroPrueba.objects.all().order_by('-creado_en')[:5],
# Trae los últimos 5 registros, del más nuevo al más viejo
}
except Exception as e:
# Si algo falla (credenciales mal escritas, base apagada, etc.) cae aquí
contexto = {'exito': False, 'error': str(e), 'identificador': identificador}
return render(request, 'conexion/resultado.html', contexto)
Paso 3.8 — Ruta y plantilla de resultado
Crea conexion/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.probar_conexion, name='probar_conexion'),
# La URL raíz "" ejecuta la vista que prueba la conexión
]
En gomez_perez_ana_practica/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('conexion.urls')),
# Cualquier URL en la raíz del sitio se busca dentro de conexion/urls.py
]
Crea la carpeta conexion/templates/conexion/ y adentro resultado.html:
<!DOCTYPE html>
<!-- Le dice al navegador "esto es HTML5 moderno", así lo interpreta con las reglas actuales -->
<html lang="es">
<!-- Abre el documento completo; lang="es" ayuda a lectores de pantalla y traductores automáticos a saber el idioma -->
<head>
<!-- Aquí va configuración que NO se ve directamente dibujada en la página -->
<meta charset="UTF-8">
<!-- Fija la codificación de caracteres; sin esto, tildes y la ñ pueden verse rotas -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Descarga Tailwind CSS desde internet cada vez que se abre la página; con esto ya puedes usar sus clases de diseño -->
<title>Prueba de conexión MySQL</title>
<!-- Texto que aparece en la pestaña del navegador -->
</head>
<body class="bg-slate-50 p-6 font-sans">
<!-- bg-slate-50 (CSS) = pinta de gris muy claro el fondo de TODA la página -->
<!-- p-6 (CSS) = padding de 1.5rem: deja un margen interno de aire en los 4 lados del body -->
<!-- font-sans (CSS) = usa la familia de letra "sans-serif" configurada al inicio del archivo (Inter) -->
<div class="max-w-xl mx-auto">
<!-- max-w-xl (CSS) = el contenido de adentro nunca crece más de ~36rem de ancho, para que sea legible en pantallas grandes -->
<!-- mx-auto (CSS) = centra este bloque horizontalmente (margen izquierdo y derecho calculados automáticamente) -->
<h1 class="text-xl font-bold mb-4">Práctica: {{ identificador }}</h1>
<!-- text-xl (CSS) = tamaño de letra grande · font-bold (CSS) = negritas · mb-4 (CSS) = margen inferior de aire -->
<!-- {{ identificador }} = imprime el valor que la vista probar_conexion() envió al contexto con ese mismo nombre -->
{% if exito %}
<!-- {% if %} es una instrucción de Django Template Language: SOLO dibuja lo de adentro cuando "exito" llegó como True -->
<div class="bg-emerald-50 border border-emerald-300 rounded-xl p-5">
<!-- bg-emerald-50 (CSS) = fondo verde muy claro · border border-emerald-300 (CSS) = borde delgado verde -->
<!-- rounded-xl (CSS) = esquinas bien redondeadas · p-5 (CSS) = relleno interno generoso -->
<p class="text-emerald-800 font-bold">✅ Conexión exitosa a MySQL</p>
<!-- text-emerald-800 (CSS) = texto verde oscuro, con buen contraste sobre el fondo claro de arriba -->
<p class="text-emerald-700 text-sm mt-1">Hora del servidor: {{ hora_servidor }}</p>
<!-- text-sm (CSS) = letra más pequeña que el párrafo anterior · mt-1 (CSS) = separación arriba -->
<!-- {{ hora_servidor }} = imprime el valor que trajo cursor.fetchone() en views.py -->
</div>
<h2 class="font-bold mt-6 mb-2">Últimos registros guardados</h2>
<!-- mt-6 (CSS) = separa este título del bloque verde de arriba · mb-2 (CSS) = un poco de aire antes de la lista -->
<ul class="space-y-2">
<!-- space-y-2 (CSS) = agrega separación vertical automática entre cada <li> hijo, sin escribir margen a mano en cada uno -->
{% for r in registros %}
<!-- {% for %} repite todo lo de adentro una vez por cada elemento de la lista "registros" que envió la vista -->
<!-- en cada vuelta del ciclo, la variable "r" representa UN registro distinto de la base de datos -->
<li class="bg-white border rounded-lg p-3 text-sm">
<!-- bg-white (CSS) = fondo blanco, para que cada tarjeta resalte sobre el fondo gris del body -->
<strong>{{ r.identificador }}</strong> — {{ r.mensaje }}
<!-- <strong> pone en negritas el identificador; luego imprime el mensaje de ESTE registro -->
<span class="text-slate-400">({{ r.creado_en }})</span>
<!-- text-slate-400 (CSS) = gris tenue, para que la fecha se lea como un dato secundario -->
</li>
{% endfor %}
<!-- {% endfor %} cierra de forma obligatoria el bucle {% for %} que abriste arriba -->
</ul>
{% else %}
<!-- {% else %} se ejecuta SOLO cuando "exito" llegó como False desde la vista (hubo un error de conexión) -->
<div class="bg-rose-50 border border-rose-300 rounded-xl p-5">
<!-- Misma estructura de tarjeta que la verde de arriba, pero en tonos rosa/rojo (bg-rose-50, border-rose-300) para comunicar error -->
<p class="text-rose-800 font-bold">❌ No se pudo conectar</p>
<p class="text-rose-700 text-sm mt-1 font-mono">{{ error }}</p>
<!-- font-mono (CSS) = muestra el texto del error con fuente de código, más fácil de leer para un mensaje técnico -->
<!-- {{ error }} = imprime el texto que guardamos con str(e) en el bloque except de views.py -->
</div>
{% endif %}
<!-- {% endif %} cierra de forma obligatoria el bloque {% if %} / {% else %} que abriste arriba -->
</div>
</body>
</html>
Paso 3.9 — Probar en local
python manage.py runserver
# Levanta el servidor local
Abre http://127.0.0.1:8000/ en tu navegador.
Subes tu código a GitHub y le indicas a Render cómo instalarlo y ejecutarlo — con el mismo identificador que ya definiste.
Paso 4.1 — Generar requirements.txt y subir a GitHub
pip freeze > requirements.txt
# pip freeze = lista, con su versión exacta, todo lo que instalaste dentro del entorno virtual
# > requirements.txt = en vez de mostrar esa lista en pantalla, la redirige y la guarda dentro de ese archivo
git init
# Convierte esta carpeta en un repositorio Git: a partir de aquí, Git empieza a "vigilar" los cambios de tus archivos
git add .
# Marca TODOS los archivos de la carpeta como "listos para guardar" (staging); el "." significa "aquí y en las subcarpetas"
# Respeta lo que hayas puesto en .gitignore, así que venv/ y .env NO se agregan
git commit -m "Practica: conexion Django a MySQL en la nube"
# Guarda una "fotografía" fija de todos los archivos marcados con git add, formando un punto en tu historial
# -m = "message": el texto entre comillas es obligatorio y debe describir qué cambió en este avance
git branch -M main
# Renombra tu rama de trabajo actual a "main" (-M fuerza el renombrado aunque ya tenga otro nombre)
# "main" es el nombre estándar que usa GitHub para la rama principal de un repositorio
git remote add origin https://github.com/TU-USUARIO/gomez-perez-ana-practica-mysql.git
# git remote add = registra una dirección en internet donde vivirá una copia de este repositorio
# origin = el apodo con el que Git identificará esa dirección de aquí en adelante (nombre estándar por convención)
# Reemplaza TU-USUARIO por tu usuario real de GitHub, y el nombre del repositorio por tu propio identificador
git push -u origin main
# git push = envía (sube) los commits que ya guardaste en tu computadora hacia el remoto llamado "origin"
# origin main = súbelo específicamente a la rama "main" de ese remoto
# -u (--set-upstream) = deja conectada tu rama local "main" con la remota; los próximos "git push" ya no necesitarán repetir "origin main"
gomez-perez-ana-practica-mysql (con tu propio identificador), en "Public".Paso 4.2 — Crear el Web Service en Render
Paso 4.3 — Variables de entorno en Render
Pestaña "Environment" → "Add Environment Variable", una por una:
DB_NAME=defaultdb
DB_USER=avnadmin
DB_PASSWORD=tu-password
DB_HOST=tu-host.aivencloud.com
DB_PORT=12345
SECRET_KEY=la-misma-clave-secreta
DEBUG=False
# En producción siempre False
ALLOWED_HOSTS=gomez-perez-ana-practica-mysql.onrender.com
# Debe coincidir exactamente con el dominio que Render te asignó
IDENTIFICADOR=gomez-perez-ana
Agrega en settings.py, si no existe ya:
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='').split(',')
# Lee la variable y la separa por comas si hubiera más de un dominio
Paso 4.4 — Desplegar y migrar en la nube
Guarda las variables → Render construye automáticamente. Cuando el círculo de estado esté verde ("Live"), abre la pestaña "Shell":
python manage.py migrate
# Crea las tablas en MySQL desde el servidor de Render (misma base de Aiven)
python manage.py createsuperuser
# Crea tu usuario administrador ya en producción
https://gomez-perez-ana-practica-mysql.onrender.com/ — debes ver la misma tarjeta verde que en local, ahora accesible desde cualquier dispositivo del mundo.Cierre de la práctica
Marca cada prueba solo cuando obtengas el resultado esperado. Esto es lo que se revisa en la defensa oral de esta práctica.
Si alguna prueba falla
DB_* en Render sean idénticas a las de tu .env local.python manage.py migrate también en la Shell de Render, no solo en tu computadora.