# Cómo centralizar la navegación en un controller

> Encapsular push/pop de la pila del EventsProcessor en un controller propio, para navegación testeable y desacoplada.

Source: https://crossfly.dev/es/docs/cookbook/nav/controller-de-navegacao — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo centralizar la navegación en un controller

**Propósito.** Esparcir `new X().show()` y `hide()` por las pantallas acopla la UI al grafo de navegación. Un **controller** concentra push/pop, da un punto único para logging/guardas y permite "cerrar hasta la raíz". Solo orquesta la pila real del `EventsProcessor`.

## APIs usadas
- `Window.show()` / `Window.hide()` — push/pop en la pila (el controller solo les delega).
- `EventsProcessor.topWindow()` — método **estático** que retorna la ventana de la cima (`instance.windows.peek()`).
- `EventsProcessor.instance.getLastWindow()` — método de **instancia**; retorna la cima real de la pila (o `null` si está vacía). Úsalo cuando necesites comprobar si hay algo apilado sin lanzar excepción.

> Nombres confirmados en la fuente de `EventsProcessor`: `topWindow()` (estático) y `getLastWindow()` (instancia). `topWindow()` hace `windows.peek()` y lanza si la pila está vacía; `getLastWindow()` retorna `null` en ese caso — prefiere este para verificaciones defensivas.

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** Pantalla principal ejecutable: usa el controlador Nav para apilar/desapilar pantallas. */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Tela raiz"));
      add(new Button("Abrir detalhe").onAction(e -> Nav.push(new OutraTela())));  // push mediante el controlador
   }
}

/** Pantalla apilada: cierra mediante el controlador (pop). */
class OutraTela extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Detalhe"));
      add(new Button("Voltar").onAction(e -> Nav.pop()));  // pop mediante el controlador
   }
}

/** Controlador: fachada fina sobre la pila real del EventsProcessor. */
final class Nav
{
   private Nav() {}

   /** push: apila y muestra. */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop: cierra la ventana superior, si la hay. */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null-safe
      if (top != null)
         top.hide();
   }

   /** ventana actualmente en la cima (puede ser null). */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo
```java
// verificado en la fuente: EventsProcessor.topWindow() (static) e instance.getLastWindow() (devuelve null si está vacía)
public final class Nav
{
   private Nav() {}

   /** push: apila y muestra. */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop: cierra la ventana superior, si la hay. */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null-safe
      if (top != null)
         top.hide();
   }

   /** pop repetido hasta que quede solo 1 ventana (la raíz). */
   public static void popToRoot()
   {
      Window top;
      // mientras haya más de una ventana apilada, elimina la de la cima
      while ((top = EventsProcessor.instance.getLastWindow()) != null
             && top != EventsProcessor.topWindow()) { top.hide(); }
      // (para "volver a la raíz", cada hide() desapila una; ajusta el criterio de parada a tu app)
   }

   /** ventana actualmente en la cima (puede ser null). */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}

// uso en las pantallas:
Nav.push(new DetalheTela());
// ...
Nav.pop();
```

Buenas prácticas:

- **No** reimplementes la pila: el `EventsProcessor` ya es la fuente de la verdad. El controller es una fachada fina sobre `show()`/`hide()`.
- Las guardas de navegación (ej.: "¿hay cambios sin guardar?") quedan naturales aquí: comprueba antes de `pop()` y, si es necesario, dispara un `Alert` de confirmación.
- Cuidado con `topWindow()` cuando la pila puede estar vacía — hace `peek()` y lanza. Para código defensivo usa `getLastWindow()`.

## Profundiza
- [La pila de ventanas](/docs/ui/navigation/the-window-stack) · [Navegar entre pantallas](/docs/ui/navigation/navigate-between-screens)
- Receta hermana: [Abrir y cerrar una pantalla](/docs/cookbook/nav/abrir-tela)
