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)); } }