03/08
Primer proyecto Django y conexión a MySQL remoto
Hasta ahora hiciste solo front-end: HTML, CSS y Tailwind, todo corriendo en el navegador. Esta semana entras al back-end con Django, un framework de Python, y conectas tu primer proyecto a una base de datos MySQL real, gestionada en la nube por Aiven —no una base de datos local que solo tú puedes ver, sino una que también va a usar tu app cuando esté publicada en Render.
.env.example (sin contraseñas reales) y capturas de pantalla de migrate corriendo sin errores y de las tablas creadas en el panel de Aiven.
Arquitectura MTV de Django vs. el mito "Django = PHP con esteroides"
Django organiza el código en tres piezas que se hablan entre sí de forma ordenada. No es "HTML con código de servidor mezclado adentro" como suele hacerse en PHP plano — es Python separado en capas con responsabilidades claras.
Model (Modelo)
Una clase de Python que describe una tabla de la base de datos. Vas a crear tu primer modelo real en la Semana 4; por ahora, Django ya trae varios modelos internos (usuarios, sesiones) que vas a migrar hoy.
Template (Plantilla)
Un archivo .html con pequeñas marcas especiales ({{ variable }}) que Django rellena con datos antes de enviarlo al navegador. Es el HTML que ya conoces, con superpoderes para insertar información dinámica.
View (Vista)
Una función de Python que recibe la petición del navegador, decide qué datos necesita (a veces consultando un Model) y elige qué Template devolver como respuesta. Es el "pegamento" entre las otras dos piezas.
Cómo viaja una petición, en la práctica
Así se conecta cada pieza cuando alguien visita una URL de tu sitio. Vas a construir estos mismos cuatro archivos en el paso 05.
# manage.py: herramienta de línea de comandos del proyecto (runserver, migrate, etc.)
manage.py
# miproyecto/: paquete de configuración global, mismo nombre que el proyecto
miproyecto/
# settings.py: toda la configuración — apps instaladas, base de datos, zona horaria
settings.py
# urls.py: tabla de rutas raíz — decide a qué app le pasa cada URL
urls.py
# core/: tu primera "app", una unidad de funcionalidad dentro del proyecto
core/
# views.py: las funciones que reciben la petición y eligen qué responder — la V
views.py
# models.py: las clases que se convierten en tablas de MySQL — la M
models.py
# urls.py: tabla de rutas propia de esta app
urls.py
# templates/core/: los archivos .html que arma la respuesta final — la T
templates/core/
El recorrido real es: el navegador pide una URL → urls.py del proyecto la deriva a urls.py de la app → esa ruta apunta a una función en views.py → la función arma los datos (a veces desde un Model) y elige un archivo de templates/ → Django devuelve ese HTML ya relleno.
Django no es "PHP con esteroides": en PHP clásico el código de servidor y el HTML suelen convivir en el mismo archivo .php. En Django, Python (views/models) y HTML (templates) están en archivos separados a propósito — así un cambio de diseño no obliga a tocar lógica de negocio, y viceversa.
Entorno virtual, instalar Django + PyMySQL, primer proyecto
Un entorno virtual es una copia aislada de Python solo para este proyecto: lo que instalas aquí no se mezcla con otros proyectos ni con el Python del sistema operativo.
Crear la carpeta del proyecto y el entorno virtual
# Crea una carpeta nueva para este proyecto
mkdir miproyecto-django
# Entra a esa carpeta (todo lo que sigue se hace dentro de ella)
cd miproyecto-django
# Crea el entorno virtual: una carpeta "venv" con una copia propia de Python
python -m venv venv
# Activa el entorno virtual — Windows (PowerShell, el que usa VS Code por defecto); debe aparecer "(venv)" al inicio de la línea
venv\Scripts\Activate.ps1
# Solo si el comando anterior dice "la ejecución de scripts está deshabilitada": permite scripts en esta terminal y vuelve a activar
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# Activa el entorno virtual — macOS / Linux (bash o zsh); en Windows NO lo escribas
source venv/bin/activate
Si activó bien, vas a ver (venv) al principio de la línea de la terminal. A partir de ahora, todo lo que instales con pip queda solo dentro de esta carpeta.
Instalar Django y el conector de MySQL
# django: el framework · pymysql: el traductor entre Django y una base de datos MySQL
pip install django pymysql
Crear el proyecto Django
# startproject genera la estructura base · "miproyecto": nombre del paquete de configuración
# el "." final evita crear una subcarpeta extra: los archivos quedan directo en miproyecto-django/
django-admin startproject miproyecto .
Confirmar que Django arranca
# Levanta el servidor de desarrollo local, todavía con la base SQLite que trae Django por defecto (se detiene con Ctrl + C)
python manage.py runserver
Abre http://127.0.0.1:8000/ en el navegador: deberías ver el cohete de bienvenida de Django. Déjalo corriendo y abre una segunda terminal para los pasos siguientes (o detén el servidor con Ctrl+C cuando lo necesites).
Si venv\Scripts\Activate.ps1 da un error de "ejecución de scripts deshabilitada", corre una vez Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass en esa misma terminal de PowerShell y vuelve a intentar.
Crear la base de datos MySQL gratuita en Aiven
Aiven ofrece un plan gratuito de MySQL gestionado: no instalas ni administras ningún servidor, solo pides el servicio y te dan las credenciales de conexión.
Crear la cuenta
Entra a aiven.io → botón de registro → elige continuar con GitHub, Google o correo. No pide tarjeta de crédito para el plan gratuito.
Crear el servicio MySQL
Dashboard → "Create service" → elige MySQL → plan "Free" → una región cercana a ti → pon un nombre (ej. curso-aplicaweb) → "Create service".
Esperar el aprovisionamiento
El servicio pasa de estado "Rebuilding" a "Running" en uno o dos minutos. Cuando esté en verde, ya puedes copiar las credenciales.
Copiar las 5 credenciales de conexión
En la pestaña "Overview" del servicio, sección "Connection information", vas a necesitar exactamente estos cinco valores para el paso siguiente:
| Dato en Aiven | Variable que vas a usar |
|---|---|
| Host | DB_HOST |
| Port | DB_PORT |
| User | DB_USER |
| Password | DB_PASSWORD |
| Default database name | DB_NAME |
Estos cinco valores son secretos. Nunca los escribas directamente en settings.py ni los subas a GitHub — en el paso 04 van a vivir en un archivo .env que no se sube al repositorio.
Configurar settings.py con variables de entorno + migrate contra MySQL real
Django viene configurado para usar SQLite (un archivo local) por defecto. Lo vas a apuntar a tu base MySQL de Aiven, leyendo las credenciales desde un archivo .env que nunca se sube a GitHub.
Instalar el lector de variables de entorno
# python-decouple: lee valores desde un archivo .env en vez de escribirlos directo en el código
pip install python-decouple
Crear .env (secreto, nunca se sube)
En la raíz del proyecto (junto a manage.py), crea un archivo llamado exactamente .env:
# Reemplaza cada valor por el que copiaste de Aiven en el paso 03 (sin comillas y sin espacios alrededor del =)
# DB_NAME: nombre de la base de datos dentro de Aiven (casi siempre defaultdb)
DB_NAME=defaultdb
# DB_USER: usuario de MySQL que te da Aiven (casi siempre avnadmin)
DB_USER=avnadmin
# DB_PASSWORD: la contraseña real de Aiven (botón del ojo en Overview); nunca se sube a GitHub
DB_PASSWORD=tu-password-real-de-aiven
# DB_HOST: el servidor de Aiven; termina en aivencloud.com y va SIN https://
DB_HOST=tu-host-real.aivencloud.com
# DB_PORT: el puerto de Aiven (un número de 5 cifras, NO 3306)
DB_PORT=12345
Crear .env.example (seguro, sí se sube)
Mismo nombre de variables, pero con valores de relleno — así cualquiera que clone el repo sabe qué variables necesita, sin ver tu contraseña real. Este es el archivo que vas a entregar.
# Mismas claves que .env, con valores de ejemplo en vez de los reales
# DB_NAME: nombre de la base (ejemplo)
DB_NAME=defaultdb
# DB_USER: usuario de MySQL (ejemplo)
DB_USER=tu-usuario
# DB_PASSWORD: contraseña de relleno; la real solo va en .env
DB_PASSWORD=tu-password
# DB_HOST: servidor de ejemplo
DB_HOST=tu-host.aivencloud.com
# DB_PORT: puerto de ejemplo
DB_PORT=12345
Decirle a Git que ignore .env
# venv/: no se sube el entorno virtual completo, cada quien crea el suyo con requirements.txt
venv/
# .env: el archivo con tus contraseñas reales, nunca debe llegar a GitHub
.env
# __pycache__/: archivos compilados que Python genera solo, no son código fuente
__pycache__/
Editar settings.py
Reemplaza el bloque DATABASES que trae Django por defecto por este, y agrega las dos primeras líneas arriba del todo del archivo:
# pymysql: el conector que instalaste en el paso 02
import pymysql
# Hace que Django use pymysql como si fuera el driver oficial de MySQL para Python
pymysql.install_as_MySQLdb()
# config: función de python-decouple que lee valores del archivo .env
from decouple import config
# (más abajo en el archivo, reemplaza el DATABASES que trae Django por defecto)
DATABASES = {
# 'default': la conexión principal; Django puede tener varias, esta es la que usa siempre
'default': {
# ENGINE: qué motor de base de datos usar — mysql en vez del sqlite3 por defecto
'ENGINE': 'django.db.backends.mysql',
# NAME: nombre de la base de datos, leído de la variable DB_NAME del .env
'NAME': config('DB_NAME'),
# USER: usuario de conexión, leído de DB_USER
'USER': config('DB_USER'),
# PASSWORD: contraseña de conexión, leída de DB_PASSWORD
'PASSWORD': config('DB_PASSWORD'),
# HOST: servidor donde vive la base de datos, leído de DB_HOST
'HOST': config('DB_HOST'),
# PORT: puerto de conexión; default='3306' se usa solo si DB_PORT no está en el .env
'PORT': config('DB_PORT', default='3306'),
# OPTIONS: Aiven exige que la conexión venga cifrada (SSL)
'OPTIONS': {'ssl': {'ssl-mode': 'REQUIRED'}},
# cierre del diccionario de configuración de la conexión
}
# cierre del diccionario DATABASES
}
Migrar contra MySQL real
# Crea en tu base MySQL las tablas internas de Django: usuarios, sesiones, permisos, tipos de contenido
python manage.py migrate
Si termina sin errores y ves varias líneas Applying ... OK, la conexión funciona. Saca una captura de esta salida — es parte de tu entrega.
Verificar en el panel de Aiven
En Aiven, pestaña "Current status" o una herramienta de consulta (ej. MySQL Workbench, DBeaver, o la consola web si Aiven la ofrece) → deberías ver tablas como django_migrations, auth_user, django_session. Saca una captura — es la segunda evidencia de tu entrega.
Si migrate falla con un error de conexión, revisa primero que copiaste bien las 5 variables del .env (sin espacios de más) y que el servicio en Aiven ya esté en estado "Running".
Primera vista, ruta y plantilla: "Hola desde MySQL"
Vas a construir los cuatro archivos del diagrama del paso 01: app, vista, ruta y plantilla — y vas a mostrar en pantalla datos que confirman que Django está hablando con tu MySQL real, no con SQLite.
Crear la app
# startapp genera la carpeta "core" con views.py, models.py, etc.
python manage.py startapp core
Registrar la app en settings.py
# INSTALLED_APPS: lista de apps que forman tu proyecto; Django solo "ve" las que están aquí
INSTALLED_APPS = [
# admin: el panel /admin
'django.contrib.admin',
# auth: usuarios, contraseñas y permisos
'django.contrib.auth',
# contenttypes: registro interno de los modelos (lo usan admin y auth)
'django.contrib.contenttypes',
# sessions: recuerda quién inició sesión entre una página y otra
'django.contrib.sessions',
# messages: mensajes de una sola vez (ej. "guardado con éxito")
'django.contrib.messages',
# staticfiles: maneja los archivos CSS, JS e imágenes
'django.contrib.staticfiles',
# 'core': tu app nueva, agregada al final de la lista
'core',
# cierre de la lista INSTALLED_APPS
]
Escribir la vista
# render: función que combina una plantilla con datos y arma la respuesta HTTP
from django.shortcuts import render
# connection: objeto de Django que expone la configuración de la base de datos activa
from django.db import connection
# def: toda vista es una función que recibe "request" (la petición del navegador)
def inicio(request):
# settings_dict: diccionario interno con ENGINE, HOST, NAME, etc. de la conexión activa
info_bd = connection.settings_dict
# contexto: los datos que la plantilla va a poder usar con {{ variable }}
contexto = {
# motor: el tipo de base de datos (debe decir mysql)
'motor': info_bd['ENGINE'],
# host: el servidor de la base (en Aiven termina en aivencloud.com)
'host': info_bd['HOST'],
# cierre del diccionario contexto
}
# render: junta el template "core/inicio.html" con el contexto y devuelve el HTML final
return render(request, 'core/inicio.html', contexto)
Crear la ruta de la app
Crea un archivo nuevo core/urls.py (no existe todavía, startapp no lo genera):
# path: función para definir una ruta individual
from django.urls import path
# views: el archivo que acabas de escribir, en la misma carpeta
from . import views
# urlpatterns: lista de rutas que esta app entiende
urlpatterns = [
# '': la URL vacía (la raíz de esta app) · views.inicio: la función que la atiende
# name='inicio': un alias interno para referirse a esta ruta sin escribir la URL a mano
path('', views.inicio, name='inicio'),
# cierre de la lista urlpatterns
]
Conectar la ruta de la app con el proyecto
# admin: ya viene importado por defecto, no lo borres
from django.contrib import admin
# path, include: include permite delegar un grupo de rutas a otra app
from django.urls import path, include
# urlpatterns: las rutas principales del proyecto completo
urlpatterns = [
# ruta del panel de administración, ya incluida por defecto
path('admin/', admin.site.urls),
# '': todo lo que llegue a la raíz del sitio se delega a core/urls.py
path('', include('core.urls')),
# cierre de la lista urlpatterns
]
Crear la carpeta y el archivo de la plantilla
Crea la ruta de carpetas core/templates/core/ (sí, "core" se repite: es la convención de Django para que dos apps distintas no confundan sus plantillas) y dentro el archivo inicio.html:
{# DOCTYPE + lang: igual que en tus HTML de las semanas 1 y 2 · en plantillas de Django los comentarios van con llaves y gato #}
<!DOCTYPE html>
{# lang="es": el contenido de la página está en español #}
<html lang="es">
{# head: datos de la página que no se ven #}
<head>
{# charset UTF-8: permite acentos y ñ #}
<meta charset="UTF-8">
{# title: el texto de la pestaña del navegador #}
<title>Hola desde MySQL</title>
{# cierra el head #}
</head>
{# body: lo que se ve en la página #}
<body>
{# h1: el título grande de la página #}
<h1>Hola desde MySQL</h1>
{# motor: Django reemplaza esa variable por el valor de contexto['motor'] que armó la vista #}
<p>Motor de base de datos: {{ motor }}</p>
{# host: reemplazado por contexto['host']; si dice "aivencloud.com" es MySQL real #}
<p>Host: {{ host }}</p>
{# cierra el body #}
</body>
{# cierra el documento HTML #}
</html>
En las plantillas de Django los comentarios se escriben {# … #}, no <!-- … -->. Django lee las etiquetas {% %} y {{ }} aunque estén dentro de <!-- -->: un comentario que diga <!-- {% for servicio in servicios %} --> abre un segundo ciclo y la tabla repite las filas. Lo que va entre {# #} Django lo borra antes de enviar la página. Cada comentario {# #} debe caber en una sola línea.
Probarlo
# Arranca el servidor y abre http://127.0.0.1:8000 en el navegador (se detiene con Ctrl + C)
python manage.py runserver
Abre http://127.0.0.1:8000/: deberías ver "Hola desde MySQL" y, debajo, django.db.backends.mysql como motor y el host de Aiven —la prueba visual de que ya no estás usando SQLite.
Práctica autónoma — segunda vista con datos en una tabla Tailwind
Todavía sin modelos (eso es la Semana 4): la vista va a definir una lista de datos directamente en Python y la plantilla la va a recorrer con la etiqueta {% for %} de Django, dentro de una tabla con Tailwind.
Agregar la segunda vista
# nueva función de vista, además de "inicio" que ya tenías
def servicios(request):
# lista_servicios: datos de ejemplo en Python — todavía no vienen de MySQL
lista_servicios = [
# cada servicio es un diccionario con su nombre y su precio
{'nombre': 'Tutorías de programación', 'precio': 150},
# segundo servicio de ejemplo
{'nombre': 'Diseño de logotipos', 'precio': 300},
# tercer servicio de ejemplo
{'nombre': 'Repostería por encargo', 'precio': 120},
# cierre de la lista
]
# se la pasamos a la plantilla con la clave 'servicios'
return render(request, 'core/servicios.html', {'servicios': lista_servicios})
Agregar la ruta
# path: función para definir una ruta individual
from django.urls import path
# views: el archivo de vistas de esta misma app
from . import views
# urlpatterns: lista de rutas que esta app entiende
urlpatterns = [
# '': la página de inicio que ya tenías
path('', views.inicio, name='inicio'),
# nueva ruta 'servicios/' → función views.servicios → alias 'servicios'
path('servicios/', views.servicios, name='servicios'),
# cierre de la lista urlpatterns
]
Crear la plantilla con tabla Tailwind
{# DOCTYPE: indica que es una página HTML5 #}
<!DOCTYPE html>
{# lang="es": el contenido de la página está en español #}
<html lang="es">
{# head: datos de la página que no se ven #}
<head>
{# charset UTF-8: permite acentos y ñ #}
<meta charset="UTF-8">
{# title: el texto de la pestaña del navegador #}
<title>Servicios</title>
{# mismo Play CDN de Tailwind que usaste en la Semana 2 #}
<script src="https://cdn.tailwindcss.com"></script>
{# cierra el head #}
</head>
{# body: max-w-2xl + mx-auto centran el contenido; py-10 px-6 dan espacio arriba/abajo y a los lados #}
<body class="max-w-2xl mx-auto py-10 px-6">
{# h1: título grande (text-2xl), en negritas (font-bold) y con espacio abajo (mb-6) #}
<h1 class="text-2xl font-bold mb-6">Servicios</h1>
{# table w-full: la tabla ocupa todo el ancho del contenedor; border dibuja el contorno gris #}
<table class="w-full border border-slate-200 rounded-lg overflow-hidden">
{# thead: la fila de encabezados, con fondo gris claro #}
<thead class="bg-slate-100">
{# tr: la única fila del encabezado #}
<tr>
{# th: encabezado de la columna del nombre, alineado a la izquierda #}
<th class="text-left px-4 py-2">Servicio</th>
{# th: encabezado de la columna del precio #}
<th class="text-left px-4 py-2">Precio</th>
{# cierra la fila del encabezado #}
</tr>
{# cierra el encabezado #}
</thead>
{# tbody: el cuerpo de la tabla, donde van las filas de datos #}
<tbody>
{# for: repite la fila de abajo una vez por cada elemento de la lista #}
{% for servicio in servicios %}
{# tr: una fila por servicio; border-t dibuja la línea gris de arriba #}
<tr class="border-t border-slate-200">
{# servicio.nombre: accede a la clave 'nombre' del diccionario actual #}
<td class="px-4 py-2">{{ servicio.nombre }}</td>
{# servicio.precio: accede a la clave 'precio'; el $ es texto fijo antes del número #}
<td class="px-4 py-2">${{ servicio.precio }}</td>
{# cierra la fila de este servicio #}
</tr>
{# endfor: cierra el bucle, obligatorio en Django (a diferencia de Python) #}
{% endfor %}
{# cierra el cuerpo de la tabla #}
</tbody>
{# cierra la tabla #}
</table>
{# cierra el body #}
</body>
{# cierra el documento HTML #}
</html>
Abre http://127.0.0.1:8000/servicios/ y confirma que las tres filas aparecen en la tabla.
Fíjate que la lógica (los datos) vive en views.py y la presentación (la tabla, las clases de Tailwind) vive en el .html. Esa separación es exactamente el patrón MTV del paso 01, ya en código real.
Entregar y presentar
Marca cada punto a medida que lo completas — queda guardado en este navegador.
Cómo subir la práctica
Generar la lista de dependencias
# pip freeze lista todo lo instalado en el entorno virtual; > lo guarda en un archivo
pip freeze > requirements.txt
Sin este archivo, nadie más (ni Render en la Semana 4) puede recrear tu entorno con pip install -r requirements.txt.
Crear el repo e inicializar Git
Cada práctica va en su propio repositorio, con el nombre u2s3-apellidopaterno-apellidomaterno-nombres (minúsculas, sin acentos, ñ → n, espacios → guion). Ej.: u2s3-hernandez-nunez-maria-jose. Primero créalo vacío en GitHub:
La regla del nombre · siempre igual
- Prefijo de la práctica:
u+ número de unidad +s+ número de semana. Esta práctica esu2s3. - Todo en minúsculas, sin acentos (
á → a), lañse escribeny los espacios se cambian por guion-. - Orden fijo: prefijo → apellido paterno → apellido materno → nombre(s). Si tienes dos nombres, van los dos.
- Esta semana el nombre solo se usa en GitHub (Repository name). Desde la Semana 4, el mismo patrón se usa también como Name del servicio en Render.
- GitHub solo acepta letras sin acento, números,
-,_y.(máximo 100 caracteres). Por eso la regla quita acentos y espacios.
| Alumno | Nombre del repositorio de esta práctica |
|---|---|
| María José Hernández Núñez | u2s3-hernandez-nunez-maria-jose |
| José Ángel De la Cruz Peña | u2s3-de-la-cruz-pena-jose-angel |
| Ana López (un solo apellido) | u2s3-lopez-ana |
| Prefijo | Práctica del curso |
|---|---|
u1s1 | Unidad 1 · Semana 1 — Sitio estático en Render |
u1s2 | Unidad 1 · Semana 2 — Tailwind CSS |
u2s3 | Unidad 2 · Semana 3 — Django + MySQL en Aiven (esta guía) |
u2s4 | Unidad 2 · Semana 4 — Modelos y deploy en Render |
u3s5 | Unidad 3 · Semana 5 — CRUD con ModelForms |
u3s6 | Unidad 3 · Semana 6 — Autenticación y sesiones |
u4s7 | Unidad 4 · Semana 7 — Pulido y defensa |
u4s8 | Unidad 4 · Semana 8 — Deploy final |
Genera tu nombre sin errores
u2s3-hernandez-nunez-maria-joseTu repo quedará en https://github.com/TU_USUARIO/u2s3-hernandez-nunez-maria-jose. Desde la Semana 4, con el mismo patrón, tu sitio de Render quedará como https://u2s3-hernandez-nunez-maria-jose.onrender.com. Los datos de ejemplo están precargados: escribe los tuyos y usa «Copiar nombre».
- Entra a
https://github.comy confirma arriba a la derecha que es tu cuenta. - Clic en + → New repository. En Repository name pega el nombre que copiaste del generador de arriba (
u2s3-…). - Déjalo en Public y no marques «Add a README», «.gitignore» ni «license». Clic en Create repository y copia la URL que termina en
.git.
Después, en la terminal de VS Code (con (venv) activo y en la carpeta que tiene manage.py), pega estos comandos uno por uno. En git remote add pega tu URL:
# Convierte la carpeta en repositorio de Git (solo se hace una vez)
git init
# Prepara todos los archivos para el commit; gracias al .gitignore del paso 04, .env y venv/ NO quedan incluidos
git add .
# Guarda una "foto" de los archivos con un mensaje que explica qué hiciste
git commit -m "Primer proyecto Django conectado a MySQL en Aiven"
# Nombra la rama principal "main"
git branch -M main
# Conecta la carpeta con el repo VACÍO que creaste en GitHub; cambia TU_USUARIO y el nombre por los tuyos
git remote add origin https://github.com/TU_USUARIO/u2s3-hernandez-nunez-maria-jose.git
# Sube la rama main a GitHub y la deja vinculada (-u) para que después baste con "git push"
git push -u origin main
Escribir el README
<!-- título del repo: cambia por tus nombres reales -->
# U2S3 — [Apellido Paterno] [Apellido Materno] [Nombre(s)]
<!-- sección con los pasos para correr el proyecto en otra computadora -->
## Cómo correrlo localmente
<!-- paso 1: el entorno virtual -->
1. Crear y activar un entorno virtual
<!-- paso 2: instalar los paquetes de requirements.txt -->
2. pip install -r requirements.txt
<!-- paso 3: crear el .env a partir del ejemplo -->
3. Copiar .env.example a .env y completar con credenciales propias de Aiven
<!-- paso 4: crear las tablas en MySQL -->
4. python manage.py migrate
<!-- paso 5: arrancar el servidor -->
5. python manage.py runserver
<!-- sección con la evidencia de la práctica -->
## Evidencia
<!-- dónde están las capturas -->
Ver capturas de `migrate` y de las tablas en Aiven en la carpeta /evidencias.
Entregar en el aula virtual
Pega el enlace del repo de GitHub (con .env.example, requirements.txt y las capturas) en la tarea de la Semana 3.
Cómo presentar en vivo — guion paso a paso
Un recorrido de ~5 minutos frente al profesor. Practícalo una vez antes de tu turno.
Mostrar el panel de Aiven
Servicio en estado "Running" y las tablas creadas por migrate — la prueba de que la base de datos es real y remota, no un archivo local.
Correr runserver en vivo
Muestra http://127.0.0.1:8000/ con "Hola desde MySQL" y señala el host de Aiven impreso en pantalla.
Mostrar la vista de servicios
/servicios/ con la tabla Tailwind, y explica dónde viven los datos (views.py) y dónde el diseño (el .html).
Recorrer settings.py
Muestra el bloque DATABASES y explica por qué usa config('DB_HOST') en vez de escribir el host directo — anticipa la pregunta "¿por qué no lo pusiste fijo?": porque así el mismo código sirve en tu compu y en Render sin tocar una línea.
Cerrar explicando el flujo MTV
"La URL entra por urls.py → llega a una función en views.py → esa función arma datos y elige un template → el template en core/templates/core/ genera el HTML final." Esa frase resume la arquitectura que armaste esta semana.
Tabla de referencias — de dónde sale toda la información
Todo lo de esta guía viene de documentación oficial de cada herramienta, no de blogs de terceros.
| Herramienta | Para qué | Dónde |
|---|---|---|
| Python venv | Cómo crear y activar entornos virtuales | docs.python.org/3/library/venv |
| Django — Tutorial oficial | Primer proyecto, primera app, primera vista | docs.djangoproject.com — tutorial01 |
| Django — URLs | Cómo funciona el enrutamiento de URLs | docs.djangoproject.com — URL dispatcher |
| Django — Templates | Sintaxis de {{ variable }} y {% for %} | docs.djangoproject.com — Templates |
| Django — MySQL notes | Requisitos y configuración oficial para usar MySQL | docs.djangoproject.com — MySQL notes |
| PyMySQL | Paquete conector entre Django y MySQL | pypi.org/project/PyMySQL |
| python-decouple | Lectura de variables de entorno desde .env | pypi.org/project/python-decouple |
| Aiven for MySQL | Crear y administrar la base de datos gratuita en la nube | aiven.io/docs/products/mysql |