Skip to content

Latest commit

 

History

History
398 lines (321 loc) · 14.2 KB

File metadata and controls

398 lines (321 loc) · 14.2 KB

Platziflix - Proyecto Multi-plataforma

Visión General

Platziflix es una plataforma de cursos online estilo Netflix con arquitectura multi-plataforma:

  • Backend: API REST con FastAPI + PostgreSQL (puerto 8000)
  • Frontend: Aplicación web con Next.js 15 (puerto 3000)
  • Mobile Android: App nativa Kotlin + Jetpack Compose
  • Mobile iOS: App nativa Swift + SwiftUI

El Backend es la única fuente de verdad de datos. Toda la UI (web y mobile) consume exclusivamente la API REST.


Estructura del Proyecto

claude-code/
├── CLAUDE.md
├── Backend/
│   ├── app/
│   │   ├── main.py                    # FastAPI app + todos los endpoints
│   │   ├── test_main.py               # Tests de endpoints principales
│   │   ├── alembic.ini
│   │   ├── alembic/versions/          # Migraciones de DB
│   │   ├── core/config.py             # Settings con Pydantic
│   │   ├── db/
│   │   │   ├── base.py                # Engine + SessionLocal + get_db()
│   │   │   └── seed.py                # Datos de prueba
│   │   ├── models/                    # SQLAlchemy models
│   │   ├── schemas/rating.py          # Pydantic schemas para ratings
│   │   ├── services/course_service.py # Lógica de negocio
│   │   └── tests/                     # Tests de integración y servicio
│   ├── specs/                         # Contratos y especificaciones API
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── Makefile
│   └── pyproject.toml
├── Frontend/
│   └── src/
│       ├── app/                       # Next.js App Router
│       │   ├── page.tsx               # Home (listado de cursos)
│       │   ├── course/[slug]/page.tsx # Detalle de curso
│       │   └── classes/[class_id]/    # Reproductor de clase
│       ├── components/
│       │   ├── Course/                # Card de curso
│       │   ├── CourseDetail/          # Header/detalle de curso
│       │   ├── StarRating/            # Estrellas de rating
│       │   └── VideoPlayer/           # Reproductor HTML5
│       ├── services/ratingsApi.ts     # Integración API ratings
│       ├── styles/                    # vars.scss + reset.scss globales
│       └── types/                     # Interfaces TypeScript
└── Mobile/
    ├── PlatziFlixAndroid/             # Clean Architecture + Jetpack Compose
    └── PlatziFlixiOS/                 # Domain-driven + SwiftUI

Stack Tecnológico

Capa Tecnología Versión
Backend Framework FastAPI ≥0.104
Backend Runtime Python ≥3.11
ORM SQLAlchemy 2.0
Migraciones Alembic ≥1.13
Base de Datos PostgreSQL 15
Container Docker + Docker Compose -
Dep. Management (BE) UV -
Frontend Framework Next.js 15.3.3
UI Library React 19.0
Lenguaje (FE) TypeScript strict 5.x
Estilos SCSS + CSS Modules -
Testing (FE) Vitest + React Testing Library 3.x / 16.x
Android UI Jetpack Compose + Material3 -
Android Network Retrofit + OkHttp 2.9 / 4.12
Android Images Coil 2.5
iOS UI SwiftUI + Combine -
iOS Network URLSession + Codable (nativo) -

Modelo de Datos

Entidades y Campos

Teacher
├── id (PK)
├── name (str, NOT NULL)
├── email (str, UNIQUE, indexed)
└── [created_at, updated_at, deleted_at]  ← heredado de BaseModel

Course
├── id (PK)
├── name (str)
├── description (text)
├── thumbnail (str, URL)
├── slug (str, UNIQUE, indexed)  ← usado para URLs SEO-friendly
├── teachers  → M:M via course_teachers
├── lessons   → 1:M (cascade delete)
├── ratings   → 1:M (cascade delete)
├── average_rating  [computed property]
└── total_ratings   [computed property]

Lesson
├── id (PK)
├── course_id (FK → courses, indexed)
├── name (str)
├── description (text)
├── slug (str, indexed)
├── video_url (str)
└── [timestamps]

CourseRating
├── id (PK)
├── course_id (FK → courses, indexed)
├── user_id (int, indexed)  ← sin FK, sistema de usuarios externo
├── rating (int, CHECK: 1-5)
├── UNIQUE(course_id, user_id, deleted_at)  ← soporta soft delete
└── [timestamps]

course_teachers [tabla asociativa]
├── course_id (FK, PK)
└── teacher_id (FK, PK)

Soft Delete

Todas las entidades heredan deleted_at (nullable). Los registros borrados no se eliminan físicamente.


API Endpoints Completos

Base URL: http://localhost:8000

