Fecha publicación: 24 jun 2025

09. Listado de lecciones con diseño

Ahora que tenemos los datos, vamos a darle un poco de diseño a la página de lecciones.

Primer componentizamos, ¿Qué queremos mostar?

  • El video de la lección
  • El listado de lecciones
  • El contenido de la lección

Así que manos a la obra:

Primero creamos un componente para el video de la lección, que recibirá la URL del video y el título de la lección, van a ser videos de YouTube, así que usaremos un iframe para mostrarlos.

./src/components/training/video.astro

---
export interface Props {
  videoUrl: string;
  title: string;
}

const { videoUrl, title } = Astro.props;
---

<div
  class="aspect-video w-full rounded-2xl overflow-hidden shadow-lg border border-gray-200"
>
  <iframe
    id="videoFrame"
    src={videoUrl}
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen
    class="w-full h-full"></iframe>
</div>
<h2 id="lessonTitle" class="mt-4 text-2xl font-semibold">{title}</h2>

Es hora de mostrar el listado de las lecciones.

Aquí iteramos sobre las lecciones del curso y mostramos un enlace a cada lección, además de resaltar la lección actual.

./src/components/training/lessons-list.astro

---
import type { Lesson } from "@/api";

export interface Props {
  lessons: Lesson[];
  trainingSlug: string;
  currentLessonSlug: string;
}

const { lessons, trainingSlug, currentLessonSlug } = Astro.props;
---

<div
  class="bg-white rounded-2xl shadow-md p-4 space-y-2 border border-gray-200"
>
  <h3 class="text-lg font-medium mb-2 text-gray-800">Lessons</h3>
  {
    lessons.map((lesson) => (
      <a
        href={`/training/${trainingSlug}/${lesson.slug}`}
        class={`block w-full text-left px-4 py-2 rounded-lg transition text-sm border ${
          lesson.slug === currentLessonSlug
            ? "bg-gray-100 text-blue-600 font-semibold"
            : "text-gray-700 hover:bg-gray-100 hover:border-gray-300"
        }`}
      >
        {lesson.title}
      </a>
    ))
  }
</div>

Y por último, cada lección tiene un guía en markdown que acompaña al video, así que creamos un componente para mostrar el contenido de la lección.

OJO de momento ponemos un parrafo, en el siguiente video más adelante, veremos cómo cargar el contenido de un archivo markdown y mostrarlo formateado.

./src/components/training/lesson-content.astro

---
export interface Props {
  content: string;
}

const { content } = Astro.props;
---

<h3 class="text-xl font-bold mb-4 text-gray-800">Contenido de la Lección</h3>
<p id="lessonContent" class="text-gray-700 leading-relaxed text-base">
  {content}
</p>

Y ahora vamos a montar la página de lecciones, eliminamos el HTML que teníamos y lo sustituimos por nuestros componentes.

./src/pages/training/[trainingSlug]/[lessonSlug]/index.astro

---
import Layout from "@/layouts/Layout.astro";
import { getTrainings } from "@/api";
+ import VideoComponent from "@/components/training/video.astro";
+ import LessonsComponent from "@/components/training/lessons-list.astro";
+ import LessonContentComponent from "@/components/training/lesson-content.astro";

import VideoComponent from "@/components/training/video.astro"; import LessonsComponent from "@/components/training/lessons-list.astro"; import LessonContentComponent from "@/components/training/lesson-content.astro";

<Layout title={`${training.title} - ${currentLesson?.title}`}>
  <div class="max-w-7xl mx-auto px-4 py-8">
    <h1 class="text-4xl font-bold mb-8 text-center">{training.title}</h1>

    <div class="grid md:grid-cols-12 gap-6">
      <div class="md:col-span-8">
        <VideoComponent
          videoUrl={currentLesson?.video ?? ""}
          title={currentLesson?.title ?? ""}
        />
      </div>

      <div class="md:col-span-4">
        <LessonsComponent
          lessons={training.lessons}
          trainingSlug={training?.slug}
          currentLessonSlug={currentLesson?.slug}
        />
      </div>
    </div>

    <div
      class="mt-10 bg-white p-6 rounded-2xl shadow-md border border-gray-200"
    >
      <LessonContentComponent content={currentLesson?.content ?? ""} />
    </div>
  </div>
</Layout>

Veamos que pinta tiene

npm run dev

¿No está mal verdad? Tenemos un listado de lecciones a la derecha, el video de la lección y el contenido de la lección abajo, si nos vamos a formato móvil, estás tres secciones se apilan.

Y ahora viene una parte muy interesantes, si hacemos un npm run build, y exploramos el directorio dist, veremos que se han generado todas las páginas de cursos y lecciones ¿A que así se entiende mejor la metáfora de Dr. Strange y el getStaticPaths?.

npm run build

carpeta dist, tenemos para cada curso y lección, una página generada

./dist/training/...

Todo tiene buena pinta, peeerooo nos hemos dejado algo en el tintero, y es que no estamos mostrando formateado la guía en Markdown de la lección, en el siguiente video veremos como hacerlo.