APLICAWEB 4to/Propuesta Intensiva 8 Semanas/Anexos/Banco de Live Coding · Unidad 3
20 retos · Unidad 3 · ≤ 3 min
20

Banco de 20 Retos de Live Coding — Unidad 3

Los 20 retos parten de tu propio proyecto Django tal como queda al terminar la práctica de la Semana 6: el modelo Servicio con propietario, el ServicioForm con clean_precio, el CRUD (crear_servicio, editar_servicio, eliminar_servicio), el registro, login y logout propios, base.html con la barra de sesión y los mensajes, y el Web Service en Render. Cada reto muestra el código ANTES y DESPUÉS y cómo se ve en el navegador.

Cómo se evalúa: el docente elige uno al azar. Todos son cambios chicos sobre código que ya escribiste y se resuelven en 3 minutos o menos: se evalúa que encuentres el archivo correcto, cambies solo lo necesario y expliques por qué mientras lo haces. «Se evalúa» lista lo que se revisa y «En tu práctica» te dice dónde está. Los retos 01-03 son los oficiales de la lista de cotejo (3.3).
Este banco sí incluye rutas: los retos 04 a 10 piden crear o cambiar direcciones en core/urls.py (path(), <int:pk>, name=, {% url %}, redirect con argumentos). Los retos 07, 08 y 09 van encadenados: 08 y 09 usan la ficha que se crea en el 07.
Tip: los nombres de archivos y funciones son los de las guías de la Semana 5 y la Semana 6 (ServicioForm, crear_servicio, editar_servicio, eliminar_servicio, registro, iniciar_sesion, cerrar_sesion, base.html). Si tu carpeta del proyecto se llama distinto, busca el mismo bloque. Cada reto trae «Cómo resolverlo en VS Code»; los comandos se escriben en la terminal integrada de VS Code, sin el $. Para practicar sin que los bloques de código te digan el archivo, usa la versión sin rutas (mismos 20 retos).

