# Arquitectura de MangaReader Este documento describe la arquitectura general del proyecto MangaReader, explicando cómo funcionan los componentes Backend y iOS App, y cómo fluyen los datos desde el scraping hasta el display en pantalla. ## Tabla de Contenidos - [Visión General](#visión-general) - [Arquitectura del Sistema](#arquitectura-del-sistema) - [Arquitectura de la App iOS](#arquitectura-de-la-app-ios) - [Flujo de Datos](#flujo-de-datos) - [Patrones de Diseño](#patrones-de-diseño) - [Diagramas de Secuencia](#diagramas-de-secuencia) ## Visión General MangaReader es una aplicación nativa de iOS para leer manga sin publicidad. El proyecto consta de dos componentes opcionales: 1. **Backend (Opcional)**: Servidor Node.js con Express que realiza scraping usando Puppeteer 2. **iOS App**: Aplicación nativa SwiftUI que puede hacer scraping localmente usando WKWebView ``` ┌─────────────────────────────────────────────────────────────────┐ │ MangaReader │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Backend │ │ iOS App │ │ │ │ (Opcional) │ │ (Principal) │ │ │ │ │ │ │ │ │ │ • Node.js │ │ • SwiftUI │ │ │ │ • Express │ │ • WKWebView │ │ │ │ • Puppeteer │ │ • Core Data │ │ │ └─────────────────┘ └─────────────────┘ │ │ ▲ │ │ │ │ │ │ │ └──────────────────────────────────┘ │ │ Scraping Independiente │ └─────────────────────────────────────────────────────────────────┘ ``` ## Arquitectura del Sistema ### Componentes Principales #### 1. Backend (Opcional) El backend es una API REST opcional que puede actuar como intermediario: ``` backend/ ├── scraper.js # Scraper con Puppeteer ├── server.js # API REST con Express └── package.json ``` **Responsabilidades:** - Realizar scraping de manhwaweb.com - Servir datos vía API REST - Cachear respuestas para mejorar rendimiento **API Endpoints:** - `GET /api/health` - Health check - `GET /api/manga/:slug` - Información de un manga - `GET /api/manga/:slug/chapters` - Lista de capítulos - `GET /api/chapter/:slug/images` - Imágenes de un capítulo **Nota Importante**: El backend es completamente opcional. La app iOS está diseñada para funcionar de manera autónoma sin necesidad del backend. #### 2. iOS App (Principal) La aplicación iOS es el componente principal y puede operar independientemente: ``` ios-app/ ├── MangaReaderApp.swift # Entry point ├── Info.plist └── Sources/ ├── Models/ # Modelos de datos │ └── Manga.swift ├── Services/ # Lógica de negocio │ ├── ManhwaWebScraper.swift │ └── StorageService.swift └── Views/ # UI SwiftUI ├── ContentView.swift ├── MangaDetailView.swift └── ReaderView.swift ``` ## Arquitectura de la App iOS ### MVVM Pattern (Model-View-ViewModel) La app iOS sigue el patrón MVVM para separar la UI de la lógica de negocio: ``` ┌─────────────────────────────────────────────────────────────────┐ │ MVVM Architecture │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────────────┐ ┌─────────┐ │ │ │ View │◄────────│ ViewModel │◄────────│ Model │ │ │ │(SwiftUI)│ │ (Observable) │ │(Struct) │ │ │ └─────────┘ └─────────────────┘ └─────────┘ │ │ ▲ │ │ │ │ │ │ │ │ │ └───────────────────────┴───────────────────────────┘ │ │ Data Binding & Commands │ └─────────────────────────────────────────────────────────────────┘ ``` ### Estructura de Componentes #### 1. Models (Datos) **Ubicación**: `ios-app/Sources/Models/Manga.swift` Los modelos son estructuras inmutables que representan los datos: ```swift - Manga: Información del manga (título, descripción, géneros, estado) - Chapter: Capítulo individual (número, título, URL, estado de lectura) - MangaPage: Página individual del capítulo (URL de imagen, índice) - ReadingProgress: Progreso de lectura del usuario - DownloadedChapter: Capítulo descargado localmente ``` **Características:** - Inmutables (`struct`) - Conformes a `Codable` para serialización - Conformes a `Identifiable` para SwiftUI - Conformes a `Hashable` para comparaciones #### 2. Services (Lógica de Negocio) **Ubicación**: `ios-app/Sources/Services/` ##### ManhwaWebScraper.swift Responsable del scraping de contenido web: ```swift class ManhwaWebScraper: NSObject, ObservableObject { // Singleton instance static let shared = ManhwaWebScraper() // Funciones principales: func scrapeMangaInfo(mangaSlug: String) async throws -> Manga func scrapeChapters(mangaSlug: String) async throws -> [Chapter] func scrapeChapterImages(chapterSlug: String) async throws -> [String] } ``` **Características:** - Usa `WKWebView` para ejecutar JavaScript - Implementa `async/await` para operaciones asíncronas - Patrón Singleton para compartir instancia - Manejo de errores con `ScrapingError` ##### StorageService.swift Responsable del almacenamiento local: ```swift class StorageService { // Singleton instance static let shared = StorageService() // Gestión de favoritos: func getFavorites() -> [String] func saveFavorite(mangaSlug: String) func removeFavorite(mangaSlug: String) // Gestión de progreso: func saveReadingProgress(_ progress: ReadingProgress) func getReadingProgress(mangaSlug: String, chapterNumber: Int) -> ReadingProgress? // Gestión de descargas: func saveDownloadedChapter(_ chapter: DownloadedChapter) func getDownloadedChapters() -> [DownloadedChapter] // Gestión de imágenes: func saveImage(_ image: UIImage, ...) async throws -> URL func loadImage(...) -> UIImage? } ``` **Características:** - Almacena favoritos en `UserDefaults` - Almacena progreso en `UserDefaults` - Guarda imágenes en el sistema de archivos - Usa `FileManager` para gestión de archivos #### 3. ViewModels (Presentación) **Ubicación**: Integrados en los archivos de Views Los ViewModels coordinan entre Services y Views: ```swift @MainActor class MangaListViewModel: ObservableObject { @Published var mangas: [Manga] = [] @Published var isLoading = false private let scraper = ManhwaWebScraper.shared private let storage = StorageService.shared func loadMangas() async func addManga(_ slug: String) async } ``` **Responsabilidades:** - Mantener estado de la UI - Transformar datos para presentación - Manejar lógica de navegación - Coordinar llamadas a servicios #### 4. Views (UI) **Ubicación**: `ios-app/Sources/Views/` ##### ContentView.swift - Vista principal de la app - Lista de mangas con filtros - Búsqueda y añadir manga ##### MangaDetailView.swift - Detalle de un manga específico - Lista de capítulos - Descarga de capítulos ##### ReaderView.swift - Lector de imágenes - Gestos de zoom y pan - Configuración de lectura ## Flujo de Datos ### 1. Flujo de Scraping de Manga ``` Usuario ingresa slug │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ContentView -> MangaListViewModel.addManga(slug) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ManhwaWebScraper.scrapeMangaInfo(mangaSlug) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ WKWebView carga URL de manhwaweb.com │ │ https://manhwaweb.com/manga/{slug} │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ JavaScript ejecutado en WKWebView: │ │ - Extrae título, descripción, géneros │ │ - Extrae estado, imagen de portada │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ Datos parseados a struct Manga │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ViewModel actualiza @Published var mangas │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ SwiftUI detecta cambio y re-renderiza UI │ └───────────────────────────────────────────────────────────────┘ ``` ### 2. Flujo de Lectura de Capítulo ``` Usuario selecciona capítulo │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ MangaDetailView -> ReaderView(manga, chapter) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ReaderViewModel.loadPages() │ └───────────────────────────────────────────────────────────────┘ │ ├──► ¿Capítulo descargado? │ │ │ SÍ │ NO │ ▼ │ ┌─────────────────────────────────────────────────┐ │ │ StorageService.getDownloadedChapter() │ │ │ Cargar páginas locales │ │ └─────────────────────────────────────────────────┘ │ │ │ └──────────────────┐ │ │ └─────────────────────────────┼──────┐ │ │ ▼ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ManhwaWebScraper.scrapeChapterImages(chapterSlug) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ WKWebView carga URL del capítulo │ │ https://manhwaweb.com/leer/{slug} │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ JavaScript extrae URLs de imágenes: │ │ - Selecciona todas las etiquetas │ │ - Filtra elementos de UI (avatars, icons) │ │ - Elimina duplicados │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ Array de strings con URLs de imágenes │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ Convertir a [MangaPage] y mostrar en ReaderView │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ReaderView muestra imágenes con AsyncImage │ │ Cache automático de imágenes en visualización │ └───────────────────────────────────────────────────────────────┘ ``` ### 3. Flujo de Guardado de Progreso ``` Usuario navega a página X │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ReaderViewModel.currentPage cambia a X │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ReaderViewModel.saveProgress() │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ Crear ReadingProgress(mangaSlug, chapterNumber, pageNumber) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ StorageService.saveReadingProgress(progress) │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ JSONEncoder codifica a Data │ └───────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────┐ │ UserDefaults.set(data, forKey: "readingProgress") │ └───────────────────────────────────────────────────────────────┘ ``` ## Patrones de Diseño ### 1. Singleton Pattern **Uso**: Services compartidos ```swift class StorageService { static let shared = StorageService() private init() { ... } } class ManhwaWebScraper { static let shared = ManhwaWebScraper() private init() { ... } } ``` **Beneficios:** - Unica instancia compartida en toda la app - Reduce consumo de memoria - Facilita acceso desde cualquier View/ViewModel ### 2. MVVM (Model-View-ViewModel) **Uso**: Arquitectura general de la app **Separación de responsabilidades:** - **Model**: Datos puras (`struct`, `Codable`) - **View**: UI pura (`SwiftUI`, reactive) - **ViewModel**: Lógica de presentación (`ObservableObject`) **Beneficios:** - Testabilidad de ViewModels sin UI - Reutilización de ViewModels - Separación clara de concerns ### 3. Repository Pattern **Uso**: Abstracción de fuentes de datos ```swift class StorageService { // Abstrae UserDefaults, FileManager, etc. func getFavorites() -> [String] func saveFavorite(mangaSlug: String) } ``` **Beneficios:** - Interfaz unificada para diferentes storage - Fácil cambiar implementación - Centraliza lógica de persistencia ### 4. Async/Await Pattern **Uso**: Operaciones de scraping ```swift func scrapeMangaInfo(mangaSlug: String) async throws -> Manga { // Operación asíncrona try await loadURLAndWait(url) let info = try await webView.evaluateJavaScript(...) return Manga(...) } ``` **Beneficios:** - Código asíncrono legible - Manejo de errores claro - No bloquea el hilo principal ### 5. Observable Object Pattern **Uso**: Reactividad en SwiftUI ```swift @MainActor class MangaListViewModel: ObservableObject { @Published var mangas: [Manga] = [] func loadMangas() async { mangas = ... // SwiftUI detecta cambio } } ``` **Beneficios:** - UI se actualiza automáticamente - Código declarativo - Menos código boilerplate ### 6. Factory Pattern (Implícito) **Uso**: Creación de modelos ```swift // Funciones estáticas que crean instancias Chapter(number: 1, title: "...", url: "...", slug: "...") MangaPage(url: "...", index: 0) ``` **Beneficios:** - Creación consistente de objetos - Validación en inicialización - Fácil de mantener ## Diagramas de Secuencia ### Secuencia 1: Agregar Manga ``` ┌─────────┐ ┌──────────────┐ ┌─────────────────┐ ┌────────────┐ │ Usuario │ │ ViewModel │ │ Scraper │ │ WKWebView │ └────┬────┘ └──────┬───────┘ └────────┬────────┘ └─────┬──────┘ │ │ │ │ │ ingresa slug │ │ │ │───────────────>│ │ │ │ │ │ │ │ │ scrapeMangaInfo() │ │ │ │────────────────────>│ │ │ │ │ │ │ │ │ load(URL) │ │ │ │───────────────────>│ │ │ │ │ │ │ │ wait 3 seconds │ │ │ │<───────────────────┤ │ │ │ │ │ │ │ evaluateJavaScript │ │ │ │───────────────────>│ │ │ │ (extrae datos) │ │ │ │<───────────────────┤ │ │ │ │ │ │ Manga │ │ │ │<────────────────────│ │ │ │ │ │ │ actualiza │ │ │ │ UI │ │ │ │<───────────────│ │ │ │ │ │ │ ┌────┴────┐ ┌──────┴───────┘ └────────┴────────┘ └─────┴──────┘ ``` ### Secuencia 2: Leer Capítulo ``` ┌─────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────┐ │ Usuario │ │ ReaderView │ │ ViewModel │ │ Storage │ └────┬────┘ └──────┬───────┘ └────────┬────────┘ └────┬─────┘ │ │ │ │ │ tap capítulo │ │ │ │───────────────>│ │ │ │ │ │ │ │ │ loadPages() │ │ │ │────────────────────>│ │ │ │ │ │ │ │ │ isDownloaded()? │ │ │ │──────────────────>│ │ │ │ │ │ │ │ NO │ │ │ │<──────────────────┤ │ │ │ │ │ │ │ scrapeChapterImages│ │ │ │ (via Scraper) │ │ │ │ │ │ │ [MangaPage] │ │ │ │<────────────────────│ │ │ │ │ │ │ muestra páginas│ │ │ │<───────────────│ │ │ │ │ │ │ ┌────┴────┐ ┌──────┴───────┘ └────────┴────────┘ └────┴─────┘ ``` ### Secuencia 3: Guardar Favorito ``` ┌─────────┐ ┌──────────────┐ ┌─────────────────┐ │ Usuario │ │ View │ │ StorageService │ └────┬────┘ └──────┬───────┘ └────────┬────────┘ │ │ │ │ tap corazón │ │ │───────────────>│ │ │ │ │ │ │ toggleFavorite() │ │ │────────────────────>│ │ │ │ │ │ │ getFavorites() │ │ │ (UserDefaults) │ │ │ │ │ │ saveFavorite() │ │ │ (UserDefaults) │ │ │ │ │ actualiza UI │ │ │<────────────────────│ │ │ │ ┌────┴────┘ ┌──────┴───────┘ └────────┴────────┘ ``` ## Decisiones de Arquitectura ### ¿Por qué WKWebView para scraping? 1. **JavaScript Rendering**: manhwaweb.com usa JavaScript para cargar contenido 2. **Sin dependencias externas**: No requiere librerías de terceros 3. **Aislamiento**: El scraping ocurre en contexto separado 4. **Control**: Full control sobre timeouts, cookies, headers ### ¿Por qué UserDefaults para favoritos/progreso? 1. **Simplicidad**: Datos pequeños y simples 2. **Sincronización**: iCloud sync automático disponible 3. **Rendimiento**: Lectura/escritura rápida 4. **Persistencia**: Survive app reinstalls (si iCloud) ### ¿Por qué FileManager para imágenes? 1. **Tamaño**: Imágenes pueden ser grandes (MBs) 2. **Performance**: Acceso directo a archivos 3. **Cache control**: Control manual de qué guardar 4. **Escalabilidad**: No limitado por UserDefaults ### ¿Por qué MVVM? 1. **SwiftUI nativo**: SwiftUI está diseñado para MVVM 2. **Testabilidad**: ViewModels testeables sin UI 3. **Reactibilidad**: `@Published` y `ObservableObject` 4. **Separación**: UI separada de lógica de negocio ## Consideraciones de Escalabilidad ### Futuras Mejoras 1. **Database**: Migrar de UserDefaults a Core Data o SQLite 2. **Background Tasks**: Descargas en background 3. **Caching Strategy**: LRU cache para imágenes 4. **Pagination**: Cargar capítulos bajo demanda 5. **Sync Service**: Sincronización entre dispositivos ### Rendimiento - **Lazy Loading**: Cargar imágenes bajo demanda - **Image Compression**: JPEG 80% calidad - **Request Batching**: Descargar páginas en paralelo - **Memory Management**: Liberar imágenes no visibles ## Seguridad ### Consideraciones 1. **No se almacenan credenciales**: La app no requiere login 2. **SSL Pinning**: Considerar para producción 3. **Input Validation**: Validar slugs antes de scraping 4. **Rate Limiting**: No sobrecargar el servidor objetivo --- **Última actualización**: Febrero 2026 **Versión**: 1.0.0