# 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 <img>                        │
│  - 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
