# 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