---
title: "Estructura del proyecto — Docs de zenit Zig UI"
description: "Coloca el punto de entrada de la compilación, la UI de cada funcionalidad, los estilos y los recursos donde puedan crecer, y ten claro quién es dueño de cada…"
url: https://zenit.z.express/es/docs/guide/project-structure
language: es
alternate_en: https://zenit.z.express/docs/guide/project-structure.md
alternate_zh: https://zenit.z.express/zh/docs/guide/project-structure.md
alternate_ja: https://zenit.z.express/ja/docs/guide/project-structure.md
alternate_ko: https://zenit.z.express/ko/docs/guide/project-structure.md
alternate_fr: https://zenit.z.express/fr/docs/guide/project-structure.md
alternate_de: https://zenit.z.express/de/docs/guide/project-structure.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Estructura del proyecto

Coloca el punto de entrada de la compilación, la UI de cada funcionalidad, los estilos y los recursos donde puedan crecer, y ten claro quién es dueño de cada bloque de memoria.

## Estructura sugerida

zenit no impone una estructura de directorios. Mantén estables los archivos de compilación y coloca juntos el árbol, el estado y los estilos de cada funcionalidad, para que cambiar una no implique saltar por todo el repositorio.

`myapp/`

```
myapp/
├── build.zig
├── build.zig.zon
└── src/
    ├── main.zig
    ├── app_state.zig
    ├── assets.zig
    └── features/
        └── dashboard/
            ├── view.zig
            ├── state.zig
            └── styles.zig
```

## Responsabilidades de cada archivo

| Archivo | Se encarga de | Evitar |
| --- | --- | --- |
| `main.zig` | Allocator, inicialización de App, montaje raíz | Acumular UI de páginas concretas |
| `view.zig` | El árbol de nodos y la conexión de eventos | Colores literales, transformaciones de datos pesadas |
| `state.zig` | El estado de negocio y sus métodos | Punteros de larga duración a nodos que pueden eliminarse o reconstruirse |
| `styles.zig` | Funciones de estilo con nombre basadas en Token, `fn (*const ui.ThemeTokens) ui.BoxStyle` | Hacer IO o mutar el estado |

Las funciones de estilo son puras; constructores como `ui.boxStyled` / `ui.vstackStyled` las evalúan al montar y las vuelven a aplicar con los nuevos tokens cuando cambia el tema; los estilos literales en línea no obtienen eso.

`styles.zig + view.zig`

```zig
// features/dashboard/styles.zig
const ui = @import("ui");

pub fn card(t: *const ui.ThemeTokens) ui.BoxStyle {
    return .{
        .padding = ui.Padding.all(t.space._6),
        .gap = t.space._4,
        .background = t.color.bg_secondary,
        .corner_radius = t.radius.xl,
    };
}

// features/dashboard/view.zig
const S = @import("styles.zig");
const panel = try ui.vstackStyled(cx, S.card, .{});
```

## Límite de importación

La superficie pública tiene dos niveles: los tipos y constructores comunes están en `ui.X`, y las funciones avanzadas se agrupan en `ui.<group>.X` (`ui.widgets`, `ui.fx`, `ui.events`, `ui.theme`, …). Todo lo que queda fuera de esos dos niveles es interno.

```zig
const ui = @import("ui");

// Tier 1: what most UI code needs
const Node = ui.Node;
const Signal = ui.Signal;

// Tier 2: grouped by concern
const Router = ui.fx.Router;
const Button = ui.widgets.Button;
const Event = ui.events.Event;
```

El sistema de compilación lo garantiza: `zenit.attach` solo da a la app los módulos `ui` y `zenit_app`, así que los módulos internos como `render` y `gpu` no se pueden importar en absoluto (consulta el diagrama en la [Introducción](https://zenit.z.express/es/docs#architecture)).

> WARNING
> 
> **La API aún no llega a 1.0.** zenit es público y sigue semver desde 0.1.0, pero antes de 1.0 una versión minor aún puede romper compatibilidad (cada cambio se registra en docs/MIGRATION.md). Fija una versión o revisión exacta en lugar de seguir la punta de una rama.

## Propiedad y ciclos de vida

Dos dueños, dos ciclos de vida. `Cx` es de nivel de ventana: es dueño del árbol de nodos y de cada struct asignado por `cx.bindState` hasta `Cx.deinit`. `Scope` es dueño de los recursos reactivos — Signals, Memos, Effects, callbacks de `onCleanup` y recursos registrados — y los libera en `dispose()`; los scopes hijos se liberan con su padre.

OWNERSHIP

Hacer dispose de un Scope de página no libera los structs que creaste con `bindState` en esa página: viven hasta que se cierra la ventana. `Cx.deinit` ejecuta: dispose del Scope raíz → liberar nodos → liberar el estado.

### Estado vinculado: declara un pub deinit

Si el struct declara un **pub** `deinit(*T)`, el framework lo llama automáticamente antes de liberar el struct en `Cx.deinit`. Si la limpieza necesita un allocator, guárdalo como campo.

`state.zig`

```zig
const Item = struct { title: []const u8 };

const Model = struct {
    allocator: std.mem.Allocator,
    items: std.ArrayList(Item) = .empty,

    // Must be pub: Cx.deinit detects it and calls it before freeing the struct.
    pub fn deinit(self: *Model) void {
        self.items.deinit(self.allocator);
    }
};

const model = try cx.bindState(Model, .{ .allocator = cx.allocator });
// Do NOT also call scope.onCleanup(Model, model, Model.deinit):
// that would run deinit twice.
```

> WARNING
> 
> **No limpies dos veces.** Declarar un pub `deinit` en un struct de `bindState` *y* registrar `scope.onCleanup(…, T.deinit)` ejecuta deinit dos veces: una en el dispose del scope y otra en `Cx.deinit`. Un `deinit` no pub no se detecta y nunca se llama automáticamente.

### Recursos con ciclo de vida de página: onCleanup

Recurre a `scope.onCleanup` solo en dos casos: la limpieza no es pub, o el recurso debe seguir el ciclo de vida de un Scope (por ejemplo, una página) en lugar del de la ventana. Asigna esos objetos tú mismo en lugar de usar `bindState`.

`features/dashboard/view.zig`

```zig
const PageCache = struct {
    allocator: std.mem.Allocator,
    rows: std.ArrayList(Item) = .empty,

    fn release(self: *PageCache) void {
        self.rows.deinit(self.allocator);
        self.allocator.destroy(self);
    }
};

fn mountDashboard(cx: *ui.Cx, parent: *ui.Scope) !*ui.Node {
    const scope = try parent.childScope();

    // Page-lifetime resource: freed when this scope is disposed,
    // not when the window closes.
    const cache = try cx.allocator.create(PageCache);
    cache.* = .{ .allocator = cx.allocator };
    scope.onCleanup(PageCache, cache, PageCache.release) catch |err| {
        cx.allocator.destroy(cache);
        return err;
    };

    return ui.vstack(cx, .{ .gap = cx.tokens.space._4 }, .{});
}
```

## Lista de verificación

-   **Mantén main.zig corto.** Allocator, `App.init` y `runWith`; nada más.
    
-   **Importa solo ui y zenit\_app.** Si algo que necesitas no está en los dos niveles públicos, abre un issue en lugar de meterte en el código interno.
    
-   **Ciclo de vida de ventana → bindState, de página → Scope.** Da al primero un pub `deinit` y al segundo un `onCleanup`; nunca ambos.
    
-   **Escribe los estilos como funciones con nombre.** Ponlos en styles.zig y úsalos con los constructores `*Styled` para que los cambios de tema funcionen sin más.
