From eee15819acb7569841914a030ded1ad8a6b42780 Mon Sep 17 00:00:00 2001 From: javier Date: Tue, 14 Jul 2026 12:00:29 +0200 Subject: [PATCH] feat(sync): media en paralelo + visibilidad proactiva de problemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pushMedia sube en lotes de 3 en paralelo (Promise.all por chunk) en vez de una a una; contadores del report seguros al ser el runtime monohilo. - SyncStatusBar: fondo rojo cuando hay conflictos/errores (prioridad sobre el estado de conexión), para que el problema salte a la vista. - ProjectDetail: el auto-sync silencioso ahora avisa una vez con Alert cuando un ciclo produce conflictos/errores, con acceso directo a la cola. Co-Authored-By: Claude Sonnet 4.6 --- src/components/SyncStatusBar.tsx | 5 ++++- src/screens/ProjectDetailScreen.tsx | 16 +++++++++++++++- src/sync/engine.ts | 13 ++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/SyncStatusBar.tsx b/src/components/SyncStatusBar.tsx index fe119fd..30fcabe 100644 --- a/src/components/SyncStatusBar.tsx +++ b/src/components/SyncStatusBar.tsx @@ -16,8 +16,11 @@ export function SyncStatusBar({ const pendientes = counts.pending + counts.mediaPending; const hasProblems = counts.conflict > 0 || counts.error > 0; + // Prioridad de color: problemas (rojo) > sin conexión (ámbar) > al día (verde). + const bg = hasProblems ? '#b00020' : online ? '#1f6f43' : '#8a6d00'; + const content = ( - + {syncing && } {online ? 'En línea' : 'Sin conexión'} diff --git a/src/screens/ProjectDetailScreen.tsx b/src/screens/ProjectDetailScreen.tsx index 64db1f3..6589e4d 100644 --- a/src/screens/ProjectDetailScreen.tsx +++ b/src/screens/ProjectDetailScreen.tsx @@ -45,8 +45,22 @@ export function ProjectDetailScreen({ route, navigation }: Props) { }, [projectId, refreshCounts]); // Auto-sync (silencioso) al recuperar red / volver a primer plano / por intervalo. + // Aunque es silencioso, si un ciclo produce conflictos o errores nuevos avisamos + // proactivamente una vez (cada conflicto/error solo se reporta en su ciclo). useAutoSync(true, async () => { - await doSync(); + const report = await doSync(); + const problems = report.conflicts + report.errors; + if (problems > 0) { + Alert.alert( + 'Sincronización con incidencias', + `${report.conflicts} conflicto(s) y ${report.errors} error(es) al sincronizar. ` + + 'Revisa la cola para resolverlos.', + [ + { text: 'Ahora no', style: 'cancel' }, + { text: 'Revisar', onPress: () => navigation.navigate('Outbox') }, + ], + ); + } }); // Sync manual (con resumen). diff --git a/src/sync/engine.ts b/src/sync/engine.ts index 72979eb..9fc0225 100644 --- a/src/sync/engine.ts +++ b/src/sync/engine.ts @@ -173,10 +173,14 @@ async function processResult( return false; } +/** Nº de ficheros que se suben en paralelo por lote. */ +const MEDIA_CONCURRENCY = 3; + /** Sube los ficheros pendientes a /media (solo los de padres ya sincronizados). */ async function pushMedia(report: SyncReport): Promise { const pending = await getPendingMedia(); - for (const m of pending) { + + const uploadOne = async (m: (typeof pending)[number]): Promise => { try { const res = await uploadMedia({ uuid: m.uuid, @@ -194,6 +198,13 @@ async function pushMedia(report: SyncReport): Promise { await markMediaError(m.uuid, e instanceof Error ? e.message : String(e)); report.mediaErrors += 1; } + }; + + // Lotes de MEDIA_CONCURRENCY en paralelo (el runtime es monohilo: los + // contadores del report se incrementan sin condición de carrera). + for (let i = 0; i < pending.length; i += MEDIA_CONCURRENCY) { + const batch = pending.slice(i, i + MEDIA_CONCURRENCY); + await Promise.all(batch.map(uploadOne)); } }