Método Ruta Descripción Respuesta
GET / Bienvenida 200
GET /health Health check + DB 200
GET /courses Lista cursos activos 200 [Course]
GET /courses/{slug} Detalle curso + teachers + lessons 200/404
GET /classes/{class_id} Detalle lección por ID 200/404
POST /courses/{course_id}/ratings Crear/actualizar rating 201/400/404
GET /courses/{course_id}/ratings Ratings activos del curso 200/404
GET /courses/{course_id}/ratings/stats Estadísticas agregadas 200/404
GET /courses/{course_id}/ratings/user/{user_id} Rating de usuario específico 200/204
PUT /courses/{course_id}/ratings/{user_id} Actualizar rating 200/400/404
DELETE /courses/{course_id}/ratings/{user_id} Soft delete rating 204/404

Shapes de Respuesta Clave

GET /courses → array de:

{ "id", "name", "description", "thumbnail", "slug", "average_rating", "total_ratings" }

GET /courses/{slug} → objeto con:

{ ...course, "teacher_id": [...], "classes": [...], "rating_distribution": {1:n, 2:n, ...}, "average_rating", "total_ratings" }

Rating Request Body:

{ "user_id": int (>0), "rating": int (1-5) }

Rating Stats Response:

{ "average_rating": float, "total_ratings": int, "rating_distribution": {1: int, 2: int, 3: int, 4: int, 5: int} }

Arquitectura Backend

Capas

Request → FastAPI Router (main.py)
        → Dependency Injection (get_db, get_course_service)
        → CourseService (services/course_service.py)  ← lógica de negocio
        → SQLAlchemy ORM (models/)
        → PostgreSQL

Patrones

  • Service Layer: CourseService concentra toda la lógica de negocio
  • Dependency Injection: FastAPI Depends() para DB session y servicios
  • Soft Delete: deleted_at en lugar de DELETE físico
  • Eager Loading: joinedload() en get_course_by_slug para evitar N+1
  • Agregación en DB: Rating stats calculados con SQL GROUP BY

Migraciones (Alembic)

  • d18a08253457 — Schema inicial: teachers, courses, lessons, course_teachers
  • 0e3a8766f785 — Tabla course_ratings con CHECK constraint

Arquitectura Frontend

Flujo de Datos

Server Component (page.tsx)
  → fetch('http://localhost:8000/...')
  → Render SSR
  → Hydration en cliente

Las páginas de listado y detalle son Server Components. El ratingsApi.ts es para llamadas cliente.

Componentes Principales

Componente Ubicación Props Clave
Course components/Course/ id, name, description, thumbnail, average_rating, total_ratings
CourseDetail components/CourseDetail/ course: CourseDetail (incluye classes[])
StarRating components/StarRating/ rating, totalRatings?, showCount?, size?, readonly?
VideoPlayer components/VideoPlayer/ src, title

Tipos TypeScript Centrales

// src/types/index.ts
interface Course { id, name, description, thumbnail, slug, average_rating?, total_ratings? }
interface Class { id, title, description, video, duration (seconds), slug }
interface CourseDetail extends Course { classes: Class[] }

// src/types/rating.ts
interface CourseRating { id, course_id, user_id, rating (1-5), created_at, updated_at }
interface RatingStats { average_rating, total_ratings }
type RatingState = 'idle' | 'loading' | 'success' | 'error'
class ApiError extends Error { status, code?, details? }

Estilos

  • Variables SCSS en src/styles/vars.scss — auto-importadas en todos los .module.scss via next.config.ts
  • Color primario Platzi: #ff2d2d
  • Breakpoint responsive: max-width: 768px

Arquitectura Mobile

Android (PlatziFlixAndroid)

Arquitectura: Clean Architecture en 3 capas

Presentation (Compose + ViewModel + MVI)
  ↓ Domain Models
Domain (Interfaces Repository + Models puros)
  ↑ DTOs mapeados
Data (Retrofit + DTOs + Mappers + RemoteRepository)

Archivos clave:

  • MainActivity.kt — Entry point, inyecta ViewModel manualmente via AppModule
  • CourseListViewModel.kt — MVI con StateFlow<CourseListUiState> + handleEvent()
  • ApiService.kt — Interface Retrofit: GET /courses
  • NetworkModule.kt — Singleton, base URL http://10.0.2.2:8000/ (emulador usa 10.0.2.2, no localhost)
  • RemoteCourseRepository.kt — Implementación real, retorna Result<List<Course>>
  • MockCourseRepository.kt — Para testing, simula delay y fallo aleatorio 10%
  • CourseMapper.ktCourseDTO → Course

Estado UI:

data class CourseListUiState(isLoading, courses, error, isRefreshing)
sealed class CourseListUiEvent { LoadCourses, RefreshCourses, ClearError }

