# Sistema de Descarga de Capítulos - MangaReader iOS

## Overview

El sistema de descarga de capítulos permite a los usuarios descargar capítulos completos de manga para lectura offline. El sistema está diseñado con arquitectura asíncrona moderna usando Swift async/await.

## Componentes Principales

### 1. DownloadManager (`/Sources/Services/DownloadManager.swift`)

Gerente centralizado que maneja todas las operaciones de descarga.

**Características:**
- Descarga asíncrona de imágenes con concurrencia controlada
- Máximo 3 descargas simultáneas de capítulos
- Máximo 5 imágenes simultáneas por capítulo
- Cancelación de descargas individuales o masivas
- Seguimiento de progreso en tiempo real
- Manejo robusto de errores
- Historial de descargas completadas y fallidas

**Uso básico:**
```swift
let downloadManager = DownloadManager.shared

// Descargar un capítulo
try await downloadManager.downloadChapter(
    mangaSlug: "one-piece",
    mangaTitle: "One Piece",
    chapter: chapter
)

// Descargar múltiples capítulos
await downloadManager.downloadChapters(
    mangaSlug: "one-piece",
    mangaTitle: "One Piece",
    chapters: chapters
)

// Cancelar descarga
downloadManager.cancelDownload(taskId: "taskId")

// Cancelar todas
downloadManager.cancelAllDownloads()
```

### 2. MangaDetailView (`/Sources/Views/MangaDetailView.swift`)

Vista de detalles del manga con funcionalidad de descarga integrada.

**Características añadidas:**
- Botón de descarga en la toolbar
- Descarga individual por capítulo
- Progreso de descarga visible en cada fila de capítulo
- Notificaciones de completado/error
- Alert para descargar últimos 10 o todos los capítulos

**Flujo de descarga:**
1. Usuario toca botón de descarga en toolbar → muestra alert
2. Selecciona cantidad de capítulos a descargar
3. Cada capítulo muestra progreso de descarga en tiempo real
4. Notificación aparece al completar todas las descargas
5. Capítulos descargados muestran checkmark verde

### 3. DownloadsView (`/Sources/Views/DownloadsView.swift`)

Vista dedicada para gestionar todas las descargas.

**Tabs:**
- **Activas**: Descargas en progreso con barra de progreso
- **Completadas**: Historial de descargas exitosas
- **Fallidas**: Descargas con errores, permite reintentar

**Funcionalidades:**
- Cancelar descargas individuales
- Cancelar todas las descargas activas
- Limpiar historiales (completadas/fallidas)
- Ver tamaño de almacenamiento usado
- Limpiar todo el almacenamiento descargado

### 4. StorageService (`/Sources/Services/StorageService.swift`)

Servicio de almacenamiento ya existente, ahora con soporte para descargas.

**Métodos utilizados:**
```swift
// Guardar imagen descargada
try await storage.saveImage(
    image,
    mangaSlug: "manga-slug",
    chapterNumber: 1,
    pageIndex: 0
)

// Verificar si capítulo está descargado
storage.isChapterDownloaded(mangaSlug: "manga-slug", chapterNumber: 1)

// Obtener directorio del capítulo
let chapterDir = storage.getChapterDirectory(
    mangaSlug: "manga-slug",
    chapterNumber: 1
)

// Obtener URL de imagen local
if let imageURL = storage.getImageURL(
    mangaSlug: "manga-slug",
    chapterNumber: 1,
    pageIndex: 0
) {
    // Usar imagen local
}

// Eliminar capítulo descargado
storage.deleteDownloadedChapter(
    mangaSlug: "manga-slug",
    chapterNumber: 1
)

// Obtener tamaño de almacenamiento
let size = storage.getStorageSize()
let formatted = storage.formatFileSize(size)
```

## Modelos de Datos

### DownloadTask
Representa una tarea de descarga individual:
```swift
class DownloadTask: ObservableObject {
    let id: String
    let mangaSlug: String
    let mangaTitle: String
    let chapterNumber: Int
    let imageURLs: [String]

    @Published var state: DownloadState
    @Published var downloadedPages: Int
    @Published var progress: Double
}
```

### DownloadState
Estados posibles de una descarga:
```swift
enum DownloadState {
    case pending
    case downloading(progress: Double)
    case completed
    case failed(error: String)
    case cancelled
}
```

### DownloadError
Tipos de errores de descarga:
```swift
enum DownloadError: LocalizedError {
    case alreadyDownloaded
    case noImagesFound
    case invalidURL
    case invalidResponse
    case httpError(statusCode: Int)
    case invalidImageData
    case cancelled
    case storageError(String)
}
```

## Configuración