Antes de cualquier reto: tu terminal es la de VS Code

  1. Abre la carpeta del proyecto: Archivo → Abrir carpeta… y elige la que tiene manage.py.
  2. Abre la terminal integrada: menú Ver → Terminal o Ctrl + ñ (teclado en español; en inglés es Ctrl + `). Se abre abajo, ya ubicada en la carpeta del proyecto, con PowerShell.
  3. Revisa que diga (venv) al inicio de la línea. Si no aparece, actívalo con el primer comando de abajo.
  4. Abre archivos rápido con Ctrl + P y escribe el nombre (ej. views.py). Guarda siempre con Ctrl + S antes de probar.
  5. Ten dos cuentas listas (ej. usuario_a y usuario_b) con servicios cargados: varios retos se prueban entrando con una y luego con la otra. Para probar «sin sesión» usa una ventana de incógnito (Ctrl + Shift + N).
TERMINAL DE VS CODE — PowerShell
# activa el entorno virtual (Windows, PowerShell de VS Code)
$ venv\Scripts\Activate.ps1
# si PowerShell bloquea el script, corre esto una vez y repite la línea anterior
$ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# revisa que no haya errores de escritura en settings, urls, vistas ni formularios
$ python manage.py check
# levanta el servidor local para ver tus cambios en http://127.0.0.1:8000/
$ python manage.py runserver
Unidad 3

CRUD Dinámico y Sesiones (Semanas 5-6)

01

Demostrar en vivo que una cuenta nueva no ve los datos de otraoficial · lista de cotejo

⏱ 3 minSemana 6producción + core/views.py

Consigna del docente: “Registra una cuenta nueva ahora mismo, carga un servicio, cierra sesión, entra con tu cuenta de siempre y muéstrame que no ves el servicio que acabas de crear con la cuenta nueva.”

LA LÍNEA QUE LO HACE POSIBLE — core/views.py, vista servicios
# @login_required: sin sesión no se ejecuta la vista
@login_required
# servicios: la vista del listado
def servicios(request):
    # filter(propietario=request.user): WHERE propietario_id = <id del usuario de la sesión>
    lista_servicios = Servicio.objects.filter(propietario=request.user)
    # la tabla se dibuja solo con los servicios de esa cuenta
    return render(request, 'core/servicios.html', {'servicios': lista_servicios})

Cómo resolverlo en VS Code

  1. Abre tu URL de producción en /registro/ y crea usuario_demo (contraseña de 8+ caracteres que no sea solo números).
  2. Al registrarte entras directo: muestra el mensaje verde y la tabla vacía «Todavía no hay servicios.»
  3. Da clic en + Nuevo servicio, crea «Prueba demo» y muestra que aparece en la tabla.
  4. Da clic en Cerrar sesión (mensaje «Sesión cerrada correctamente.») y entra con tu cuenta de siempre en /login/.
  5. Muestra tu listado: «Prueba demo» no aparece.
  6. En VS Code: Ctrl + P → escribe views.py → Enter. Señala la línea del filter y explica el WHERE.

Vista — la misma página /servicios/ con dos cuentas distintas

tu-proyecto.onrender.com/servicios/ · usuario_demo

Hola, usuario_demo · Cerrar sesión

Servicios

ServicioPrecioAcciones
Prueba demo$99.00Editar Eliminar

Cuenta nueva: solo ve lo que ella creó

tu-proyecto.onrender.com/servicios/ · usuario_a

Hola, usuario_a · Cerrar sesión

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

«Prueba demo» no aparece en la otra cuenta ✓

SE EVALÚA
  • Registra la cuenta desde /registro/, no desde /admin
  • Muestra el listado vacío de la cuenta nueva antes de crear nada
  • Explica que filter(propietario=request.user) agrega el WHERE con el id del usuario
  • Relaciona request.user con la cookie de sesión que dejó el login

📍 En tu práctica: tu URL de producción en Render y core/views.py, vista servicios.

02

Forzar el 404 al editar un servicio ajeno y explicarlo leyendo views.pyoficial · lista de cotejo

⏱ 3 minSemana 6barra de direcciones + core/views.py

Consigna del docente: “Aquí tienes el id de un servicio que no es tuyo. Intenta editarlo desde la URL a mano y explica, leyendo views.py, por qué te da 404 en vez de mostrarte el formulario.”

ASÍ ESTABA en la Semana 5 — vulnerable a IDOR
# solo una condición: cualquier id existente se abría, aunque fuera de otra cuenta
servicio = get_object_or_404(Servicio, pk=pk)
ASÍ ESTÁ HOY — core/views.py, editar_servicio
# dos condiciones: el id correcto Y que el dueño sea el usuario de la sesión
# si el servicio es de otra cuenta, la consulta no encuentra nada y responde 404
servicio = get_object_or_404(Servicio, pk=pk, propietario=request.user)

Cómo resolverlo en VS Code

  1. En tu listado pasa el mouse sobre Editar y lee el número en la esquina inferior del navegador (ej. /servicios/7/editar/).
  2. En la barra de direcciones cambia ese número por el que te dicte el docente (ej. /servicios/3/editar/) y presiona Enter.
  3. Muestra el Not Found (en local con DEBUG=True dice «No Servicio matches the given query»).
  4. En VS Code: Ctrl + P → escribe views.py → Enter. Con Ctrl + F busca get_object_or_404 y señala propietario=request.user.
  5. Repite con /servicios/3/eliminar/: mismo candado, mismo 404.

Vista — escribir a mano el id de otra cuenta

tu-proyecto.onrender.com/servicios/3/editar/

Editar servicio

Titulo

Clases de guitarra

Precio

250.00

Semana 5: se abría el servicio de OTRA cuenta ✗

tu-proyecto.onrender.com/servicios/3/editar/

Not Found

The requested resource was not found on this server.

Semana 6: 404, ni siquiera confirma que el id 3 existe ✓

SE EVALÚA
  • Cambia el id en la URL a mano, sin usar los botones
  • Señala la segunda condición propietario=request.user
  • Explica por qué 404 y no un aviso de «no es tuyo»: no revela que ese id existe
  • Menciona que eliminar_servicio tiene el mismo candado

📍 En tu práctica: core/views.py, funciones editar_servicio y eliminar_servicio (primera línea dentro de cada una).

03

Rechazar títulos de menos de 5 caracteres con clean_titulooficial · lista de cotejo

⏱ 3 minSemana 5core/forms.py

Consigna del docente: “Agrega una regla a ServicioForm que rechace títulos de menos de 5 caracteres, usando clean_titulo, y pruébala en el formulario.”

ANTES — core/forms.py (final del archivo)
    # clean_precio: la validación de la Semana 5
    def clean_precio(self):
        # ... (sus líneas no cambian)
        # return: regresa el precio limpio
        return precio
DESPUÉS — core/forms.py
    # clean_precio: no se toca
    def clean_precio(self):
        # ... (sus líneas no cambian)
        # return: regresa el precio limpio
        return precio

    # clean_titulo: NUEVO; Django lo llama solo porque se llama clean_ + nombre del campo
    def clean_titulo(self):
        # cleaned_data['titulo']: el texto que escribió el usuario
        titulo = self.cleaned_data['titulo']
        # len(titulo) < 5: cuenta los caracteres; menos de 5 no se acepta
        if len(titulo) < 5:
            # ValidationError: el formulario queda inválido y el mensaje sale junto al campo
            raise ValidationError('El título debe tener al menos 5 caracteres.')
        # return: sin esta línea el título llegaría vacío (None) y fallaría al guardar
        return titulo

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe forms.py → Enter.
  2. Debajo de clean_precio escribe clean_titulo con 4 espacios de sangría (dentro de la clase, al mismo nivel que class Meta).
  3. No agregues import: ValidationError ya está importado arriba desde la Semana 5.
  4. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  5. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Escribe «Web» → error rojo; escribe «Web pro» → se guarda.

Vista — /servicios/nuevo/ con el título «Web»

127.0.0.1:8000/servicios/nuevo/

Nuevo servicio

Titulo

Web
Guardar

Se guardaba un título de 3 letras ✗

127.0.0.1:8000/servicios/nuevo/

Nuevo servicio

Titulo

Web

El título debe tener al menos 5 caracteres.

Guardar

No se guarda hasta corregir ✓

SE EVALÚA
  • Nombre exacto clean_titulo, dentro de la clase ServicioForm
  • Lee el valor desde self.cleaned_data
  • Usa raise ValidationError, no un print
  • No olvida return titulo y explica qué pasa sin él

📍 En tu práctica: core/forms.py, después de clean_precio (al final del archivo).

04

Cambiar la dirección /registro/ por /crear-cuenta/

⏱ 2 minSemana 6core/urls.py

Consigna del docente: “Quiero que el registro se abra en /crear-cuenta/. Cambia solo lo necesario y muéstrame que el enlace «Crear cuenta» de la barra sigue funcionando.”

ANTES — core/urls.py
    # /registro/: pantalla de alta de cuenta
    path('registro/', views.registro, name='registro'),
DESPUÉS — core/urls.py
    # solo cambia el primer texto: lo que se escribe en el navegador
    # la vista y el name='registro' se quedan iguales
    path('crear-cuenta/', views.registro, name='registro'),

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe urls.py → Enter. Elige el de core (la carpeta se ve a la derecha del nombre).
  2. Cambia solo 'registro/' por 'crear-cuenta/'.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/login/ en el navegador. Da clic en «Regístrate»: abre /crear-cuenta/. Prueba /registro/ para mostrar el 404.

Vista — enlace «Crear cuenta» de la barra

127.0.0.1:8000/registro/

Iniciar sesión · Crear cuenta

Crear cuenta

Nombre de usuario

La dirección vieja

127.0.0.1:8000/crear-cuenta/

Iniciar sesión · Crear cuenta

Crear cuenta

Nombre de usuario

Mismo enlace, nueva dirección, sin tocar plantillas ✓

SE EVALÚA
  • Distingue las 3 partes de path(): dirección, vista y name
  • No toca base.html ni login.html
  • Explica que {% url 'registro' %} busca por name, por eso los enlaces se actualizan solos

📍 En tu práctica: core/urls.py, la línea de name='registro'.

05

Crear la página pública /acerca/: vista, ruta y plantilla

⏱ 3 minSemana 5views.py + urls.py + acerca.html

Consigna del docente: “Agrega una página /acerca/ que diga el nombre de tu proyecto, que use base.html y que se pueda ver sin iniciar sesión.”

ANTES — core/urls.py (final de la lista)
    # /logout/: cierra la sesión activa
    path('logout/', views.cerrar_sesion, name='logout'),
# cierra la lista urlpatterns
]
DESPUÉS — core/urls.py
    # /logout/: no se toca
    path('logout/', views.cerrar_sesion, name='logout'),
    # /acerca/: NUEVA ruta; apunta a views.acerca y se llama 'acerca'
    path('acerca/', views.acerca, name='acerca'),
# cierra la lista urlpatterns
]
NUEVO — core/views.py (pega al final)
# acerca: NUEVA vista pública (sin @login_required)
def acerca(request):
    # render sin contexto: la plantilla no necesita datos de la base
    return render(request, 'core/acerca.html')
NUEVO — core/templates/core/acerca.html
{# extends: usa el molde base.html (barra de sesión y mensajes incluidos) #}
{% extends 'core/base.html' %}
{# block contenido: lo que va dentro del body de base.html #}
{% block contenido %}
  {# h1: título visible de la página #}
  <h1 class="text-2xl font-bold mb-4">Acerca de</h1>
  {# p: texto libre; pon el nombre de tu proyecto #}
  <p class="text-slate-600">Proyecto de Aplicaciones Web — UTH.</p>
{# endblock: fin del bloque contenido #}
{% endblock %}

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Pega la vista acerca al final.
  2. En el Explorador de VS Code, clic derecho sobre core/templates/core → Nuevo archivo → acerca.html, y escribe la plantilla.
  3. En VS Code: Ctrl + P → escribe urls.py → Enter. (el de core) y agrega el path antes del ].
  4. Guarda los 3 archivos (Ctrl + K y luego S guarda todos).
  5. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/acerca/ en el navegador. Ábrelo también en una ventana de incógnito (Ctrl + Shift + N) para mostrar que no pide login.

Vista — /acerca/ sin sesión

127.0.0.1:8000/acerca/

Page not found (404)

Todavía no existe la ruta ✗

127.0.0.1:8000/acerca/

Iniciar sesión · Crear cuenta

Acerca de

Proyecto de Aplicaciones Web — UTH.

Pública y con la barra de base.html ✓

SE EVALÚA
  • Toca los 3 archivos en orden lógico: vista, plantilla, ruta
  • La plantilla empieza con {% extends %}
  • Explica por qué no lleva @login_required
  • Si sale TemplateDoesNotExist, sabe revisar la carpeta core/templates/core/

📍 En tu práctica: core/views.py (final), core/urls.py (antes del ]) y un archivo nuevo en core/templates/core/.

06

Proteger /acerca/ con @login_required y explicar el ?next=

⏱ 2 minSemana 6core/views.py

Consigna del docente: “Ahora haz que /acerca/ solo se vea con sesión iniciada. Ábrela sin sesión y explícame la dirección a la que te manda.”

⚠ Requiere el reto «Página pública /acerca/».

ANTES — core/views.py
# acerca: NUEVA vista pública (sin @login_required)
def acerca(request):
    # render sin contexto: la plantilla no necesita datos de la base
    return render(request, 'core/acerca.html')
DESPUÉS — core/views.py
# @login_required: NUEVO; sin sesión, manda a LOGIN_URL con ?next=/acerca/
@login_required
# acerca: la misma vista, ahora protegida
def acerca(request):
    # render sin contexto: no cambia
    return render(request, 'core/acerca.html')

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Con Ctrl + F busca def acerca.
  2. Escribe @login_required en la línea de arriba, pegado al margen (sin sangría). El import ya existe.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. Abre una ventana de incógnito (Ctrl + Shift + N) y ve a http://127.0.0.1:8000/acerca/.
  5. Lee en voz alta la dirección resultante: /login/?next=/acerca/.

Vista — visitante sin sesión pide /acerca/

127.0.0.1:8000/acerca/

Iniciar sesión · Crear cuenta

Acerca de

Cualquiera la ve

127.0.0.1:8000/login/?next=/acerca/

Iniciar sesión · Crear cuenta

Iniciar sesión

Nombre de usuario

Contraseña

Redirigido al login ✓

SE EVALÚA
  • Coloca el decorador justo encima del def
  • Explica que /login/ sale de LOGIN_URL = 'login' en settings.py
  • Explica que ?next= guarda la página que se pedía
  • Nota que tu iniciar_sesion ignora next y siempre manda a servicios

📍 En tu práctica: core/views.py, encima de def acerca; LOGIN_URL al final de settings.py.

07

Crear la ficha de un servicio en /servicios/<pk>/

⏱ 4 minSemanas 5-6views.py + urls.py + servicio_detalle.html

Consigna del docente: “Agrega una página que muestre un solo servicio completo, con descripción y categoría, en /servicios/<id>/. Que nadie pueda ver la ficha de otra cuenta.”

ANTES — core/urls.py
    # /servicios/nuevo/: crear
    path('servicios/nuevo/', views.crear_servicio, name='crear_servicio'),
DESPUÉS — core/urls.py
    # /servicios/nuevo/: no se toca
    path('servicios/nuevo/', views.crear_servicio, name='crear_servicio'),
    # /servicios/<id>/: NUEVA; <int:pk> captura el número y lo pasa a la vista
    path('servicios/<int:pk>/', views.detalle_servicio, name='detalle_servicio'),
NUEVO — core/views.py (pega al final)
# @login_required: solo con sesión iniciada
@login_required
# detalle_servicio: recibe el pk desde la URL /servicios/<pk>/
def detalle_servicio(request, pk):
    # mismo candado que editar: el id correcto Y que sea del usuario; si no, 404
    servicio = get_object_or_404(Servicio, pk=pk, propietario=request.user)
    # render: manda ese servicio a la plantilla de la ficha
    return render(request, 'core/servicio_detalle.html', {'servicio': servicio})
NUEVO — core/templates/core/servicio_detalle.html
{# extends: usa el molde base.html #}
{% extends 'core/base.html' %}
{# block titulo: la pestaña muestra el título del servicio #}
{% block titulo %}{{ servicio.titulo }}{% endblock %}
{# block contenido: la ficha #}
{% block contenido %}
  {# h1: título del servicio #}
  <h1 class="text-2xl font-bold mb-2">{{ servicio.titulo }}</h1>
  {# p: descripción completa #}
  <p class="text-slate-600 mb-4">{{ servicio.descripcion }}</p>
  {# p: precio y categoría en una línea #}
  <p class="font-semibold">${{ servicio.precio }} · {{ servicio.categoria }}</p>
  {# enlace de regreso al listado #}
  <a href="{% url 'servicios' %}" class="inline-block mt-4 text-blue-600 underline">← Regresar</a>
{# endblock: fin del bloque contenido #}
{% endblock %}

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Pega detalle_servicio al final.
  2. Crea core/templates/core/servicio_detalle.html (clic derecho en la carpeta → Nuevo archivo).
  3. En VS Code: Ctrl + P → escribe urls.py → Enter. (el de core) y agrega la ruta debajo de servicios/nuevo/.
  4. Guarda todo y corre python manage.py runserver en la terminal de VS Code.
  5. Abre /servicios/<tu id>/ y luego el id de otra cuenta para mostrar el 404.

Vista — /servicios/7/

127.0.0.1:8000/servicios/7/

Page not found (404)

No existe la ruta ✗

127.0.0.1:8000/servicios/7/

Hola, usuario_a · Cerrar sesión

Tutorías de programación

Clases de Python de 1 hora por videollamada.

$150.00 · Tutorías

Ficha propia; la de otra cuenta da 404 ✓

SE EVALÚA
  • Usa <int:pk> y la vista recibe pk
  • Copia el candado propietario=request.user (si no, abre IDOR)
  • Lleva @login_required
  • Explica por qué /servicios/nuevo/ no choca: «nuevo» no es número

📍 En tu práctica: core/urls.py (debajo de crear_servicio), core/views.py (final) y un archivo nuevo en core/templates/core/.

08

Convertir el título de cada fila en enlace a su ficha

⏱ 2 minSemana 5core/templates/core/servicios.html

Consigna del docente: “En el listado, que al dar clic en el nombre del servicio se abra su ficha.”

⚠ Requiere el reto «Ficha /servicios/<pk>/».

ANTES — servicios.html
        {# td: celda con el título del servicio #}
        <td class="px-4 py-2">{{ servicio.titulo }}</td>
DESPUÉS — servicios.html
        {# td: el título ahora es enlace; {% url %} arma /servicios/<id>/ con el pk de la fila #}
        <td class="px-4 py-2"><a href="{% url 'detalle_servicio' servicio.pk %}" class="text-blue-600 underline">{{ servicio.titulo }}</a></td>

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe servicios.html → Enter.
  2. Con Ctrl + F busca servicio.titulo y envuélvelo en el <a>.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/ en el navegador. Da clic en un título.

Vista — columna Servicio

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

Cada título abre su ficha ✓

SE EVALÚA
  • Usa {% url 'detalle_servicio' servicio.pk %}, no la dirección a mano
  • Comillas simples adentro porque href usa dobles
  • Sabe que servicio.pk es el mismo número que servicio.id

📍 En tu práctica: core/templates/core/servicios.html, primera <td> dentro del {% for %}.

09

Después de crear un servicio, mandar a su ficha en vez del listado

⏱ 2 minSemana 5core/views.py

Consigna del docente: “Cuando guarde un servicio nuevo, que me lleve directo a la ficha de ese servicio.”

⚠ Requiere el reto «Ficha /servicios/<pk>/».

ANTES — core/views.py, crear_servicio
            # save(): guarda el registro completo en MySQL
            nuevo.save()
            # redirect('servicios'): regresa al listado
            return redirect('servicios')
DESPUÉS — core/views.py, crear_servicio
            # save(): después de esta línea, nuevo.pk ya tiene el id que asignó MySQL
            nuevo.save()
            # redirect con argumento: arma /servicios/<id>/ con el pk recién creado
            return redirect('detalle_servicio', pk=nuevo.pk)

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Busca def crear_servicio.
  2. Cambia solo el return redirect(...) de dentro del if form.is_valid():.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Guarda uno y mira la dirección final.

Vista — justo después de «Guardar»

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
Asesoría web$500.00Editar Eliminar
127.0.0.1:8000/servicios/8/

Asesoría web

Revisión de tu sitio en una sesión.

Llega a la ficha del nuevo id 8 ✓

SE EVALÚA
  • Pasa el pk como argumento con nombre (pk=)
  • Explica que el id existe solo después del save()
  • No cambia los redirect de editar ni eliminar

📍 En tu práctica: core/views.py, función crear_servicio, dentro del if form.is_valid():.

10

Arreglar un NoReverseMatch: alguien borró la ruta de logout

⏱ 3 minSemana 6core/urls.py

Consigna del docente: “Borré una línea de tu proyecto y ahora el listado truena. Lee el error, encuentra qué falta y arréglalo.”

ANTES — core/urls.py (como lo dejó el docente)
    # /login/: pantalla de inicio de sesión propia
    path('login/', views.iniciar_sesion, name='login'),
# cierra la lista urlpatterns (aquí faltaba la ruta de logout)
]
DESPUÉS — core/urls.py
    # /login/: no se toca
    path('login/', views.iniciar_sesion, name='login'),
    # /logout/: RESTAURADA; base.html la busca con {% url 'logout' %}
    path('logout/', views.cerrar_sesion, name='logout'),
# cierra la lista urlpatterns
]

Cómo resolverlo en VS Code

  1. Lee la página amarilla de error: Reverse for 'logout' not found y la línea marcada de base.html.
  2. Explica: la plantilla pide una ruta con name='logout' que ya no existe en urls.py.
  3. En VS Code: Ctrl + P → escribe urls.py → Enter. (el de core) y vuelve a escribir el path de logout.
  4. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  5. Recarga F5 la página: el listado y la barra «Cerrar sesión» regresan.

Vista — /servicios/ con sesión iniciada

127.0.0.1:8000/servicios/

NoReverseMatch at /servicios/

Reverse for 'logout' not found. 'logout' is not a valid view function or pattern name.

Error en core/base.html, línea de {% url 'logout' %}

127.0.0.1:8000/servicios/

Hola, usuario_a · Cerrar sesión

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

La barra vuelve a armar /logout/ ✓

SE EVALÚA
  • Lee el error completo antes de tocar código
  • Relaciona {% url 'logout' %} con el name='logout'
  • Explica por qué el error sale aunque no visite /logout/: la plantilla arma el enlace al dibujarse
  • Explica por qué sin sesión no truena: esa línea está dentro de {% if user.is_authenticated %}

📍 En tu práctica: core/urls.py, después de la ruta login/.

11

Mostrar un mensaje verde «Servicio creado» al guardar uno nuevo

⏱ 2 minSemana 6core/views.py

Consigna del docente: “Cuando cree un servicio, que aparezca un aviso verde con su nombre arriba del listado.”

ANTES — core/views.py, crear_servicio
            # save(): guarda el registro completo en MySQL
            nuevo.save()
            # redirect: regresa al listado
            return redirect('servicios')
DESPUÉS — core/views.py, crear_servicio
            # save(): no se toca
            nuevo.save()
            # messages.success: NUEVO; se guarda ahora y se muestra una vez en la siguiente página
            messages.success(request, f'Servicio "{nuevo.titulo}" creado.')
            # redirect: el listado usa base.html, que ya dibuja los mensajes
            return redirect('servicios')

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Busca def crear_servicio.
  2. Entre nuevo.save() y el return redirect agrega la línea de messages.success (misma sangría).
  3. No agregues import: from django.contrib import messages ya está desde la Semana 6.
  4. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece). En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Crea uno y luego recarga F5.

Vista — al regresar al listado

127.0.0.1:8000/servicios/

Hola, usuario_a · Cerrar sesión

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
127.0.0.1:8000/servicios/

Hola, usuario_a · Cerrar sesión

Servicio "Asesoría web" creado.

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

Desaparece al recargar ✓

SE EVALÚA
  • Pone el mensaje antes del redirect
  • No toca la plantilla: explica que base.html ya tiene el {% for message in messages %}
  • Explica por qué desaparece al recargar

📍 En tu práctica: core/views.py, crear_servicio, dentro del if form.is_valid():.

12

Mostrar «Tienes N servicios» arriba de la tabla

⏱ 2 minSemana 6core/templates/core/servicios.html

Consigna del docente: “Arriba de la tabla quiero un texto que diga cuántos servicios tiene el usuario.”

ANTES — servicios.html
  {# h1: título de la página #}
  <h1 class="text-2xl font-bold mb-6">Servicios</h1>
DESPUÉS — servicios.html
  {# h1: título de la página (mb-2 para dejar el contador pegado) #}
  <h1 class="text-2xl font-bold mb-2">Servicios</h1>
  {# |length cuenta los elementos de la lista; |pluralize agrega la "s" si no es 1 #}
  <p class="mb-6 text-slate-600">Tienes {{ servicios|length }} servicio{{ servicios|length|pluralize }}</p>

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe servicios.html → Enter.
  2. Debajo del <h1> agrega el <p> con los filtros.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/ en el navegador. Entra con dos cuentas: cada una ve su propio número.

Vista — arriba de la tabla

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
127.0.0.1:8000/servicios/

Servicios

Tienes 2 servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

Cuenta solo los del usuario ✓

SE EVALÚA
  • Reutiliza la variable servicios que ya manda la vista (no toca views.py)
  • Usa el filtro |length
  • Explica que el número es por usuario gracias al filter(propietario=...)

📍 En tu práctica: core/templates/core/servicios.html, debajo del <h1>.

13

Agregar la columna Categoría a la tabla del listado

⏱ 3 minSemana 5core/templates/core/servicios.html

Consigna del docente: “Quiero ver la categoría de cada servicio en la tabla, entre Precio y Acciones.”

ANTES — servicios.html (3 fragmentos)
        {# th: encabezado de la columna del precio #}
        <th class="text-left px-4 py-2">Precio</th>
        {# ··· más abajo, dentro del for ··· #}
        <td class="px-4 py-2">${{ servicio.precio }}</td>
        {# ··· y en el empty ··· #}
      <tr><td colspan="3" class="px-4 py-6 text-center text-slate-500">Todavía no hay servicios.</td></tr>
DESPUÉS — servicios.html
        {# th: encabezado del precio (no se toca) #}
        <th class="text-left px-4 py-2">Precio</th>
        {# th: NUEVO encabezado #}
        <th class="text-left px-4 py-2">Categoría</th>
        {# td: precio (no se toca) #}
        <td class="px-4 py-2">${{ servicio.precio }}</td>
        {# td: NUEVA celda con el campo categoria del modelo #}
        <td class="px-4 py-2">{{ servicio.categoria }}</td>
        {# colspan="4": ahora son 4 columnas, el aviso debe abarcarlas todas #}
      <tr><td colspan="4" class="px-4 py-6 text-center text-slate-500">Todavía no hay servicios.</td></tr>

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe servicios.html → Enter.
  2. Agrega el <th> después de «Precio» en el <thead>.
  3. Agrega el <td> después de la celda del precio, dentro del {% for %}.
  4. Cambia colspan="3" por colspan="4" en el {% empty %}.
  5. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece). En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/ en el navegador.

Vista — la tabla

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
127.0.0.1:8000/servicios/

Servicios

ServicioPrecioCategoríaAcciones
Tutorías de programación$150.00TutoríasEditar Eliminar
Diseño de logotipos$300.00DiseñoEditar Eliminar

Encabezado y celda en la misma posición ✓

SE EVALÚA
  • Agrega encabezado y celda en la misma posición
  • Usa el nombre exacto del campo: categoria sin acento
  • Se acuerda del colspan

📍 En tu práctica: core/templates/core/servicios.html: <thead>, la fila del {% for %} y el {% empty %}.

14

Ordenar el listado del más nuevo al más viejo

⏱ 2 minSemana 6core/views.py

Consigna del docente: “Que el último servicio que creé salga hasta arriba de la tabla.”

ANTES — core/views.py, vista servicios
    # filter: solo los servicios del usuario de la sesión
    lista_servicios = Servicio.objects.filter(propietario=request.user)
DESPUÉS — core/views.py
    # filter: no cambia · order_by('-creado_en'): ORDER BY creado_en DESC (el "-" invierte el orden)
    lista_servicios = Servicio.objects.filter(propietario=request.user).order_by('-creado_en')

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Busca def servicios.
  2. Al final de la línea del filter(...) agrega .order_by('-creado_en').
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Crea uno y comprueba que queda primero.

Vista — después de crear «Asesoría web»

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
Asesoría web$500.00Editar Eliminar

El nuevo queda al final

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Asesoría web$500.00Editar Eliminar
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar

El nuevo queda arriba ✓

SE EVALÚA
  • Encadena order_by después del filter sin perder el filtro
  • Explica el - (descendente)
  • Usa creado_en, el campo con auto_now_add

📍 En tu práctica: core/views.py, función servicios.

15

Quitar @login_required del listado, mostrar el error y restaurarlo

⏱ 3 minSemana 6core/views.py + ventana de incógnito

Consigna del docente: “Comenta el @login_required de la vista servicios, abre el listado sin sesión, explícame el error y déjalo como estaba.”

PRUEBA — core/views.py (decorador comentado)
# @login_required   ← comentado solo para la prueba
# servicios: la vista del listado
def servicios(request):
    # sin sesión, request.user es AnonymousUser, que no tiene id
    lista_servicios = Servicio.objects.filter(propietario=request.user)
RESTAURADO — core/views.py
# @login_required: corta ANTES de llegar al filter y manda al login
@login_required
# servicios: la vista del listado
def servicios(request):
    # aquí request.user siempre es un usuario real con id
    lista_servicios = Servicio.objects.filter(propietario=request.user)

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. Busca def servicios.
  2. Pon el cursor en la línea @login_required y presiona Ctrl + } (o Ctrl + /) para comentarla. Guarda.
  3. Abre una ventana de incógnito (Ctrl + Shift + N) en http://127.0.0.1:8000/servicios/ y lee el error.
  4. Descomenta con el mismo atajo, guarda y recarga: ahora te manda a /login/?next=/servicios/.

Vista — /servicios/ sin sesión (ventana de incógnito)

127.0.0.1:8000/servicios/

TypeError at /servicios/

Field 'id' expected a number but got <AnonymousUser>.

En producción sería un Server Error (500) ✗

127.0.0.1:8000/login/?next=/servicios/

Iniciar sesión · Crear cuenta

Iniciar sesión

Nombre de usuario

Redirige al login ✓

SE EVALÚA
  • Explica que sin sesión request.user es AnonymousUser y no tiene id
  • Explica que el decorador corta antes de ejecutar la vista
  • Lo deja restaurado y prueba el redirect a /login/?next=

📍 En tu práctica: core/views.py, línea de arriba de def servicios.

16

Quitar {% csrf_token %}, mostrar el 403 y restaurarlo

⏱ 2 minSemana 5core/templates/core/servicio_form.html

Consigna del docente: “Borra el csrf_token del formulario, intenta guardar, explícame el error y déjalo como estaba.”

PRUEBA — servicio_form.html (sin token)
  {# method="POST": este formulario modifica la base de datos #}
  <form method="POST" class="space-y-4 max-w-md">
    {# form.as_p: los campos (aquí faltaba el csrf_token) #}
    {{ form.as_p }}
RESTAURADO — servicio_form.html
  {# method="POST": este formulario modifica la base de datos #}
  <form method="POST" class="space-y-4 max-w-md">
    {# csrf_token: campo oculto con el token que Django revisa en cada POST #}
    {% csrf_token %}
    {# form.as_p: los campos del ModelForm #}
    {{ form.as_p }}

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe servicio_form.html → Enter.
  2. Corta la línea {% csrf_token %} con Ctrl + X y guarda.
  3. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Llena el formulario y da Guardar: sale el 403.
  4. Pega de nuevo la línea con Ctrl + V, guarda, recarga F5 la página del formulario y guarda: ahora sí funciona.
  5. Opcional: Ctrl + U en el navegador para ver el <input type="hidden" name="csrfmiddlewaretoken">.

Vista — clic en «Guardar»

127.0.0.1:8000/servicios/nuevo/

Forbidden (403)

CSRF verification failed. Request aborted. Reason: CSRF token missing.

127.0.0.1:8000/servicios/

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar
Diseño de logotipos$300.00Editar Eliminar
Asesoría web$500.00Editar Eliminar

Con el token, el POST se acepta ✓

SE EVALÚA
  • Explica qué ataque evita (envío desde otro sitio con tu sesión)
  • Sabe que lo revisa el middleware en cada POST
  • Lo deja restaurado y recarga el formulario antes de probar

📍 En tu práctica: core/templates/core/servicio_form.html, justo debajo de <form method="POST">.

17

Mostrar en /admin que la contraseña está hasheada

⏱ 2 minSemana 6/admin

Consigna del docente: “Entra al panel de administración y enséñame cómo guardó Django la contraseña de usuario_a. ¿Me la puedes decir?”

LO QUE VERÁS — /admin → Usuarios → usuario_a → Contraseña
# algoritmo: pbkdf2_sha256 · iteraciones: 870000 · sal: aleatoria · hash: resultado
algorithm: pbkdf2_sha256  iterations: 870000  salt: Xy3k******  hash: Qm9v**********

Cómo resolverlo en VS Code

  1. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/admin/ en el navegador. Entra con tu superusuario.
  2. Ve a Usuarios → usuario_a.
  3. Señala el campo Contraseña: algoritmo, iteraciones, sal y hash (Django oculta parte con asteriscos).
  4. Explica que no hay forma de leer la original; el botón del admin solo permite cambiarla.

Vista — ficha del usuario en /admin

127.0.0.1:8000/admin/auth/user/

Seleccione usuario a modificar

Nombre de usuarioEs staff
usuario_a✗
usuario_demo✗
127.0.0.1:8000/admin/auth/user/2/change/

Contraseña:

algorithm: pbkdf2_sha256 iterations: 870000 salt: Xy3k****** hash: Qm9v**********

No se guarda la contraseña, solo su hash ✓

SE EVALÚA
  • Dice que no se puede desencriptar (función de un solo sentido)
  • Nombra el algoritmo y la sal
  • Explica cómo se valida el login: se hashea lo tecleado y se compara

📍 En tu práctica: /admin → Autenticación y autorización → Usuarios.

18

Agregar el enlace «Mis servicios» en la barra, solo con sesión iniciada

⏱ 2 minSemana 6core/templates/core/base.html

Consigna del docente: “En la barra de arriba quiero un enlace a Mis servicios, pero que solo aparezca si ya inicié sesión.”

ANTES — base.html
    {% if user.is_authenticated %}
      {# user.username: nombre del usuario que inició sesión #}
      <span>Hola, <b>{{ user.username }}</b></span>
DESPUÉS — base.html
    {% if user.is_authenticated %}
      {# NUEVO: enlace al listado; {% url %} usa el name='servicios' que ya existe #}
      <a href="{% url 'servicios' %}" class="text-blue-600 underline">Mis servicios</a>
      {# user.username: no se toca #}
      <span>Hola, <b>{{ user.username }}</b></span>

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe base.html → Enter.
  2. Dentro del {% if user.is_authenticated %}, arriba del «Hola», agrega el <a>.
  3. Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/nuevo/ en el navegador. Cierra sesión y mira que el enlace desaparece.

Vista — la barra superior

127.0.0.1:8000/servicios/nuevo/

Hola, usuario_a · Cerrar sesión

Nuevo servicio

127.0.0.1:8000/servicios/nuevo/

Mis servicios · Hola, usuario_a · Cerrar sesión

Nuevo servicio

Solo aparece con sesión ✓

SE EVALÚA
  • Lo pone dentro del if, no del else
  • Usa {% url 'servicios' %}
  • Explica que al ser base.html aparece en todas las páginas

📍 En tu práctica: core/templates/core/base.html, dentro de <nav>.

19

Aviso amarillo al eliminar (nivel warning) y colores según el tipo de mensaje

⏱ 3 minSemana 6core/views.py + base.html

Consigna del docente: “Cuando borre un servicio, que salga un aviso, pero amarillo, no verde como los demás.”

ANTES — core/views.py, eliminar_servicio
        # delete(): borra la fila de MySQL
        servicio.delete()
        # redirect: regresa al listado
        return redirect('servicios')
DESPUÉS — core/views.py, eliminar_servicio
        # delete(): no se toca
        servicio.delete()
        # messages.warning: NUEVO; mismo mecanismo, pero con la etiqueta "warning"
        messages.warning(request, 'Servicio eliminado.')
        # redirect: no se toca
        return redirect('servicios')
DESPUÉS — base.html (reemplaza el for de mensajes)
  {# for: recorre los mensajes pendientes #}
  {% for message in messages %}
    {# message.tags: "success", "info" o "warning", según la función usada en la vista #}
    {% if message.tags == 'warning' %}
      {# warning: caja amarilla #}
      <div class="bg-amber-100 text-amber-800 px-4 py-2 rounded-lg mb-4">{{ message }}</div>
    {# cualquier otro tipo #}
    {% else %}
      {# success / info: la caja verde de siempre #}
      <div class="bg-emerald-100 text-emerald-800 px-4 py-2 rounded-lg mb-4">{{ message }}</div>
    {# fin de la condición #}
    {% endif %}
  {# fin de la repetición #}
  {% endfor %}

Cómo resolverlo en VS Code

  1. En VS Code: Ctrl + P → escribe views.py → Enter. En eliminar_servicio, entre delete() y el redirect, agrega messages.warning.
  2. En VS Code: Ctrl + P → escribe base.html → Enter. Reemplaza el bloque {% for message in messages %} por el de abajo.
  3. Guarda los 2 archivos.
  4. En la terminal de VS Code (con (venv)): python manage.py runserver y abre http://127.0.0.1:8000/servicios/ en el navegador. Elimina uno y crea otro para comparar colores.

Vista — después de eliminar

127.0.0.1:8000/servicios/

Hola, usuario_a · Cerrar sesión

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar

Se borró sin avisar

127.0.0.1:8000/servicios/

Hola, usuario_a · Cerrar sesión

Servicio eliminado.

Servicios

ServicioPrecioAcciones
Tutorías de programación$150.00Editar Eliminar

Amarillo para warning, verde para lo demás ✓

SE EVALÚA
  • Usa messages.warning, no otro color en la vista
  • Decide el color en la plantilla con message.tags
  • Explica que la vista dice qué pasó y la plantilla cómo se ve

📍 En tu práctica: core/views.py (eliminar_servicio) y core/templates/core/base.html (bloque de mensajes).

20

Subir un cambio y probarlo en producción (y resolver un 403 de CSRF)

⏱ 3 minSemana 6terminal de VS Code + Render

Consigna del docente: “Sube el cambio que acabas de hacer, muéstrame el deploy en Render y pruébalo en tu URL pública. Si el login te da 403, arréglalo.”

RENDER → Environment — la variable que evita el 403 en el login
# CSRF_TRUSTED_ORIGINS: tu dirección completa CON https:// y SIN / al final
CSRF_TRUSTED_ORIGINS=https://u3s6-hernandez-nunez-maria-jose.onrender.com

Cómo resolverlo en VS Code

  1. Abre la terminal de VS Code (Ctrl + ñ) y corre los comandos de abajo, uno por uno.
  2. En Render abre tu Web Service → Events: aparece un deploy nuevo con tu mensaje de commit.
  3. En Logs espera Your service is live 🎉.
  4. Abre tu URL pública, inicia sesión y prueba el cambio.
  5. Si el login responde 403 CSRF verification failed: Environment → revisa CSRF_TRUSTED_ORIGINS con https:// → Save, rebuild, and deploy.
TERMINAL DE VS CODE — PowerShell
# confirma que tu proyecto apunta a tu repo u3s6-…
$ git remote -v
# lista lo que cambiaste: NO debe aparecer .env
$ git status
# agrega los cambios al siguiente commit
$ git add .
# crea el commit con un mensaje que explica el cambio
$ git commit -m "U3: live coding en vivo"
# sube a GitHub; Render detecta el push y redespliega solo
$ git push

Vista — iniciar sesión en producción

tu-proyecto.onrender.com/login/

Forbidden (403)

CSRF verification failed. Request aborted. Origin checking failed.

Falta https:// en CSRF_TRUSTED_ORIGINS ✗

tu-proyecto.onrender.com/servicios/

Hola, usuario_a · Cerrar sesión

Sesión iniciada correctamente.

Servicios

Deploy nuevo y login funcionando ✓

SE EVALÚA
  • Revisa git status antes del add (sin .env)
  • Muestra en Events que el push disparó el deploy
  • Sabe que las variables viven en Render, no en el repo
  • Explica qué protege CSRF_TRUSTED_ORIGINS

📍 En tu práctica: terminal integrada de VS Code en la carpeta con manage.py; en Render: tu Web Service → Events, Logs y Environment.

↑ Números