Paleta Material You — Tema verde agrícola
☀️ Light Theme
Primary
#2E7D32
PrimaryContainer
#B7E4B9
Secondary
#52634F
SecondaryContainer
#D5E8D0
Tertiary
#38656A
TertiaryContainer
#BCEBF0
Surface
#F8FAF7
SurfaceVariant
#DCE5D9
Error
#BA1A1A
Outline
#717970
🌙 Dark Theme
Primary
#6DBF70
PrimaryContainer
#1A3D1C
Secondary
#BACBB5
SecondaryContainer
#2E3D2A
Tertiary
#A0CFD4
TertiaryContainer
#1A3E44
Surface
#1C1F1A
SurfaceVariant
#2B2F2A
Error
#FFB4AB
Outline
#8A9E87
Material 3 Type Scale
Display Large
Parcelas del Norte
57sp / 64sp
Headline Medium
Finca La Vega
28sp / 36sp
Title Large
Tratamiento fitosanitario #247
22sp / 28sp
Title Medium
Registro de incidencia
16sp / 24sp · M500
Body Large
Presencia de Botrytis cinerea en sector 3 — requiere tratamiento preventivo con azufre.
16sp / 24sp
Body Medium
Última actualización: hoy a las 14:32 · Técnico: Marta Rodríguez
14sp / 20sp
Label Medium
ESTADO · CRÍTICO · VER MÁS
12sp · M500
Material 3 Component Library
Buttons
Chips — Estado parcelas
Óptimo
Riesgo
Crítico
Filter chips
✓ Activas
En tratamiento
Cosecha próxima
🌿 Ecológico
Navigation Bar + Top App Bar
Mis Parcelas
🔍
— contenido —
Text Fields
Máx. 80 caracteres
⚠ Este campo es obligatorio
Flujo principal: Dashboard → Parcela → Incidencia
9:41
📶 📡 🔋
AgroSync
🔔
MR
Buenos días,
Marta Rodríguez 👋
12
Parcelas activas
3
Con alertas
48h
Próx. lluvia
Parcelas recientes
🍇
La Vega — Sector 3
4.2 ha · Tempranillo
Óptimo
🌾
Cereal Norte — P12
8.7 ha · Trigo blando
Riesgo
🫒
Olivos Extremadura
11.3 ha · Picual
Crítico
🏠
Inicio
🌾
Parcelas
⚠️
Alertas
👤
Perfil
🌤 Dashboard — Light Mode
9:41
📶 📡 🔋
Nueva Incidencia
Guardar
Parcela afectada
Olivos Extremadura — P07
Tipo de incidencia
🦠 Plaga
💧 Riego
🌡 Clima
⚙️ Maquinaria
Severidad
🟢 Baja
🟡 Media
🔴 Alta
📸
Añadir foto
JPG · PNG · máx 10 MB
Descripción
Presencia de Bactrocera oleae en…
🏠
Inicio
🌾
Parcelas
⚠️
Alertas
👤
Perfil
🌙 Formulario Incidencia — Dark Mode
9:41
📶 📡 🔋
Detalle Parcela
🗺
📍 38.7789°N, 6.2134°W
Olivos Extremadura
Parcela 07 · Badajoz, Extremadura
Crítico
🫒 Picual
🌿 Ecológico
Superficie
11,3 ha
Cosecha prev.
Oct 2026
Incidencias
3 abiertas
Últ. tratam.
hace 5d
Incidencias activas
🦠 Bactrocera oleae — Alta
Registrado hace 2h · Sector sur
📋
🏠
Inicio
🌾
Parcelas
⚠️
Alertas
👤
Perfil
🌤 Detalle Parcela — Light Mode
Implementación de referencia
AgroSyncTheme.kt — Material 3 Theme Setup
private val LightColorScheme = lightColorScheme(
    primary         = Color(0xFF2E7D32),
    onPrimary       = Color(0xFFFFFFFF),
    primaryContainer    = Color(0xFFB7E4B9),
    onPrimaryContainer  = Color(0xFF002105),
    secondary       = Color(0xFF52634F),
    secondaryContainer  = Color(0xFFD5E8D0),
    tertiary        = Color(0xFF38656A),
    surface         = Color(0xFFF8FAF7),
    surfaceVariant  = Color(0xFFDCE5D9),
    error           = Color(0xFFBA1A1A),
)

