TV es fundamentalmente diferente

Android TV no es "la misma app en una pantalla más grande". Es una plataforma con restricciones radicalmente distintas:

# Lo que NO existe en TV:
# ✗ Touchscreen — el usuario no puede tocar la pantalla
# ✗ Gestos — swipe, pinch-to-zoom, long press táctil
# ✗ Teclado físico siempre disponible (puede haber uno Bluetooth, pero no asumirlo)
# ✗ Notificaciones push visibles en tiempo real

# Lo que SÍ hay en TV:
# ✓ D-pad: arriba, abajo, izquierda, derecha, OK/Select
# ✓ Botones de control: Back, Home, Play/Pause, Rebobinar, Adelantar
# ✓ Control remoto de voz en algunos dispositivos
# ✓ La app ocupa siempre la pantalla completa (sin ventanas)

# El paradigma central de TV:
# → El FOCO es todo — el elemento enfocado debe ser OBVIO
# → El usuario navega con las flechas del D-pad
# → Enter/OK activa el elemento enfocado
# → Back cierra o vuelve atrás

Manifest para TV

<!-- AndroidManifest.xml — declarar soporte para TV -->
<manifest>
    <!-- Declarar que la app funciona en TV -->
    <uses-feature
        android:name="android.software.leanback"
        android:required="false" />  <!-- false = también funciona en móvil -->

    <!-- TV no requiere touchscreen -->
    <uses-feature
        android:name="android.hardware.touchscreen"
        android:required="false" />

    <application>
        <!-- Activity principal para TV (puede ser la misma que móvil o diferente) -->
        <activity android:name=".MainActivity">
            <!-- Intent filter para TV -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>
            <!-- Intent filter para móvil -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Focus management — el trabajo central en TV

// En TV, el focus se mueve con las flechas del D-pad
// Compose maneja el focus automáticamente entre componentes focusables
// Pero hay casos donde necesitás controlarlo manualmente

// Hacer un composable focusable:
Box(
    modifier = Modifier
        .focusable()  // participa en la navegación por focus
        .onFocusChanged { focusState ->
            if (focusState.isFocused) {
                // El elemento está enfocado — mostrar indicador visual
            }
        }
) { /* contenido */ }

// Indicador visual de focus — CRÍTICO en TV:
@Composable
fun BotonTV(texto: String, onClick: () -> Unit) {
    val focusState = remember { mutableStateOf(false) }

    Button(
        onClick = onClick,
        modifier = Modifier
            .onFocusChanged { focusState.value = it.isFocused }
            .border(
                width = if (focusState.value) 3.dp else 0.dp,
                color = if (focusState.value) Color.White else Color.Transparent,
                shape = RoundedCornerShape(8.dp)
            )
            .scale(if (focusState.value) 1.1f else 1.0f)  // leve zoom al enfocar
    ) {
        Text(texto, fontSize = if (focusState.value) 20.sp else 18.sp)
    }
}

FocusRequester — focus programático

// Pedir el focus cuando una pantalla aparece:
@Composable
fun PantallaTV() {
    val primerBotonFocus = remember { FocusRequester() }

    LaunchedEffect(Unit) {
        // Dar focus al primer elemento al entrar a la pantalla
        primerBotonFocus.requestFocus()
    }

    Column {
        Button(
            onClick = { /* acción */ },
            modifier = Modifier.focusRequester(primerBotonFocus)
        ) {
            Text("Reproducir")
        }
        Button(onClick = { /* acción */ }) {
            Text("Más información")
        }
    }
}

// Capturar eventos específicos del D-pad:
Box(
    modifier = Modifier
        .focusable()
        .onKeyEvent { evento ->
            when (evento.key) {
                Key.DirectionUp    -> { /* flecha arriba */ true }
                Key.DirectionDown  -> { /* flecha abajo  */ true }
                Key.DirectionLeft  -> { /* flecha izq    */ true }
                Key.DirectionRight -> { /* flecha der    */ true }
                Key.Enter          -> { onClick(); true }
                Key.Back           -> { onVolver(); true }
                Key.MediaPlayPause -> { onPlayPause(); true }
                else -> false  // no consumido
            }
        }
) { /* contenido */ }

Compose para TV — la librería específica

// implementation("androidx.tv:tv-foundation:1.0.0-alpha11")
// implementation("androidx.tv:tv-material:1.0.0-alpha11")

// Componentes TV de Compose con focus correcto out-of-the-box:

// TvLazyRow — lista horizontal típica de TV (Netflix, YouTube)
@Composable
fun FilaContenido(items: List<Contenido>) {
    TvLazyRow(
        contentPadding = PaddingValues(horizontal = 48.dp)
    ) {
        items(items, key = { it.id }) { contenido ->
            Card(
                onClick = { /* reproducir */ },
                modifier = Modifier
                    .size(width = 200.dp, height = 120.dp)
                    .padding(8.dp)
            ) {
                // Thumbnail del contenido
                AsyncImage(
                    model = contenido.thumbnailUrl,
                    contentDescription = contenido.titulo,
                    contentScale = ContentScale.Crop,
                    modifier = Modifier.fillMaxSize()
                )
            }
        }
    }
}

// ImmersiveList — elemento destacado con lista debajo (patrón Hero)
@Composable
fun PantallaInicio(contenidos: List<Contenido>) {
    ImmersiveList(
        background = { index, _ ->
            // Imagen de fondo del elemento actual
            AsyncImage(
                model = contenidos[index].backdropUrl,
                contentDescription = null,
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Crop
            )
        }
    ) {
        TvLazyRow {
            itemsIndexed(contenidos) { index, contenido ->
                Card(onClick = { /* reproducir */ }) {
                    Text(contenido.titulo)
                }
            }
        }
    }
}

Diseño para la distancia — reglas de TV

# TEXTO — legible a 3 metros de distancia:
# → Tamaño mínimo: 24sp para texto de cuerpo
# → Títulos: 32sp o más
# → No usar fuentes ligeras (weight 300 o menos)

# TOUCH TARGETS — aunque no haya touch:
# → Elementos interactivos: mínimo 60x60dp
# → Espaciado entre elementos: mínimo 12dp

# MÁRGENES — la zona segura de la pantalla (overscan):
# → Las TV modernas no tienen overscan, pero el estándar es 48dp de margen
# → Nunca poner contenido crítico en los bordes

# FOCO — siempre visible:
# → El elemento enfocado debe destacar claramente del resto
# → Usar escala (1.05-1.15x), borde blanco o sombra para indicar focus
# → El foco debe moverse de forma predecible con el D-pad

# COLORES:
# → Fondo oscuro (no blanco) — la TV emite luz, el blanco cansa los ojos
# → Evitar gradientes complejos — pueden verse mal en TVs de baja calidad
# → Alto contraste texto / fondo