# Límite de errores

> Dibuja una parte de la vista, o una tarjeta de error en su lugar cuando falla. Componente de Pyzed para GUIs de escritorio en Python, con ejemplos y API.

Source: https://albakore.github.io/pyzed/es/library/error-boundary/

:::tip[En pocas palabras]
Una red de seguridad para una parte de la ventana: si esa parte se rompe, ahí aparece una cajita de error y el resto de la app sigue funcionando.
:::

`ui.error_boundary(build)` devuelve `build()` o, cuando lanza una excepción, una tarjeta de error en su lugar, así una parte rota de la vista (una barra lateral, un panel) nunca tumba la ventana entera. La tarjeta muestra el error, el archivo y la línea de donde viene y un botón Copiar el error; `on_details` añade un botón Detalles y `fallback` cambia la tarjeta por tu propio widget. `on_error(info)` recibe el error cada vez que la parte no se puede dibujar. `ui.ErrorInfo` describe un error (su tipo, mensaje, traceback y origen) y `ui.error_card` es la tarjeta sola.

`ui.error_boundary` · Categoría: [Estado y avisos](https://albakore.github.io/pyzed/es/library/#estado-y-avisos) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#error_boundary)

**Código:**

```python
def totals():
    values = []
    return ui.label(f"Average: {sum(values) / len(values)}")

pz.row([
    ui.card(ui.muted("This panel keeps working."), title="Files", width=200),
    pz.container(ui.error_boundary(totals, title="Totals", on_details=on_details), width=380),
], spacing=12, align="center")
```

:::note[Nuevo]
Este componente se añadió hace poco. Cuéntanos qué tal te funciona en el [gestor de incidencias](https://github.com/albakore/pyzed/issues).
:::

## Cuándo usarlo

- Alrededor de cada panel de una ventana grande, para que un fallo en uno deje los demás funcionando.
- Alrededor de partes que dibujan datos que no controlas: un plugin, un archivo que abrió el usuario, la respuesta de un servidor.
- Abre un [Diálogo de error](https://albakore.github.io/pyzed/es/library/error-dialog/) desde `on_details` para enseñar el traceback completo.

## Uso

```python
import pyzed as pz
from pyzed import ui

class Report(pz.App):
    def __init__(self):
        self.error = None  # the ui.ErrorInfo shown in the dialog

    def totals(self):
        ...  # may raise

    def show(self, info):
        self.error = info

    def close(self):
        self.error = None

    def view(self):
        page = pz.row([
            self.files(),
            ui.error_boundary(self.totals, title="Totals", on_details=self.show),
        ])
        dialog = ui.error_dialog(self.error, self.close) if self.error else None
        return ui.dialog_layer(page, dialog, on_dismiss=self.close)
```

## Variantes

:::note[Sobre estas capturas]
Cada captura la graba pyzed mismo, fuera de pantalla a 2x, en One Dark o One Light según el tema de este sitio, y recortada al componente para que se vea a su tamaño real. El puntero pasa por encima, pulsa o escribe en él; un componente que no reacciona al puntero recorre algunos temas. Con movimiento reducido ves el primer fotograma. Abre **Código** bajo una captura para ver el fragmento que la dibuja; los nombres libres como `on_save` son tus propios callbacks.
:::

### Límite

Mientras `build()` funciona, el límite devuelve su widget tal cual; solo se reemplaza una parte que lanza una excepción.

```python
ui.error_boundary(lambda: ui.label("Average: 4.2"))
```

```python
def totals():
    values = []
    return ui.label(f"Average: {sum(values) / len(values)}")

pz.container(ui.error_boundary(totals), width=360)
```

```python
def totals():
    values = []
    return ui.label(f"Average: {sum(values) / len(values)}")

pz.container(ui.error_boundary(totals, title="Totals"), width=360)
```

```python
def totals():
    values = []
    return ui.label(f"Average: {sum(values) / len(values)}")

pz.container(ui.error_boundary(totals, title="Totals", on_details=on_details),
             width=360)
```

```python
def totals():
    values = []
    return ui.label(f"Average: {sum(values) / len(values)}")

ui.error_boundary(totals, fallback=lambda info: ui.callout(
    "Totals are not available", info.summary, tint="warning"))
```

### Tarjeta de error

`ui.error_card` es la tarjeta sola, para los errores que capturas tú.

```python
info = ui.ErrorInfo("KeyError", "'email'", origin=("app/forms.py", 42), where="Sign-up form")
pz.container(ui.error_card(info), width=360)
```

```python
info = ui.ErrorInfo("KeyError", "'email'", origin=("app/forms.py", 42), where="Sign-up form")
pz.container(ui.error_card(info, title="The form could not be loaded",
                           actions=[ui.button("Retry", on_retry, icon="rotate-ccw",
                                              size="sm", variant="ghost")]),
             width=360)
```

```python
pz.container(ui.error_card(ui.ErrorInfo("TimeoutError", "the server took too long")),
             width=360)
```

## Eventos

Límite de errores tiene **2 eventos propios**, en la tabla de abajo. Como todo widget, también tiene los **18 eventos comunes** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), que se conectan con `.on(...)`: mira [Eventos](https://albakore.github.io/pyzed/es/guides/events/) y [Eventos de cada componente](https://albakore.github.io/pyzed/es/library/events/).

| Evento | De | El handler recibe | Cuándo |
| --- | --- | --- | --- |
| `on_error` | `ui.error_boundary` | `(info)` | Recibe el `ui.ErrorInfo`. |
| `on_details` | `ui.error_boundary`, `ui.error_card` | `(info)` | Añade el botón Detalles; recibe el `ui.ErrorInfo` (abre un `ui.error_dialog`). |

**El handler recibe** es lo que Pyzed le pasa a tu función: `()` es nada. Cualquier handler también puede ser un mensaje (un valor que no se puede llamar), que va a `update()`.

### Manejar los eventos

Los mismos eventos escritos de tres formas. **Imperativa**: métodos de la app que cambian `self`. **Declarativa**: los handlers producen mensajes y un único `update()` decide. **Funcional**: una función pura dibuja un estado inmutable y pide cambios con `set_state(...)`. Mira [Imperativo, declarativo y funcional](https://albakore.github.io/pyzed/es/guides/code-styles/).

**Imperativo:**

```python
class Report(pz.App):
    def __init__(self):
        self.values = []
        self.error = None                # the ui.ErrorInfo shown in the dialog

    def totals(self):
        return ui.label(f"Average: {sum(self.values) / len(self.values)}")   # raises on []

    def view(self):
        page = ui.error_boundary(self.totals, title="Totals",
                                 on_error=lambda info: print(info.message),         # every failure
                                 on_details=lambda info: setattr(self, "error", info))  # Details
        dialog = ui.error_dialog(self.error, lambda: setattr(self, "error", None)) if self.error else None
        return ui.dialog_layer(page, dialog)
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class ShowError:
    info: object

class Report(pz.App):
    error = None

    def totals(self):
        return ui.label(f"Average: {sum([]) / len([])}")

    def update(self, message):
        match message:
            case ShowError(info):
                self.error = info
            case "close":
                self.error = None

    def view(self):
        page = ui.error_boundary(self.totals, title="Totals", on_details=ShowError)
        return ui.dialog_layer(page, ui.error_dialog(self.error, "close") if self.error else None)
```

**Funcional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    values: tuple = ()
    failures: int = 0

def totals(values):
    return ui.label(f"Average: {sum(values) / len(values)}")

def view(state, set_state):
    return pz.column([
        ui.error_boundary(lambda: totals(state.values), title="Totals",
                          on_error=lambda info: set_state(failures=state.failures + 1)),
        ui.button("Add 10", lambda: set_state(values=state.values + (10,))),
    ], spacing=8, width=380)
```

## Consejos

:::tip
El límite ejecuta `build()` en cada redibujado, así que `on_error` se llama otra vez cada vez que la parte falla. Para avisar de un error una sola vez, compáralo con el anterior: dos `ErrorInfo` con el mismo tipo, mensaje, traceback y origen son iguales (`time` no cuenta). `ErrorInfo.from_exception(err)` describe cualquier excepción que captures tú, e `info.report()` da el texto para pegar en un informe de error.
:::

## Propiedades y API

### `ui.error_boundary`

```python
error_boundary(
    build,
    *,
    on_error=None,
    title=None,
    where='',
    on_details=None,
    fallback=None,
    theme=None,
)
```

Dibuja `build()`, o una tarjeta de error si falla, para que una parte de la vista que falla nunca tumbe la ventana entera (como los error boundaries de React). `on_error(info)` se entera del error cada vez que la parte se dibuja y falla, así que regístralo una sola vez (compara `info.traceback`).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `build` | *obligatorio* | Una función que devuelve el widget de la parte. |
| `title` | `None` | Título de la tarjeta (y el `where` del error). |
| `where` | `""` | Qué es la parte, para el informe. |
| `fallback` | `None` | Tu propio widget para el error: `fallback(info)`. |
| `theme` | `None` | Tema a usar. |

Eventos: `on_error`, `on_details` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#error_boundary)

### `ui.error_card`

```python
error_card(info, *, title=None, on_details=None, actions=(), theme=None)
```

La tarjeta que dibuja `ui.error_boundary` en lugar de lo que falló: el error, de dónde viene y el botón Copiar el error (y Detalles con `on_details`).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `info` | *obligatorio* | Un `ui.ErrorInfo`. |
| `title` | `None` | Título. |
| `actions` | `()` | Más botones tras ellos. |
| `theme` | `None` | Tema a usar. |

Eventos: `on_details` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#error_card)

### `ui.ErrorInfo`

```python
@dataclass
class ErrorInfo(kind, message, traceback='', origin=None, where='', time=0.0)
```

Un error tal como lo muestran `ui.error_card` y `ui.error_dialog`: `kind` (el tipo de la excepción), `message`, el `traceback` completo, el `origin` `(ruta, línea)` del que viene (el último frame fuera de la librería estándar, los paquetes instalados y Pyzed), `where` pasó y `time`. `ErrorInfo.from_exception(err, where=...)` crea uno (`prefer=` elige qué frames pueden ser el origen); `ErrorInfo.current()` describe la excepción que se está manejando. `summary` es `Tipo: mensaje`, `location` es `archivo.py:12` y `report()` es todo como texto para un informe de error.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `kind` | *obligatorio* | El nombre del tipo de la excepción. |
| `message` | *obligatorio* | Su mensaje. |
| `traceback` | `""` | El traceback completo, como lo imprime Python. |
| `origin` | `None` | `(ruta, línea)` del código del que viene, o `None`. |
| `where` | `""` | Qué se estaba ejecutando. |
| `time` | `0.0` | Cuándo pasó (`time.time()`). |

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#errorinfo)

## Relacionados

- [Diálogo de error](https://albakore.github.io/pyzed/es/library/error-dialog/): Qué salió mal, dónde y el traceback, con Copiar e Ir al origen.
- [Aviso](https://albakore.github.io/pyzed/es/library/callout/): Un aviso en línea con título, texto y acciones.
- [Consola](https://albakore.github.io/pyzed/es/library/console/): Un registro estilo dev tools con niveles, filtro, copiar y limpiar.
