# Contributing to MangaReader

Gracias por tu interés en contribuir a MangaReader. Este documento proporciona una guía completa para contribuyentes, incluyendo cómo agregar nuevas fuentes de manga, modificar el scraper, estándares de código y testing.

## Tabla de Contenidos

- [Configuración del Entorno de Desarrollo](#configuración-del-entorno-de-desarrollo)
- [Cómo Agregar Nuevas Fuentes de Manga](#cómo-agregar-nuevas-fuentes-de-manga)
- [Cómo Modificar el Scraper](#cómo-modificar-el-scraper)
- [Estándares de Código](#estándares-de-código)
- [Testing](#testing)
- [Pull Request Guidelines](#pull-request-guidelines)
- [Troubleshooting](#troubleshooting)

---

## Configuración del Entorno de Desarrollo

### Requisitos Previos

1. **MacOS** con Xcode 15+
2. **iOS 15+** device o simulator
3. **Git** para control de versiones
4. **Cuenta de Developer** de Apple (opcional, para firmar)

### Pasos Iniciales

1. **Fork el repositorio** (si es proyecto open source)
2. **Clona tu fork**:
   ```bash
   git clone https://github.com/tu-usuario/MangaReader.git
   cd MangaReader/ios-app
   ```

3. **Abre el proyecto en Xcode**:
   ```bash
   open MangaReader.xcodeproj
   ```

4. **Configura el signing**:
   - Selecciona el proyecto en el sidebar
   - En "Signing & Capabilities", elige tu Team
   - Asegúrate que "Automatically manage signing" esté activado

5. **Ejecuta el proyecto**:
   - Selecciona un dispositivo o simulador
   - Presiona `Cmd + R`

---

## Cómo Agregar Nuevas Fuentes de Manga

MangaReader está diseñado para soportar múltiples fuentes de manga. Actualmente soporta manhwaweb.com, pero puedes agregar más fuentes siguiendo estos pasos.

### Arquitectura de Fuentes

Las fuentes se implementan usando un protocolo común:

```swift
protocol MangaSource {
    var name: String { get }
    var baseURL: String { get }

    func fetchMangaInfo(slug: String) async throws -> Manga
    func fetchChapters(mangaSlug: String) async throws -> [Chapter]
    func fetchChapterImages(chapterSlug: String) async throws -> [String]
}
```

### Paso 1: Crear el Scraper de la Nueva Fuente

Crea un nuevo archivo en `ios-app/Sources/Services/`:

**Ejemplo: `MangaTownScraper.swift`**

```swift
import Foundation
import WebKit

/// Scraper para mangatown.com
/// Implementa el protocolo MangaSource para agregar soporte de esta fuente
@MainActor
class MangaTownScraper: NSObject, ObservableObject, MangaSource {

    // MARK: - MangaSource Protocol

    let name = "MangaTown"
    let baseURL = "https://www.mangatown.com"

    // MARK: - Properties

    static let shared = MangaTownScraper()
    private var webView: WKWebView?

    // MARK: - Initialization

    private override init() {
        super.init()
        setupWebView()
    }

    // MARK: - Setup

    private func setupWebView() {
        let configuration = WKWebViewConfiguration()
        configuration.applicationNameForUserAgent = "Mozilla/5.0"

        webView = WKWebView(frame: .zero, configuration: configuration)
        webView?.navigationDelegate = self
    }

    // MARK: - MangaSource Implementation

    /// Obtiene la información de un manga desde MangaTown
    /// - Parameter slug: Identificador único del manga
    /// - Returns: Objeto Manga con información completa
    /// - Throws: ScrapingError si falla el scraping
    func fetchMangaInfo(slug: String) async throws -> Manga {
        guard let webView = webView else {
            throw ScrapingError.webViewNotInitialized
        }

        let url = URL(string: "\(baseURL)/manga/\(slug)")!
        try await loadURLAndWait(url, webView: webView)

        // Extraer información usando JavaScript
        let info: [String: Any] = try await webView.evaluateJavaScript("""
            (function() {
                // Implementación específica para MangaTown
                return {
                    title: document.querySelector('.manga-title')?.textContent || '',
                    description: document.querySelector('.manga-summary')?.textContent || '',
                    // ... más extracciones
                };
            })();
        """) as! [String: Any]

        return Manga(
            slug: slug,
            title: info["title"] as? String ?? "",
            description: info["description"] as? String ?? "",
            genres: [],
            status: "",
            url: url.absoluteString,
            coverImage: nil
        )
    }

    /// Obtiene los capítulos de un manga
    /// - Parameter mangaSlug: Slug del manga
    /// - Returns: Array de capítulos ordenados
    /// - Throws: ScrapingError si falla
    func fetchChapters(mangaSlug: String) async throws -> [Chapter] {
        // Implementación similar a scrapeChapters
        // ...
        return []
    }

    /// Obtiene las imágenes de un capítulo
    /// - Parameter chapterSlug: Slug del capítulo
    /// - Returns: Array de URLs de imágenes
    /// - Throws: ScrapingError si falla
    func fetchChapterImages(chapterSlug: String) async throws -> [String] {
        // Implementación similar a scrapeChapterImages
        // ...
        return []
    }

    // MARK: - Helper Methods

    private func loadURLAndWait(_ url: URL, webView: WKWebView, waitTime: Double = 3.0) async throws {
        try await withCheckedThrowingContinuation { continuation in
            webView.load(URLRequest(url: url))
            DispatchQueue.main.asyncAfter(deadline: .now() + waitTime) {
                continuation.resume()
            }
        }
    }
}

// MARK: - WKNavigationDelegate

extension MangaTownScraper: WKNavigationDelegate {
    nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // Navegación completada
    }

    nonisolated func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("Navigation failed: \(error.localizedDescription)")
    }
}
```

### Paso 2: Registrar la Nueva Fuente

Agrega la fuente al registro de fuentes disponibles. Crea un archivo `MangaSourceRegistry.swift`:

```swift
/// Registro de fuentes de manga disponibles
enum MangaSourceRegistry {
    static let allSources: [MangaSource] = [
        ManhwaWebScraper.shared,
        MangaTownScraper.shared,
    ]

    static func source(named name: String) -> MangaSource? {
        allSources.first { $0.name == name }
    }
}
```

### Paso 3: Actualizar la UI para Seleccionar Fuente

Modifica `ContentView.swift` para permitir selección de fuente:

```swift
// En MangaListViewModel
@Published var selectedSource: MangaSource = ManhwaWebScraper.shared

// En ContentView
Picker("Fuente", selection: $viewModel.selectedSource) {
    ForEach(MangaSourceRegistry.allSources, id: \.name) { source in
        Text(source.name).tag(source)
    }
}
```

### Paso 4: Testing de la Nueva Fuente

Crea tests para verificar que el scraper funciona correctamente:

```swift
import XCTest
@testable import MangaReader

class MangaTownScraperTests: XCTestCase {
    var scraper: MangaTownScraper!

    override func setUp() {
        super.setUp()
        scraper = MangaTownScraper.shared
    }

    func testFetchMangaInfo() async throws {
        let manga = try await scraper.fetchMangaInfo(slug: "one-piece")

        XCTAssertFalse(manga.title.isEmpty)
        XCTAssertFalse(manga.description.isEmpty)
    }

    func testFetchChapters() async throws {
        let chapters = try await scraper.fetchChapters(mangaSlug: "one-piece")

        XCTAssertFalse(chapters.isEmpty)
        XCTAssertTrue(chapters.first?.number == 1)
    }
}
```

---

## Cómo Modificar el Scraper

Si la estructura de manhwaweb.com cambia y el scraper deja de funcionar, sigue estos pasos para actualizarlo.

### Paso 1: Investigar la Nueva Estructura

1. Abre manhwaweb.com en Safari/Chrome
2. Abre Web Inspector (F12 o Cmd+Option+I)
3. Navega a un manga/capítulo
4. Inspecciona el HTML para encontrar los nuevos selectores

### Paso 2: Identificar Selectores Clave

Busca los siguientes elementos:

**Para Manga Info:**
- Selector del título (ej: `h1`, `.title`, `[class*="title"]`)
- Selector de la descripción (ej: `p`, `.description`)
- Selector de géneros (ej: `a[href*="/genero/"]`, `.genres a`)
- Selector de estado (ej: regex en body o `.status`)
- Selector de cover (ej: `.cover img`, `[class*="cover"] img`)

**Para Capítulos:**
- Selector de links (ej: `a[href*="/leer/"]`, `.chapter-link`)
- Cómo extraer el número de capítulo (regex o atributo)

**Para Imágenes:**
- Selector de imágenes (ej: `img`, `.page-image img`)
- Cómo distinguir imágenes de contenido de UI

### Paso 3: Actualizar el JavaScript

En `ManhwaWebScraper.swift`, actualiza el JavaScript en los métodos correspondientes:

**Ejemplo: Actualizar `scrapeMangaInfo`**

```swift
let mangaInfo: [String: Any] = try await webView.evaluateJavaScript("""
    (function() {
        // ACTUALIZAR: Nuevo selector de título
        let title = '';
        const titleEl = document.querySelector('.nuevo-selector-titulo');
        if (titleEl) {
            title = titleEl.textContent?.trim() || '';
        }

        // ACTUALIZAR: Nueva lógica de descripción
        let description = '';
        const descEl = document.querySelector('.nuevo-selector-desc');
        if (descEl) {
            description = descEl.textContent?.trim() || '';
        }

        // Resto de extracciones...
        return {
            title: title,
            description: description,
            // ...
        };
    })();
""") as! [String: Any]
```

### Paso 4: Probar los Cambios

1. Compila y ejecuta la app
2. Intenta agregar un manga existente
3. Verifica que la información se muestre correctamente
4. Intenta leer un capítulo
5. Verifica que las imágenes carguen

### Paso 5: Manejo de Errores

Agrega manejo de errores robusto:

```swift
// Agregar fallbacks
let titleEl = document.querySelector('.nuevo-selector') ||
               document.querySelector('.selector-respalgo') ||
               document.querySelector('h1'); // Último recurso

if (titleEl) {
    title = titleEl.textContent?.trim() || '';
}
```

### Consejos para Troubleshooting

1. **Incrementa el tiempo de espera** si la página carga lento:
   ```swift
   try await loadURLAndWait(url, waitForImages: true)
   // Aumenta el tiempo en loadURLAndWait
   ```

2. **Verifica que JavaScript esté habilitado** en WKWebView

3. **Revisa la consola** del WebView agregando logging:
   ```swift
   webView.evaluateJavaScript("console.log('Debug info')")
   ```

4. **Usa `try?`** en vez de `try!` temporalmente para evitar crashes:
   ```swift
   let info = try? webView.evaluateJavaScript(...) as? [String: Any]
   print("Info: \(info ?? [:])")
   ```

---

## Estándares de Código

### Swift Style Guide

Sigue las convenciones de Swift para código limpio y mantenible.

#### 1. Nomenclatura

**Clases y Structs**: PascalCase
```swift
class ManhwaWebScraper { }
struct Manga { }
```

**Propiedades y Métodos**: camelCase
```swift
var mangaTitle: String
func scrapeMangaInfo() { }
```

**Constantes Privadas**: camelCase con prefijo si es necesario
```swift
private let favoritesKey = "favoriteMangas"
```

#### 2. Organización de Código

Usa MARK comments para organizar:

```swift
class ManhwaWebScraper {
    // MARK: - Properties

    private var webView: WKWebView?

    // MARK: - Initialization

    init() { }

    // MARK: - Public Methods

    func scrapeMangaInfo() { }

    // MARK: - Private Methods

    private func loadURLAndWait() { }
}
```

#### 3. Documentación

Documenta todas las funciones públicas:

```swift
/// Obtiene la lista de capítulos de un manga
///
/// Este método carga la página del manga en un WKWebView,
/// ejecuta JavaScript para extraer los capítulos, y retorna
/// un array ordenado de manera descendente.
///
/// - Parameter mangaSlug: El slug único del manga
/// - Returns: Array de capítulos ordenados por número (descendente)
/// - Throws: `ScrapingError` si el WebView no está inicializado
///           o si falla la extracción de contenido
///
/// # Example
/// ```swift
/// do {
///     let chapters = try await scraper.scrapeChapters(mangaSlug: "one-piece")
///     print("Found \(chapters.count) chapters")
/// } catch {
///     print("Error: \(error)")
/// }
/// ```
func scrapeChapters(mangaSlug: String) async throws -> [Chapter] {
    // Implementación
}
```

#### 4. Async/Await

Usa async/await para código asíncrono:

```swift
// BUENO
func loadManga() async throws -> Manga {
    let manga = try await scraper.scrapeMangaInfo(slug: slug)
    return manga
}

// MAL (evitar completion handlers si es posible)
func loadManga(completion: @escaping (Result<Manga, Error>) -> Void) {
    // ...
}
```

#### 5. Error Handling

Usa errores tipados en vez de genéricos:

```swift
// BUENO
enum ScrapingError: LocalizedError {
    case webViewNotInitialized
    case pageLoadFailed
}

// MAL
func scrape() throws {
    throw NSError(domain: "Error", code: 1, userInfo: nil)
}
```

#### 6. Opcionalidad

Usa opcionales con cuidado:

```swift
// BUENO - unwrap seguro
if let coverImage = manga.coverImage {
    // Usar coverImage
}

// BUENO - nil coalescing
let title = manga.title ?? "Unknown"

// MAL - force unwrap (evitar a menos que estés 100% seguro)
let image = UIImage(contentsOfFile: path)!
```

#### 7. Closures

Usa trailing closure syntax cuando sea el último parámetro:

```swift
// BUENO
DispatchQueue.main.async {
    print("Async code")
}

// ACEPTABLE
DispatchQueue.main.async(execute: {
    print("Async code")
})
```

---

## Testing

### Escribir Tests

Crea tests para nuevas funcionalidades:

**Ejemplo de Unit Test:**

```swift
import XCTest
@testable import MangaReader

class StorageServiceTests: XCTestCase {
    var storage: StorageService!

    override func setUp() {
        super.setUp()
        storage = StorageService.shared
    }

    override func tearDown() {
        // Limpiar después de cada test
        storage.clearAllDownloads()
        super.tearDown()
    }

    func testSaveAndRetrieveFavorite() {
        // Given
        let slug = "test-manga"

        // When
        storage.saveFavorite(mangaSlug: slug)
        let isFavorite = storage.isFavorite(mangaSlug: slug)

        // Then
        XCTAssertTrue(isFavorite)
    }

    func testRemoveFavorite() {
        // Given
        let slug = "test-manga"
        storage.saveFavorite(mangaSlug: slug)

        // When
        storage.removeFavorite(mangaSlug: slug)
        let isFavorite = storage.isFavorite(mangaSlug: slug)

        // Then
        XCTAssertFalse(isFavorite)
    }
}
```

### Ejecutar Tests

1. En Xcode, presiona `Cmd + U` para ejecutar todos los tests
2. Para ejecutar un test específico, click en el diamante junto al nombre del test
3. Los tests deben ejecutarse en el simulator o en un dispositivo real

### Cobertura de Código

Apunta a tener al menos 70% de cobertura de código:

- Servicios: 80%+
- ViewModels: 70%+
- Models: 90%+ (son datos simples)
- Views: 50%+ (UI testing es más difícil)

---

## Pull Request Guidelines

### Antes de Abrir un PR

1. **Actualiza tu rama**:
   ```bash
   git checkout main
   git pull upstream main
   git checkout tu-rama
   git rebase main
   ```

2. **Resuelve conflicts** si los hay

3. **Ejecuta los tests**:
   ```bash
   xcodebuild test -scheme MangaReader -destination 'platform=iOS Simulator,name=iPhone 15'
   ```

4. **Limpia el código**:
   - Remueve `print()` statements de debugging
   - Formatea el código (Xcode puede hacerlo automáticamente: Ctrl + I)
   - Remueve comentarios TODO/FIXME implementados

### Estructura del PR

Usa esta plantilla para tu PR:

```markdown
## Descripción
Breve descripción de los cambios.

## Tipo de Cambio
- [ ] Bug fix (non-breaking change que arregla un issue)
- [ ] New feature (non-breaking change que agrega funcionalidad)
- [ ] Breaking change (fix or feature que causaría breaking changes)
- [ ] Documentation update

## Testing
Describe los tests que ejecutaste:
- [ ] Unit tests pasan
- [ ] Manual testing en device/simulator
- [ ] Probado en iOS 15, iOS 16, iOS 17

## Screenshots (si aplica)
Before/After screenshots para cambios de UI.

## Checklist
- [ ] Mi código sigue los style guidelines
- [ ] He realizado self-review de mi código
- [ ] He comentado código complejo
- [ ] He actualizado la documentación
- [ ] No hay nuevos warnings
- [ ] He agregado tests que prueban mis cambios
- [ ] Todos los tests pasan
```

### Review Process

1. **Automated Checks**: CI ejecutará tests automáticamente
2. **Code Review**: Al menos 1 revisor debe aprobar
3. **Testing**: El revisor probará los cambios en un dispositivo
4. **Merge**: El maintainer mergeará si todo está bien

---

## Troubleshooting

### Issues Comunes

#### 1. WebView no carga contenido

**Síntoma**: `scrapeMangaInfo` retorna datos vacíos

**Solución**:
- Aumenta el tiempo de espera en `loadURLAndWait`
- Verifica que la URL sea correcta
- Agrega logging para ver qué JavaScript retorna

#### 2. Tests fallan en CI pero pasan localmente

**Síntoma**: Tests pasan en tu máquina pero fallan en GitHub Actions

**Solución**:
- Asegúrate de que los tests no dependen de datos locales
- Usa mocks en vez de scrapers reales en tests
- Verifica que la configuración de iOS sea la misma

#### 3. Imágenes no cargan

**Síntoma**: ReaderView muestra placeholders en vez de imágenes

**Solución**:
- Verifica que las URLs sean válidas
- Agrega logging en `scrapeChapterImages`
- Prueba las URLs en un navegador
- Verifica que no haya bloqueo de red

#### 4. El proyecto no compila

**Síntoma**: Errores de "Cannot find type" o "No such module"

**Solución**:
1. Limpia el proyecto: `Cmd + Shift + K`
2. Cierra Xcode
3. Borra `DerivedData`: `rm -rf ~/Library/Developer/Xcode/DerivedData`
4. Abre Xcode y rebuild

### Pedir Ayuda

Si estás atascado:

1. **Busca en Issues existentes**: Puede que alguien ya tuvo el mismo problema
2. **Crea un Issue** con:
   - Descripción detallada del problema
   - Pasos para reproducir
   - Logs relevantes
   - Tu entorno (Xcode version, iOS version, macOS version)
3. **Únete a Discord/Slack** (si existe) para ayuda en tiempo real

---

## Recursos Adicionales

### Documentación de Referencia

- [Swift Language Guide](https://docs.swift.org/swift-book/)
- [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui)
- [WKWebView Reference](https://developer.apple.com/documentation/webkit/wkwebview)
- [Swift Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html)

### Herramientas Útiles

- **SwiftLint**: Linter para Swift
- **SwiftFormat**: Formateador de código
- **SwiftGen**: Generador de código para recursos

### Comunidad

- [Swift Forums](https://forums.swift.org/)
- [Stack Overflow - swift tag](https://stackoverflow.com/questions/tagged/swift)
- [r/swift subreddit](https://www.reddit.com/r/swift/)

---

**Última actualización**: Febrero 2026
**Versión**: 1.0.0
