From 21f2aa0e9b9a5e007e7850836516130f3648972d Mon Sep 17 00:00:00 2001 From: Helber Toro Date: Thu, 19 Mar 2026 16:13:05 -0500 Subject: [PATCH] refactor: update course and class data structures, replace title and duration with name and description, and adjust related components and styles --- Frontend/next.config.ts | 2 +- .../src/app/classes/[class_id]/page.test.tsx | 23 ++++-------- Frontend/src/app/classes/[class_id]/page.tsx | 36 ++++++++++++------- .../src/app/course/[slug]/error.module.scss | 1 - .../src/app/course/[slug]/loading.module.scss | 1 - .../app/course/[slug]/not-found.module.scss | 1 - Frontend/src/app/course/[slug]/page.tsx | 12 +++---- Frontend/src/app/page.module.scss | 1 - Frontend/src/app/page.tsx | 19 +++++----- .../src/components/Course/Course.module.scss | 1 - Frontend/src/components/Course/Course.tsx | 9 +++-- .../Course/__test__/Course.test.tsx | 23 +++--------- .../CourseDetail/CourseDetail.module.scss | 1 - .../components/CourseDetail/CourseDetail.tsx | 21 +++-------- Frontend/src/styles/vars.scss | 4 ++- Frontend/src/types/index.ts | 11 +++--- 16 files changed, 69 insertions(+), 97 deletions(-) diff --git a/Frontend/next.config.ts b/Frontend/next.config.ts index 39fba07..5359997 100644 --- a/Frontend/next.config.ts +++ b/Frontend/next.config.ts @@ -4,7 +4,7 @@ import path from "path"; const nextConfig: NextConfig = { sassOptions: { includePaths: [path.join(__dirname, "src/styles")], - prependData: `@import "vars.scss";` + prependData: `@use "vars.scss" as *;` }, /* config options here */ }; diff --git a/Frontend/src/app/classes/[class_id]/page.test.tsx b/Frontend/src/app/classes/[class_id]/page.test.tsx index de0af85..4c56bff 100644 --- a/Frontend/src/app/classes/[class_id]/page.test.tsx +++ b/Frontend/src/app/classes/[class_id]/page.test.tsx @@ -1,38 +1,29 @@ import { renderToString } from "react-dom/server"; -import { startTransition } from "react"; import ClassPage from "./page"; import { Class } from "@/types"; import { describe, it, expect, vi } from "vitest"; -vi.mock("@/components/VideoPlayer/VideoPlayer", () => ({ - VideoPlayer: ({ src, title }: { src: string; title: string }) => ( -
- {title} - {src} -
- ), -})); - // Mock de fetch que resuelve inmediatamente global.fetch = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ id: 19, - title: "Clase de Test", + name: "Clase de Test", description: "Descripción de la clase de test", - video: "https://test.com/video.mp4", - duration: 1200, slug: "clase-test", } as Class), }); describe("ClassPage", () => { - it("renders class info and video", async () => { - const html = await renderToString(); + it("renders class info", async () => { + const element = await ClassPage({ + params: Promise.resolve({ class_id: "19" }), + }); + const html = renderToString(element); expect(html).toContain("Clase de Test"); expect(html).toContain("Descripción de la clase de test"); - expect(html).toContain("mock-video-player"); expect(html).toContain("Regresar al curso"); - }, 10000); // Aumentamos el timeout a 10 segundos + }, 10000); }); diff --git a/Frontend/src/app/classes/[class_id]/page.tsx b/Frontend/src/app/classes/[class_id]/page.tsx index f969664..a4f0733 100644 --- a/Frontend/src/app/classes/[class_id]/page.tsx +++ b/Frontend/src/app/classes/[class_id]/page.tsx @@ -1,29 +1,41 @@ import { Class } from "@/types"; -import { VideoPlayer } from "@/components/VideoPlayer/VideoPlayer"; import Link from "next/link"; import styles from "./page.module.scss"; interface ClassPageProps { - params: { class_id: string }; + params: Promise<{ class_id: string }>; } -async function getClassData(class_id: string): Promise { - const res = await fetch(`http://localhost:8000/classes/${class_id}`, { cache: "no-store" }); - if (!res.ok) throw new Error("No se pudo cargar la clase"); - return res.json(); +async function getClassData(class_id: string): Promise { + try { + const res = await fetch(`http://localhost:8000/classes/${class_id}`, { cache: "no-store" }); + if (!res.ok) return null; + return res.json(); + } catch { + return null; + } } export default async function ClassPage({ params }: ClassPageProps) { - const classData = await getClassData(params.class_id); + const { class_id } = await params; + const classData = await getClassData(class_id); + + if (!classData) { + return ( +
+

No se pudo cargar la clase.

+ + ← Volver a cursos + +
+ ); + } - // Asumimos que classData tiene un campo 'slug' para el curso, si no, ajustar aquí - // Si no hay relación directa, el botón puede regresar a /course return (
- -

