import Foundation // MARK: - Manga Model /// Representa la información completa de un manga. /// /// `Manga` es una estructura inmutable que contiene toda la información relevante /// sobre un manga, incluyendo título, descripción, géneros, estado de publicación /// y metadatos adicionales como la URL de la imagen de portada. /// /// Conforma a `Codable` para serialización/deserialización automática, /// `Identifiable` para uso en listas de SwiftUI, y `Hashable` para comparaciones /// y uso en sets. /// /// # Example /// ```swift /// let manga = Manga( /// slug: "one-piece_1695365223767", /// title: "One Piece", /// description: "La historia de Monkey D. Luffy y su tripulación...", /// genres: ["Acción", "Aventura", "Comedia"], /// status: "PUBLICANDOSE", /// url: "https://manhwaweb.com/manga/one-piece_1695365223767", /// coverImage: "https://example.com/cover.jpg" /// ) /// print(manga.displayStatus) // "En publicación" /// ``` struct Manga: Codable, Identifiable, Hashable { /// Identificador único del manga (computed, igual al slug) let id: String { slug } /// Slug único usado en URLs del sitio web let slug: String /// Título del manga let title: String /// Descripción o sinopsis del manga let description: String /// Array de géneros literarios del manga let genres: [String] /// Estado de publicación (crudo, sin traducir) let status: String /// URL completa del manga en el sitio web let url: String /// URL de la imagen de portada (opcional) let coverImage: String? /// Coding keys para mapeo JSON/codificación personalizada enum CodingKeys: String, CodingKey { case slug, title, description, genres, status, url, coverImage } /// Estado de publicación formateado para mostrar en la UI /// /// Traduce los estados crudos del sitio web a formato legible para el usuario. /// /// # Mapeos /// - `"PUBLICANDOSE"` → `"En publicación"` /// - `"FINALIZADO"` → `"Finalizado"` /// - `"EN_PAUSA"`, `"EN_ESPERA"` → `"En pausa"` /// - Otro → retorna el valor original sin modificar /// /// - Returns: String con el estado traducido y formateado var displayStatus: String { switch status { case "PUBLICANDOSE": return "En publicación" case "FINALIZADO": return "Finalizado" case "EN_PAUSA", "EN_ESPERA": return "En pausa" default: return status } } } // MARK: - Chapter Model /// Representa un capítulo individual de un manga. /// /// `Chapter` contiene información sobre un capítulo específico, incluyendo /// su número, título, URL, y metadatos de lectura como si ha sido leído, /// descargado, y la última página leída. /// /// Las propiedades `isRead`, `isDownloaded`, y `lastReadPage` son mutables /// para actualizar el estado de lectura del usuario. /// /// # Example /// ```swift /// var chapter = Chapter( /// number: 1, /// title: "El inicio de la aventura", /// url: "https://manhwaweb.com/leer/one-piece/1", /// slug: "one-piece/1" /// ) /// chapter.isRead = true /// chapter.lastReadPage = 15 /// print(chapter.displayNumber) // "Capítulo 1" /// ``` struct Chapter: Codable, Identifiable, Hashable { /// Identificador único del capítulo (computed, igual al número) let id: Int { number } /// Número del capítulo let number: Int /// Título del capítulo let title: String /// URL completa del capítulo en el sitio web let url: String /// Slug para identificar el capítulo en URLs let slug: String /// Indica si el capítulo ha sido marcado como leído var isRead: Bool = false /// Indica si el capítulo ha sido descargado localmente var isDownloaded: Bool = false /// Última página leída por el usuario var lastReadPage: Int = 0 /// Número de capítulo formateado para mostrar en la UI /// /// - Returns: String con formato "Capítulo {número}" var displayNumber: String { return "Capítulo \(number)" } /// Progreso de lectura como Double para ProgressViews /// /// - Returns: Double representando la última página leída var progress: Double { return Double(lastReadPage) } } // MARK: - Manga Page (Image) /// Representa una página individual (imagen) de un capítulo. /// /// `MangaPage` contiene la URL de una imagen de manga y su posición /// dentro del capítulo. Puede marcar si la imagen está cacheada localmente /// para evitar descargas redundantes. /// /// # Example /// ```swift /// let page = MangaPage(url: "https://example.com/page1.jpg", index: 0) /// print(page.id) // URL completa /// ``` struct MangaPage: Codable, Identifiable, Hashable { /// Identificador único de la página (computed, igual a la URL) let id: String { url } /// URL completa de la imagen let url: String /// Índice de la página en el capítulo (0-based) let index: Int /// Indica si la imagen está cacheada en almacenamiento local var isCached: Bool = false /// URL de la versión thumbnail de la imagen /// /// Actualmente retorna la misma URL. Futura implementación puede /// retornar una versión optimizada/miniatura de la imagen. /// /// - Returns: URL del thumbnail (o de la imagen completa) var thumbnailURL: String { // Para thumbnail podríamos usar una versión más pequeña return url } } // MARK: - Reading Progress /// Almacena el progreso de lectura de un usuario. /// /// `ReadingProgress` registra qué página de qué capítulo de qué manga /// leyó el usuario, junto con un timestamp para sincronización. /// /// # Example /// ```swift /// let progress = ReadingProgress( /// mangaSlug: "one-piece", /// chapterNumber: 1, /// pageNumber: 15, /// timestamp: Date() /// ) /// if progress.isCompleted { /// print("Capítulo completado") /// } /// ``` struct ReadingProgress: Codable { /// Slug del manga que se está leyendo let mangaSlug: String /// Número del capítulo let chapterNumber: Int /// Número de página actual let pageNumber: Int /// Fecha y hora en que se guardó el progreso let timestamp: Date /// Indica si el capítulo se considera completado /// /// Un capítulo se considera completado si el usuario ha leído /// más de 5 páginas. Este umbral evita marcar como completados /// capítulos que el usuario solo hojeó. /// /// - Returns: `true` si `pageNumber > 5`, `false` en caso contrario var isCompleted: Bool { // Considerar completado si leyó más de 5 páginas return pageNumber > 5 } } // MARK: - Downloaded Chapter /// Representa un capítulo descargado localmente en el dispositivo. /// /// `DownloadedChapter` contiene metadata sobre un capítulo que ha sido /// descargado, incluyendo todas sus páginas, fecha de descarga, y tamaño /// total en disco. /// /// # Example /// ```swift /// let downloaded = DownloadedChapter( /// mangaSlug: "one-piece", /// mangaTitle: "One Piece", /// chapterNumber: 1, /// pages: [page1, page2, page3], /// downloadedAt: Date() /// ) /// print(downloaded.displayTitle) // "One Piece - Capítulo 1" /// ``` struct DownloadedChapter: Codable, Identifiable { /// Identificador único compuesto por manga-slug y número de capítulo let id: String { "\(mangaSlug)-chapter\(chapterNumber)" } /// Slug del manga let mangaSlug: String /// Título del manga let mangaTitle: String /// Número del capítulo let chapterNumber: Int /// Array de páginas del capítulo let pages: [MangaPage] /// Fecha y hora de descarga let downloadedAt: Date /// Tamaño total del capítulo en bytes var totalSize: Int64 = 0 /// Título formateado para mostrar en la UI /// /// - Returns: String con formato "{MangaTitle} - Capítulo {number}" var displayTitle: String { "\(mangaTitle) - Capítulo \(chapterNumber)" } } // MARK: - API Response Models /// Respuesta de API que contiene una lista de mangas. /// /// Usado para respuestas paginadas o listas completas de mangas /// desde un backend opcional. struct MangaListResponse: Codable { /// Array de mangas en la respuesta let mangas: [Manga] /// Número total de mangas (útil para paginación) let total: Int } /// Respuesta de API que contiene la lista de capítulos de un manga. struct ChapterListResponse: Codable { /// Array de capítulos del manga let chapters: [Chapter] /// Slug del manga al que pertenecen los capítulos let mangaSlug: String } /// Respuesta de API con las URLs de imágenes de un capítulo. struct ChapterImagesResponse: Codable { /// Array de URLs de imágenes en orden let images: [String] /// Slug del capítulo let chapterSlug: String }