@Composable
fun AgroSyncTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context)
            else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    MaterialTheme(
        colorScheme = colorScheme,
        typography = AgroSyncTypography,
        content = content
    )
}
ParcelaCard.kt — Composable con estado
@Composable
fun ParcelaCard(
    parcela: Parcela,
    onParcelaClick: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    val statusColor = when (parcela.status) {
        ParcelaStatus.OPTIMO   -> MaterialTheme.colorScheme.primaryContainer
        ParcelaStatus.RIESGO   -> Color(0xFFFFF3E0)
        ParcelaStatus.CRITICO  -> MaterialTheme.colorScheme.errorContainer
    }

    Card(
        onClick = { onParcelaClick(parcela.id) },
        modifier = modifier.fillMaxWidth(),
        shape = RoundedCornerShape(16.dp),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surface
        ),
        elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
    ) {
        Row(
            modifier = Modifier.padding(14.dp).fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Box(
                modifier = Modifier
                    .size(44.dp)
                    .clip(RoundedCornerShape(12.dp))
                    .background(MaterialTheme.colorScheme.primaryContainer),
                contentAlignment = Alignment.Center
            ) {
                Text(parcela.cropEmoji, fontSize = 20.sp)
            }
            Spacer(Modifier.width(12.dp))
            Column(modifier = Modifier.weight(1f)) {
                Text(
                    text = parcela.name,
                    style = MaterialTheme.typography.titleSmall,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis
                )
                Text(
                    text = "${parcela.hectareas} ha · ${parcela.cultivo}",
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant
                )
            }
            ParcelaStatusChip(status = parcela.status)
        }
    }
}

@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ParcelaCardPreview() {
    AgroSyncTheme {
        ParcelaCard(
            parcela = sampleParcela(),
            onParcelaClick = {}
        )
    }
}
MainScreen.kt — Navigation Compose con Bottom Bar
@Composable
fun MainScreen() {
    val navController = rememberNavController()

    Scaffold(
        bottomBar = {
            NavigationBar(
                containerColor = MaterialTheme.colorScheme.surface,
                tonalElevation = 3.dp
            ) {
                val currentDest by navController.currentBackStackEntryAsState()
                AgroSyncDestination.entries.forEach { dest ->
                    NavigationBarItem(
                        icon = { Icon(dest.icon, contentDescription = stringResource(dest.labelRes)) },
                        label = { Text(stringResource(dest.labelRes)) },
                        selected = currentDest?.destination?.hierarchy?.any {
                            it.route == dest.route
                        } == true,
                        onClick = {
                            navController.navigate(dest.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )
                }
            }
        },
        floatingActionButton = {
            ExtendedFloatingActionButton(
                onClick = { navController.navigate(Route.NuevaIncidencia) },
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                contentColor = MaterialTheme.colorScheme.onPrimaryContainer
            ) {
                Icon(Icons.Default.Add, contentDescription = null)
                Spacer(Modifier.width(8.dp))
                Text("Nueva incidencia")
            }
        }
    ) { padding ->
        NavHost(
            navController = navController,
            startDestination = AgroSyncDestination.Home.route,
            modifier = Modifier.padding(padding)
        ) {
            composable(AgroSyncDestination.Home.route)     { HomeScreen(navController) }
            composable(AgroSyncDestination.Parcelas.route)  { ParcelasScreen(navController) }
            composable(AgroSyncDestination.Alertas.route)   { AlertasScreen() }
            composable(AgroSyncDestination.Perfil.route)    { PerfilScreen() }
            composable("parcela/{id}") { backStack ->
                ParcelaDetailScreen(
                    parcelaId = backStack.arguments?.getString("id")!!,
                    onBack = { navController.popBackStack() }
                )
            }
        }
    }
}
Checklist WCAG AA + Material You