{classData.title}

+

{classData.name}

{classData.description}

- + ← Regresar al curso
diff --git a/Frontend/src/app/course/[slug]/error.module.scss b/Frontend/src/app/course/[slug]/error.module.scss index 8bd0719..96e0148 100644 --- a/Frontend/src/app/course/[slug]/error.module.scss +++ b/Frontend/src/app/course/[slug]/error.module.scss @@ -1,4 +1,3 @@ -@import '../../../styles/vars.scss'; .container { min-height: 100vh; diff --git a/Frontend/src/app/course/[slug]/loading.module.scss b/Frontend/src/app/course/[slug]/loading.module.scss index 4bbac7c..c7deac3 100644 --- a/Frontend/src/app/course/[slug]/loading.module.scss +++ b/Frontend/src/app/course/[slug]/loading.module.scss @@ -1,4 +1,3 @@ -@import '../../../styles/vars.scss'; .container { min-height: 100vh; diff --git a/Frontend/src/app/course/[slug]/not-found.module.scss b/Frontend/src/app/course/[slug]/not-found.module.scss index b40e043..f073a9f 100644 --- a/Frontend/src/app/course/[slug]/not-found.module.scss +++ b/Frontend/src/app/course/[slug]/not-found.module.scss @@ -1,4 +1,3 @@ -@import '../../../styles/vars.scss'; .container { min-height: 100vh; diff --git a/Frontend/src/app/course/[slug]/page.tsx b/Frontend/src/app/course/[slug]/page.tsx index fc3bc90..4b28192 100644 --- a/Frontend/src/app/course/[slug]/page.tsx +++ b/Frontend/src/app/course/[slug]/page.tsx @@ -3,9 +3,7 @@ import { CourseDetail } from "@/types"; import { CourseDetailComponent } from "@/components/CourseDetail/CourseDetail"; interface CoursePageProps { - params: { - slug: string; - }; + params: Promise<{ slug: string }>; } async function getCourseData(slug: string): Promise { @@ -25,16 +23,18 @@ async function getCourseData(slug: string): Promise { } export default async function CoursePage({ params }: CoursePageProps) { - const courseData = await getCourseData(params.slug); + const { slug } = await params; + const courseData = await getCourseData(slug); return ; } export async function generateMetadata({ params }: CoursePageProps) { - const courseData = await getCourseData(params.slug); + const { slug } = await params; + const courseData = await getCourseData(slug); return { - title: `${courseData.title} - Curso Online`, + title: `${courseData.name} - Curso Online`, description: courseData.description, }; } diff --git a/Frontend/src/app/page.module.scss b/Frontend/src/app/page.module.scss index 2dfec88..220fc91 100644 --- a/Frontend/src/app/page.module.scss +++ b/Frontend/src/app/page.module.scss @@ -1,4 +1,3 @@ -@import '../styles/vars.scss'; .page { min-height: 100vh; diff --git a/Frontend/src/app/page.tsx b/Frontend/src/app/page.tsx index bfcf5df..3368a90 100644 --- a/Frontend/src/app/page.tsx +++ b/Frontend/src/app/page.tsx @@ -4,12 +4,15 @@ import { Course as CourseComponent } from "@/components/Course/Course"; import Link from "next/link"; async function getCourses(): Promise { - const res = await fetch("http://localhost:8000/courses", { cache: "no-store" }); - if (!res.ok) { - throw new Error("Failed to fetch courses"); + try { + const res = await fetch("http://localhost:8000/courses", { cache: "no-store" }); + if (!res.ok) { + throw new Error("Failed to fetch courses"); + } + return await res.json(); + } catch { + return []; } - const data = await res.json(); - return data.data; } export default async function Home() { @@ -32,10 +35,8 @@ export default async function Home() { {courses.map((course) => ( diff --git a/Frontend/src/components/Course/Course.module.scss b/Frontend/src/components/Course/Course.module.scss index 64c7563..9850178 100644 --- a/Frontend/src/components/Course/Course.module.scss +++ b/Frontend/src/components/Course/Course.module.scss @@ -1,4 +1,3 @@ -@import '../../styles/vars.scss'; .courseCard { background: color('white'); diff --git a/Frontend/src/components/Course/Course.tsx b/Frontend/src/components/Course/Course.tsx index e11cfbf..0d1b03a 100644 --- a/Frontend/src/components/Course/Course.tsx +++ b/Frontend/src/components/Course/Course.tsx @@ -3,16 +3,15 @@ import { Course as CourseType } from "@/types"; type CourseProps = Omit; -export const Course = ({ id, title, teacher, duration, thumbnail }: CourseProps) => { +export const Course = ({ name, description, thumbnail }: Omit) => { return (
- {title} + {name}
-

{title}

-

Profesor: {teacher}

-

Duración: {duration} minutos

+

{name}

+

{description}

); diff --git a/Frontend/src/components/Course/__test__/Course.test.tsx b/Frontend/src/components/Course/__test__/Course.test.tsx index 8b38117..8e03e54 100644 --- a/Frontend/src/components/Course/__test__/Course.test.tsx +++ b/Frontend/src/components/Course/__test__/Course.test.tsx @@ -5,24 +5,16 @@ import { Course } from "../Course"; describe("Course Component", () => { const mockCourse = { - id: 1, - title: "React Fundamentals", - teacher: "John Doe", - duration: 120, + name: "React Fundamentals", + description: "Aprende React desde cero", thumbnail: "https://example.com/thumbnail.jpg", }; it("renders course information correctly", () => { render(); - // Check if title is rendered - expect(screen.getByText(mockCourse.title)).toBeDefined(); - - // Check if teacher information is rendered - expect(screen.getByText(`Profesor: ${mockCourse.teacher}`)).toBeDefined(); - - // Check if duration is rendered - expect(screen.getByText(`Duración: ${mockCourse.duration} minutos`)).toBeDefined(); + expect(screen.getByText(mockCourse.name)).toBeDefined(); + expect(screen.getByText(mockCourse.description)).toBeDefined(); }); it("renders thumbnail with correct alt text", () => { @@ -30,19 +22,14 @@ describe("Course Component", () => { const thumbnail = screen.getByRole("img"); expect(thumbnail).toHaveAttribute("src", mockCourse.thumbnail); - expect(thumbnail).toHaveAttribute("alt", mockCourse.title); + expect(thumbnail).toHaveAttribute("alt", mockCourse.name); }); it("renders with correct structure", () => { const { container } = render(); - // Check if the main article exists expect(container.querySelector("article")).toBeDefined(); - - // Check if the thumbnail container exists expect(container.querySelector("div > img")).toBeDefined(); - - // Check if the course info section exists expect(container.querySelector("div > h2")).toBeDefined(); expect(container.querySelector("div > p")).toBeDefined(); }); diff --git a/Frontend/src/components/CourseDetail/CourseDetail.module.scss b/Frontend/src/components/CourseDetail/CourseDetail.module.scss index 4e3e6c4..3a9625e 100644 --- a/Frontend/src/components/CourseDetail/CourseDetail.module.scss +++ b/Frontend/src/components/CourseDetail/CourseDetail.module.scss @@ -1,4 +1,3 @@ -@import '../../styles/vars.scss'; .container { max-width: 1200px; diff --git a/Frontend/src/components/CourseDetail/CourseDetail.tsx b/Frontend/src/components/CourseDetail/CourseDetail.tsx index 1b0c2b0..a79901c 100644 --- a/Frontend/src/components/CourseDetail/CourseDetail.tsx +++ b/Frontend/src/components/CourseDetail/CourseDetail.tsx @@ -8,14 +8,6 @@ interface CourseDetailComponentProps { } export const CourseDetailComponent: FC = ({ course }) => { - const formatDuration = (duration: number) => { - const hours = Math.floor(duration / 3600); - const minutes = Math.floor((duration % 3600) / 60); - return `${hours}h ${minutes}m`; - }; - - const totalDuration = course.classes.reduce((acc, cls) => acc + cls.duration, 0); - return (
@@ -25,14 +17,12 @@ export const CourseDetailComponent: FC = ({ course }
- {course.title} + {course.name}
-

{course.title}

-

Por {course.teacher}

+

{course.name}

{course.description}

- Duración total: {formatDuration(totalDuration)} {course.classes.length} clases
@@ -42,14 +32,13 @@ export const CourseDetailComponent: FC = ({ course }

Contenido del curso

{course.classes.map((cls, index) => ( - +
{(index + 1).toString().padStart(2, "0")}
-

{cls.title}

+

{cls.name}

{cls.description}

- {formatDuration(cls.duration)}
- +
))}
diff --git a/Frontend/src/styles/vars.scss b/Frontend/src/styles/vars.scss index 560968c..27943b8 100644 --- a/Frontend/src/styles/vars.scss +++ b/Frontend/src/styles/vars.scss @@ -1,3 +1,5 @@ +@use "sass:map"; + // Color Tokens $colors: ( // Primary Colors @@ -17,7 +19,7 @@ $colors: ( // Function to get color values @function color($color-name) { - @return map-get($colors, $color-name); + @return map.get($colors, $color-name); } // Usage example: diff --git a/Frontend/src/types/index.ts b/Frontend/src/types/index.ts index d74d9a4..bc87e4d 100644 --- a/Frontend/src/types/index.ts +++ b/Frontend/src/types/index.ts @@ -1,9 +1,8 @@ // Course types export interface Course { id: number; - title: string; - teacher: string; - duration: number; + name: string; + description: string; thumbnail: string; slug: string; } @@ -11,16 +10,14 @@ export interface Course { // Class types export interface Class { id: number; - title: string; + name: string; description: string; - video: string; - duration: number; slug: string; } // Course Detail type export interface CourseDetail extends Course { - description: string; + teacher_id: number[]; classes: Class[]; }