### Parámetros de Descarga
En `DownloadManager`:
```swift
private let maxConcurrentDownloads = 3           // Máximo de capítulos simultáneos
private let maxConcurrentImagesPerChapter = 5    // Máximo de imágenes simultáneas por capítulo
```

### Calidad de Imagen
En `StorageService.saveImage()`:
```swift
image.jpegData(compressionQuality: 0.8)  // 80% de calidad JPEG
```

En `DownloadExtensions`:
```swift
func optimizedForStorage() -> Data? {
    // Redimensiona si > 2048px
    // Comprime a 75% de calidad
}
```

## Integración con ReaderView

Para leer capítulos descargados:

```swift
struct ReaderView: View {
    let chapter: Chapter
    let mangaSlug: String

    @StateObject private var storage = StorageService.shared

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(pageIndices, id: \.self) { index in
                    if let imageURL = storage.getImageURL(
                        mangaSlug: mangaSlug,
                        chapterNumber: chapter.number,
                        pageIndex: index
                    ) {
                        // Usar imagen local
                        AsyncImage(url: imageURL) { image in
                            image.resizable()
                        } placeholder: {
                            ProgressView()
                        }
                    } else {
                        // Fallback a URL remota
                        RemoteChapterPage(url: remoteURL)
                    }
                }
            }
        }
    }
}
```

## Notificaciones

El sistema emite notificaciones para seguimiento:
```swift
extension Notification.Name {
    static let downloadDidStart = Notification.Name("downloadDidStart")
    static let downloadDidUpdate = Notification.Name("downloadDidUpdate")
    static let downloadDidComplete = Notification.Name("downloadDidComplete")
    static let downloadDidFail = Notification.Name("downloadDidFail")
    static let downloadDidCancel = Notification.Name("downloadDidCancel")
}
```

## Manejo de Errores

### Errores de Red
- Timeout: 30 segundos por imagen
- Reintentos: Manejados por URLSession
- HTTP errors: Capturados y reportados en UI

### Errores de Almacenamiento
- Espacio insuficiente: Error con mensaje descriptivo
- Permisos: Manejados por FileManager
- Corrupción de archivos: Archivos eliminados y descarga reiniciada

### Errores de Scraping
- No se encontraron imágenes: Error `noImagesFound`
- Página no carga: Error del scraper propagado
- Cambios en la web: Requieren actualización del scraper

## Best Practices

### 1. Concurrencia
El sistema usa Swift Concurrency:
- `async/await` para operaciones asíncronas
- `Task` para crear contextos de concurrencia
- `@MainActor` para actualizaciones de UI
- `TaskGroup` para descargas en paralelo

### 2. Memoria
- Imágenes comprimidas antes de guardar
- Descarga limitada a 5 imágenes simultáneas
- Limpieza automática de historiales (50 completadas, 20 fallidas)

### 3. UX
- Progreso visible en tiempo real
- Cancelación en cualquier punto
- Notificaciones de estado
- Estados vacíos descriptivos
- Feedback inmediato de acciones

### 4. Robustez
- Validación de estados antes de descargar
- Limpieza de archivos parciales al cancelar
- Verificación de archivos existentes
- Manejo exhaustivo de errores

## Testing

### Pruebas Unitarias
```swift
func testDownloadManager() async throws {
    let manager = DownloadManager.shared

    // Probar descarga individual
    try await manager.downloadChapter(
        mangaSlug: "test",
        mangaTitle: "Test Manga",
        chapter: testChapter
    )

    XCTAssertTrue(manager.activeDownloads.isEmpty)
    XCTAssertEqual(manager.completedDownloads.count, 1)
}
```

### Pruebas de Integración
- Descargar capítulo completo
- Cancelar descarga a mitad
- Descargar múltiples capítulos
- Probar con y sin conexión
- Verificar persistencia de archivos

## Troubleshooting

### Descargas no inician
- Verificar conexión a internet
- Verificar que el scraper puede acceder a la web
- Revisar logs del scraper

### Progreso no actualiza
- Asegurar que las vistas están en @MainActor
- Verificar que DownloadTask es @ObservedObject
- Chequear que las propiedades son @Published

### Archivos no se guardan
- Verificar permisos de la app
- Chequear espacio disponible
- Revisar que directorios existen

### Imágenes corruptas
- Verificar calidad de compresión
- Chequear que URLs sean válidas
- Probar redimensionado de imágenes

## Futuras Mejoras

- [ ] Soporte para reanudar descargas pausadas
- [ ] Priorización de descargas
- [ ] Descarga automática de nuevos capítulos
- [ ] Compresión adicional de imágenes
- [ ] Soporte para formatos WebP
- [ ] Batch operations en StorageService
- [ ] Background downloads con URLSession
- [ ] Metrics y analytics de descargas