Pantallas Compose:

  • CourseListScreen — Scaffold + LargeTopAppBar + LazyColumn
  • CourseCard — Card con thumbnail 16:9 via Coil + AsyncImage

Nota: Navegación al detalle de curso está marcada como TODO.

iOS (PlatziFlixiOS)

Arquitectura: Domain-driven con protocolos Swift

Presentation (SwiftUI Views + ViewModel @MainActor)
  ↓ Domain Models (Struct Identifiable)
Domain (Protocol CourseRepositoryProtocol + Models)
  ↑ DTOs + Mappers
Data (NetworkManager → URLSession + Codable)

Archivos clave:

  • CourseListViewModel.swift@MainActor ObservableObject, async/await + Combine para debounce búsqueda
  • RemoteCourseRepository.swift — Implementa CourseRepositoryProtocol
  • CourseAPIEndpoints.swift — Enum con getAllCourses / getCourseBySlug base URL http://localhost:8000
  • NetworkManager.swift — Singleton URLSession wrapper
  • NetworkError.swift — Errores tipados con mensajes en español

Vistas SwiftUI:

  • CourseListViewNavigationView + LazyVStack + búsqueda + pull-to-refresh
  • CourseCardViewAsyncImage 16:9 + nombre + descripción
  • DesignSystem.swift — Constantes de spacing, colores, tipografía

No usa dependencias externas — solo frameworks nativos iOS (Foundation, SwiftUI, Combine).


Comandos de Desarrollo

Backend (Docker obligatorio)

cd Backend
make start              # docker-compose up -d
make stop               # docker-compose down
make restart            # docker-compose restart
make build              # docker-compose build
make logs               # docker-compose logs -f
make clean              # docker-compose down -v --rmi all
make migrate            # alembic upgrade head (dentro del contenedor)
make create-migration   # crear nueva migración (prompt interactivo)
make seed               # poblar datos de prueba
make seed-fresh         # limpiar y repoblar
make help               # ver todos los comandos

IMPORTANTE: Antes de ejecutar cualquier comando de Backend, verificar que el contenedor Docker esté corriendo (make start). Revisar el Makefile para los comandos disponibles.

Frontend

cd Frontend
yarn dev          # Servidor desarrollo (turbopack)
yarn build        # Build producción
yarn start        # Iniciar producción
yarn test         # Vitest
yarn lint         # ESLint

URLs del Sistema

Servicio URL
Backend API http://localhost:8000
API Docs (Swagger) http://localhost:8000/docs
Frontend Web http://localhost:3000
Android emulador → Backend http://10.0.2.2:8000
iOS simulator → Backend http://localhost:8000

Testing

Backend

  • app/test_main.py — Tests endpoints raíz, health, cursos (mock del servicio)
  • app/tests/test_rating_endpoints.py — Integración endpoints rating
  • app/tests/test_course_rating_service.py — Unit tests servicio
  • app/tests/test_rating_db_constraints.py — Constraints de DB

Frontend (Vitest + jsdom)

  • Course.test.tsx — Renderizado de card (3 casos)
  • StarRating.test.tsx — Rendering, accesibilidad, variantes de tamaño (~45 casos)
  • VideoPlayer.test.tsx — Elemento video básico
  • classes/[class_id]/page.test.tsx — SSR de página con mock de fetch

Android

  • CourseListViewModelTest.kt — JUnit + coroutines test

iOS

  • PlatziFlixiOSTests.swift — XCTest estándar

Consideraciones de Desarrollo

  1. Docker obligatorio para el backend — DB y API corren en containers
  2. API REST es la única fuente de datos para Frontend y Mobile
  3. TypeScript strict en Frontend — no usar any
  4. Testing requerido para nuevas funcionalidades
  5. Migraciones automáticas para cualquier cambio de schema de DB — nunca modificar la DB directamente
  6. Soft delete en todas las entidades — nunca hacer DELETE físico
  7. Snake_case en Python/SQL, camelCase en JS/TS, PascalCase en Swift/Kotlin
  8. SCSS vars auto-importadas — no importar vars.scss manualmente en módulos
  9. Android emulador usa 10.0.2.2 para referirse al localhost del host
  10. Idioma UI: Español (lang="es", mensajes de error en español)

Funcionalidades Implementadas

  • Catálogo de cursos con grid estilo Netflix
  • Detalle de cursos (profesores, lecciones, clases)
  • Navegación por slug SEO-friendly
  • Reproductor de video integrado (HTML5)
  • Sistema de ratings 1-5 estrellas con estadísticas
  • Distribución de ratings por estrella
  • Health checks de API y DB
  • Apps móviles nativas (Android lista, iOS con búsqueda y pull-to-refresh)
  • Soft delete en entidades de ratings
  • Testing en todos los niveles del stack