— Antoine de Saint-Exupéry, probablemente. Si no fue él, debería haberlo sido.
Terminaste el cap. 28. Tienes un mini metroidvania que se mueve, tiene enemigos, recoges items, llegas a un objetivo, guardas la partida y oyes efectos. Pero no está listo para publicar. Le falta todo lo que pasa entre "anda en mi PC" y "está en Steam" o "está en itch.io".
Este capítulo es la continuación natural: cómo llevar tu proyecto Bevy de un prototipo local a un release público. Cubre:
Nota: el cap. 14B y el cap. 14 ya cubren organización del código. Acá nos enfocamos en lo que viene DESPUÉS: el release.
Tu juego con cargo run pesa ~200 MB. En el momento que lo veas, te va a dar algo. Bienvenido al desarrollo de juegos en Rust. El binario sin optimizar de Bevy con todas las features pesa eso. Hay cuatro técnicas que lo bajan a ~30 MB:
# cap-28B — sección 28B.2: Cargo.toml para release pequeño.
[profile.release]
opt-level = 3 # Para 2D, "3" suele dar mejor balance que "z".
# Usa "z" solo si el tamaño es absolutamente crítico
# (webassembly, embedidos), a costa de algo de velocidad.
lto = "fat" # Link-Time Optimization: el linker ve todo el código.
# "fat" = más lento de compilar, más pequeño/rápido binario.
codegen-units = 1 # Una unidad de compilación (lento, máx. optimización).
strip = true # Quita símbolos de debug del binario.
panic = "abort" # En vez de unwind, aborta. Más pequeño.
incremental = false # No incremental en release (más consistente).
Eso solo baja el binario a ~80-100 MB. Para llegar a 30 MB, sumamos selección de features:
# cap-28B — sección 28B.2: features mínimas para un 2D puro.
# Importante: NO existe una feature llamada "2d" en Bevy (es un error común
# copiado de tutoriales viejos). El soporte 2D ya viene incluido en `bevy_sprite`.
[dependencies]
bevy = { version = "0.19", default-features = false, features = [
"bevy_asset",
"bevy_audio",
"bevy_sprite",
"bevy_state",
"bevy_text",
"bevy_winit",
"default_font",
"vorbis", # decodificador .ogg para bevy_audio
] }
Apagas las features que no usas: sin bevy_pbr (3D PBR), sin bevy_gltf (si no cargas glTF), sin tonemapping_luts, etc. Ojo: bevy_sprite ya arrastra el pipeline de render 2D, no necesitas nada llamado "2d".
Truco pro: el linker mold o lld reduce tiempos de compilación y a veces el binario:
# cap-28B — sección 28B.2: usar mold como linker.
RUSTFLAGS="-C link-arg=-fuse-ld=mold" cargo build --release
Truco aún más pro: cargo-bloat te dice qué parte del binario es Bevy, qué parte es tu código, y qué parte son features que activaste sin usar. Ejecútalo y mira los 10 crates más pesados.
cargo install cargo-bloat
cargo bloat --release --crates
Típicamente, bevy_render y bevy_winit son los gordos. Si no usas winit (por ejemplo, si tu juego corre en un dispositivo custom), puedes apagarlo.
Si vas a publicar en web, el binario es .wasm. Un binario wasm32-unknown-unknown sin optimizar pesa ~50 MB. Con wasm-opt (parte de binaryen):
# cap-28B — sección 28B.3: optimizar wasm.
# -Oz = optimiza para tamaño. -O3 = para velocidad. -Os = balance.
wasm-opt -Oz -o game-opt.wasm game.wasm
Baja a ~15-20 MB. Con wasm-strip (parte de wabt), puedes sacar más:
wasm-strip game-opt.wasm -o game-strip.wasm
Para web serio, la cadena es: cargo build --release --target wasm32-unknown-unknown → wasm-bindgen → wasm-opt → wasm-strip → gzip en el servidor (los CDNs como itch.io, GitHub Pages, Cloudflare Pages lo hacen automáticamente).
Caveat: un .wasm de 20 MB tarda en descargarse. Para un juego web, lo óptimo es ~5-10 MB. Si no llegas, considera lazy loading: cargas assets y partes del juego según se necesitan.
El escenario clásico: tu juego anda en tu Linux de desarrollo, pero ¿anda en Windows? ¿En Mac? La respuesta corta: con Bevy y Rust, sí, pero hay que configurar bien.
# cap-28B — sección 28B.4: GitHub Actions para build matriz.
# .github/workflows/release.yml
name: release
on:
push:
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact: my-game-linux.tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact: my-game-windows.zip
- os: macos-latest
target: x86_64-apple-darwin
artifact: my-game-macos-x86_64.tar.gz
- os: macos-latest
target: aarch64-apple-darwin
artifact: my-game-macos-arm64.tar.gz
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- run: cargo build --release --target ${{ matrix.target }}
- run: |
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
tar czf ${{ matrix.artifact }} -C target/${{ matrix.target }}/release my-game
elif [ "${{ matrix.os }}" = "windows-latest" ]; then
7z a ${{ matrix.artifact }} target/${{ matrix.target }}/release/my-game.exe
else
tar czf ${{ matrix.artifact }} -C target/${{ matrix.target }}/release my-game
fi
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: ${{ matrix.artifact }}
Eso te da builds para los tres OS principales (con Mac cubriendo Intel y Apple Silicon). Para web, agregas un job adicional con ubuntu-latest y target wasm32-unknown-unknown.
Caveat Linux: las distros Linux vienen en sabores (glibc, musl, etc.). Para un binario verdaderamente portable, compila con --target x86_64-unknown-linux-musl. Eso produce un binario estático que corre en cualquier Linux moderno. Para Bevy, además necesitas instalar las dependencias del sistema (Ubuntu: libasound2-dev libudev-dev pkg-config).
Caveat Mac: Apple Silicon (M1, M2, M3) vs Intel. Publica ambos: aarch64-apple-darwin y x86_64-apple-darwin. O un "universal binary" con lipo (más complejo).
Tu juego tiene sprites, sonidos, música, fuentes, datos. ¿Cómo van en el binario?
Opción A: assets como carpeta al lado del binario. El método simple. Estructura:
my-game/
├── my-game.exe # binario.
└── assets/ # carpeta con todo.
├── sprites/
│ ├── player.png
│ └── enemy.png
├── sounds/
│ ├── jump.ogg
│ └── gem.ogg
└── fonts/
└── pixel.ttf
Pros: simple, el usuario puede moddear. Contras: el usuario puede borrar la carpeta y romper el juego. Para un release, en general quieres evitar esto.
Opción B: assets embebidos en el binario. El crate include_dir te permite meter toda la carpeta en el binario en tiempo de compilación:
// cap-28B — sección 28B.5: assets embebidos con include_dir.
use include_dir::include_dir;
static ASSETS: include_dir::Dir = include_dir!("$CARGO_MANIFEST_DIR/assets");
// En Bevy, para cargar un asset embebido NO uses turbofish en `load`.
// La forma estándar es dejar que el tipo se infiera del handle al que se asigna:
fn load_player_sprite(asset_server: Res<AssetServer>) -> Handle<Image> {
asset_server.load("embedded://crate/assets/sprites/player.png")
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Prefijo especial que `bevy_asset` interpreta como "embedded".
// El tipo Image se infiere del tipo de retorno del handle.
}
Importante: evita el turbofish
asset_server.load::<Image>(...). La forma estándar es dejar que el tipo se infiera del handle al que asignas el resultado (let h: Handle<Image> = asset_server.load(...)) o usar el helper del AssetServer. El turbofish no es la API recomendada y en algunas versiones compila mal con pathsembedded://.
Pros: binario único, imposible de romper. Contras: binario más grande, hot-reload más difícil.
Opción C: assets como archivo .pak (custom o con crate). Empaquetas todos los assets en un solo archivo. El usuario no puede tocarlo. Más complejo; solo vale la pena para juegos grandes.
Para un indie pequeño, opción A con instrucciones claras ("no borres la carpeta assets") es suficiente. Para un juego grande, opción B con include_dir es razonable.
Tu juego tiene cosas que cambian: URL del servidor, claves de API, paths de assets, niveles de logging. La solución idiomática en Rust: el crate config o un struct propio con serde.
//! cap-28B — sección 28B.6: config con serde + RON.
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct GameConfig {
pub title: String,
pub server_url: Option<String>, // None en single-player.
pub assets_path: String,
pub log_level: String,
}
fn load_config() -> GameConfig {
let path = if cfg!(debug_assertions) {
"config.dev.ron"
} else {
"config.release.ron"
};
let raw = std::fs::read_to_string(path).expect("config file");
ron::from_str(&raw).expect("valid RON")
}
Y los archivos:
// config.dev.ron
(
title: "Mi Juego (dev)",
server_url: None,
assets_path: "assets/",
log_level: "debug",
)
// config.release.ron
(
title: "Mi Juego",
server_url: Some("https://api.mijuego.com"),
assets_path: "assets/",
log_level: "warn",
)
Truco: nunca hardcodees URLs ni claves en el código. Siempre en un archivo de config. Y nunca commitees el config.release.ron con las claves reales. Usa variables de entorno o un .gitignore para los secrets.
Tu juego v0.3 guarda así:
#[derive(Serialize, Deserialize)]
struct SaveV1 { player_name: String, hp: i32 }
En v0.4 agregas un campo level: u32. ¿Qué pasa con los saves de v0.3? El Deserialize falla o produce un struct incompleto. La solución: migraciones.
//! cap-28B — sección 28B.7: migración manual con versión en el header.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct SaveV1 { player_name: String, hp: i32 }
#[derive(Serialize, Deserialize)]
struct SaveV2 { player_name: String, hp: i32, level: u32 }
impl SaveV1 {
fn migrate(self) -> SaveV2 {
SaveV2 {
player_name: self.player_name,
hp: self.hp,
level: 1, // default para migraciones v1 → v2.
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "version")]
enum SaveAny {
#[serde(rename = "1")]
V1(SaveV1),
#[serde(rename = "2")]
V2(SaveV2),
}
impl SaveAny {
fn to_v2(self) -> SaveV2 {
match self {
SaveAny::V1(v1) => v1.migrate(),
SaveAny::V2(v2) => v2,
}
}
}
// Al cargar:
fn load_save() -> SaveV2 {
let raw = std::fs::read_to_string("save.json").unwrap();
let any: SaveAny = serde_json::from_str(&raw).unwrap();
any.to_v2()
}
La regla: cada save tiene una versión en su header. Al cargar, miras la versión; si es vieja, aplicas las migraciones en cadena. Esto se vuelve crítico cuando un jugador tiene 50 horas de save y subes un patch. El crate bevy_save automatiza esto con un trait Migrate, pero la idea es la misma.
El cap. 24 cubre persistencia. Acá el énfasis: si tu juego tiene saves, las migraciones son obligatorias desde el día uno. Piénsalo antes, no después.
Después de publicar, ¿cómo sabes si tu juego corre bien en hardware modesto? Tres formas:
Para un indie, opción 2 + 3 es suficiente. Para un juego grande, opción 1 tiene sentido.
Una métrica clave: el percentil 95 de FPS (no el promedio). El promedio puede decir 60 fps mientras que el 5% de los frames son 12 fps (lo que se siente horrible). Tracy (cap. 26) o un profiler simple te da esto.
□ El binario arranca y se cierra limpiamente.
□ El binario arranca y se cierra en cada OS target.
□ El binario corre con assets completos.
□ El binario corre sin assets (al menos da un error claro, no un panic).
□ Los saves se cargan y guardan correctamente.
□ Las migraciones de save se aplican correctamente.
□ El binario de release no tiene el inspector/BRP.
□ El binario de release no tiene features de debug.
□ El tamaño del binario es razonable (< 50 MB para desktop, < 10 MB para web).
□ El juego cabe en una resolución mínima razonable (1280×720).
□ El input funciona con gamepad y teclado.
□ El sonido funciona (verifica que los .ogg cargan, no solo en tu PC).
□ El idioma del juego es el correcto.
□ El icono del juego está bien (Windows: .ico; Mac: .icns; Linux: .png en .desktop).
□ El "about" tiene la versión correcta.
□ El save default no contiene datos sensibles.
□ El save default está en una carpeta accesible al usuario (no en Program Files).
Release: el binario que se distribuye al usuario final.
Binary size: tamaño del binario. Bevy sin optimizar ~200 MB; optimizado ~30 MB.
opt-level: nivel de optimización del compilador Rust. "z" = tamaño, "3" = velocidad, "s" = balance.
LTO (Link-Time Optimization): el linker optimiza entre crates. Más lento de compilar, binario más chico/rápido. Valores: true/false/"thin"/"fat".
codegen-units: número de unidades de compilación. Menos = más optimización, más lento.
strip: quitar símbolos de debug del binario final.
panic = abort: en caso de panic, abortar en vez de hacer unwinding. Más chico.
mold / lld: linkers alternativos a ld. Más rápidos.
cargo-bloat: herramienta que muestra qué crates pesan más en tu binario.
wasm-opt: optimizador de WebAssembly (parte de binaryen).
wasm-strip: eliminador de secciones innecesarias de .wasm (parte de wabt).
wasm-bindgen: puente entre Rust y JavaScript para WebAssembly.
GitHub Actions: CI de GitHub. Estándar para builds de open source.
Build matrix: estrategia de CI que corre el mismo build en múltiples OS/arquitecturas.
include_dir: crate que embebe una carpeta en el binario en tiempo de compilación.
config (crate): crate para gestionar configuración desde archivos.
Migración de save: conversión de un save de versión antigua a la nueva.
Telemetry: envío de datos de uso desde el juego a un servidor.
Percentil 95: métrica que ignora el 5% peor. Útil para "frame time" porque el promedio miente.
Release checklist: lista mental o automatizada de cosas a verificar antes de publicar.
bevy_cli: CLI emergente (TheBevyFlock/bevy_cli, alpha) que automatiza builds, migraciones y scaffolding.
Hollow Knight (Team Cherry, 2017) — el equipo de 3 personas en Australia tardó 3 años en hacer el release. La parte de "publicar y mantener" les tomó otros 2 años (DLC, ports a Switch). Lección: el release es un trabajo continuo, no un evento.
Stardew Valley (ConcernedApe, 2016) — un solo desarrollador, publicado en 2016, sigue recibiendo updates en 2024. Cada update requiere un test de "no rompo saves viejos". La herramienta de migración de ConcernedApe es interna; el cap. 28B es la versión "hazlo bien desde el día uno".
Bevy 0.18 (2025) — mejoras notables en binary size: un Hello World cabe mucho mejor que en 0.13/0.14. El equipo de Bevy invirtió mucho en reducir el peso base.
GitHub Actions free tier (2020) — democratizó el CI. Antes, configurar Travis o AppVeyor era un dolor. Ahora, un `.github/workflows/release.yml` y listo.
mold linker (Rui Ueyama, 2020) — el linker rápido que cambió las expectativas de tiempo de compilación. Antes, linkear un proyecto grande era 30+ segundos; mold lo baja a 2-3.
WebAssembly MVP (W3C, 2017) — el primer estándar de WebAssembly. Permitió el ecosistema que hoy tiene Bevy como uno de los frameworks más serios.
bevy_save (hankjordan, 2024) — el crate de save/load más maduro para Bevy. Trae el trait `Migrate` que el cap. recomienda.
Steam Deck (Valve, 2022) — obligó a la industria indie a pensar en hardware modesto. Si tu juego corre en un Steam Deck, corre en la mayoría de PCs de los jugadores.
itch.io (Leaf Corcoran, 2013) — la plataforma que hizo accesible el publishing de juegos indie. Sin gatekeepers, sin review, sin comisión significativa.
Humble Bundle (Wolfire Games, 2010) — la primera plataforma de "paga lo que quieras". Cambió el modelo de negocio indie.
❌ Hardcodear URLs de servidor, API keys, paths absolutos.
✅ Un archivo de config + variables de entorno + `cfg!(debug_assertions)`.
💡 Por qué: hardcodear obliga a recompilar para cambiar nada. Un atacante que decompile tu binario ve tus claves. Un día quieres apuntar a otro servidor y tienes que re-buildear.
❌ Publicar sin haber probado el instalador en una máquina limpia.
✅ Probar en una VM con Windows/Linux/Mac limpios.
💡 Por qué: tu PC tiene mil cosas instaladas que el jugador no tiene. DLLs faltantes, codecs de audio, drivers. La VM limpia es la verdad.
❌ Asumir que los saves de v0.3 cargan en v0.4 porque "solo agregué un campo".
✅ Testear la migración con saves reales de v0.3.
💡 Por qué: serde es quisquilloso. `#[serde(default)]` te salva la vida, pero no es magia. Prueba.
❌ Poner `features = ["2d"]` (o "3d") en tu dependencia de Bevy. Esas features no existen.
✅ `features = ["bevy_sprite"]` para 2D, `features = ["bevy_pbr"]` para 3D PBR.
💡 Por qué: el error de cargo es críptico: "Package bevy does not have feature `2d`". La gente lo copia de tutoriales viejos.
❌ Olvidar incluir el icono y los assets de branding.
✅ Un checklist de "qué archivos van en el release".
💡 Por qué: un binario sin icono en Windows es amateur. Un juego sin logo en el menú principal es olvidable. La primera impresión importa.
Problema: el equipo de desarrollo trata el release como "el día que subimos el binario a Steam". El día llega, hay problemas, se patea para atrás, se hace a las apuradas, sale con bugs.
Solución: tratar el release como una feature más, con su milestone, su checklist, su test, su plan. No es un evento; es un proceso.
Milestone 12: el binario de release.
- Binary size < 50 MB (desktop), < 10 MB (web).
- Build matrix para 3 OS + wasm32.
- Config separada dev/release.
- Migraciones de save testeadas.
- Checklist corrida.
- QA en máquinas limpias.
- Icon, branding, metadata.
- Página de store (Steam, itch.io) lista.
Cuándo sí:
Cuándo no:
Empezaste este libro con un "Hola Mundo" en Bevy. Terminaste con un proyecto con su ciclo de release. Eso es el camino de un indie Bevy-dev. Lo que viene después es:
El libro se cierra. Tu viaje no. ¡A publicar!
bevy_github_ci_template + bevy_cli: CI/CD sin reinventar la ruedaHay dos herramientas que ahorran semanas de fricción. La primera es la plantilla oficial de GitHub Actions que el Bevy Engine org mantiene en bevyengine/bevy_github_ci_template. La segunda, emergente pero prometedora, es bevy_cli (TheBevyFlock/bevy_cli), un comando unificado que automatiza builds, scaffolding de plugins y migraciones de versión.
# Opción 1: clonar la plantilla dentro de tu proyecto.
git submodule add https://github.com/bevyengine/bevy_github_ci_template .github
# Opción 2: usar bevy_cli para scaffolding + build.
cargo install bevy_cli --git https://github.com/TheBevyFlock/bevy_cli
bevy new my-game # scaffolding con estructura recomendada
bevy build --release --web # build para web (incluye wasm-bindgen, wasm-opt)
bevy migrate 0.18 0.19 # aplica migraciones automáticas de versión
Estado de
bevy_cli: al escribir esto (julio 2026) está en alpha. Algunos subcomandos pueden cambiar o romper entre versiones. Para producción hoy (2026), usa la plantilla oficial de CI; dejabevy_clipara scaffolding y experimentación local.
Lo que trae la plantilla oficial de CI:
ci.yml: en cada PR, corre cargo check, cargo clippy, cargo fmt --check, cargo test para Linux, Windows, macOS, y WASM. Cachea las deps con Swatinem/rust-cache para que tarde 2 min en vez de 30.release.yml: cuando pusheas un tag v*.*.*, compila para las cinco plataformas (linux-x64, linux-arm64, windows-x64, macos-x64, macos-arm64, wasm32), comprime los bins, y crea un GitHub Release con los assets adjuntos.itch.yml (opcional): si activas el secreto BUTLER_CREDENTIALS, sube automáticamente a itch.io con butler push.# .github/workflows/release.yml (extracto).
name: Release
on:
push:
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-latest
target: x86_64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run: cargo build --release --target ${{ matrix.target }}
- run: |
mkdir -p artifacts
cp target/${{ matrix.target }}/release/my_game artifacts/
- uses: softprops/action-gh-release@v2
with:
files: artifacts/*
WASM build (en el mismo workflow):
wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run: rustup target add wasm32-unknown-unknown
- run: cargo install wasm-bindgen-cli --locked
- run: cargo build --release --target wasm32-unknown-unknown
- run: |
wasm-bindgen --out-dir artifacts/web --target web \
target/wasm32-unknown-unknown/release/my_game.wasm
- run: |
# Optimización opcional: wasm-opt + wasm-strip.
wasm-opt -Oz -o artifacts/web/my_game.wasm artifacts/web/my_game_bg.wasm
wasm-strip artifacts/web/my_game.wasm
- uses: softprops/action-gh-release@v2
with:
files: artifacts/web/*
Publicación en Steam y itch.io:
butler, la CLI oficial de itch. Tras loguearte (butler login), subes con butler push my-game-build user/game:channel. Es gratuito, sin review, y acepta HTML5 (wasm) directamente. Ideal para demos y game jams.steamworks (no oficial pero mantenido) o empaquetar con el steamcmd directamente desde CI. Hay plantillas de GitHub Actions que envuelven steamcmd para subir builds tras un tag.# Publicar a itch.io desde tu máquina o desde CI.
butler login
butler push artifacts/windows myuser/my-game:windows
butler push artifacts/linux myuser/my-game:linux
butler push artifacts/web myuser/my-game:html5
Cuándo usar la plantilla + bevy_cli:
Cuándo NO usarlas:
Personalización común:
my_game → el nombre real).RUSTFLAGS para optimizaciones de tamaño.La plantilla es el punto de partida, no el final. La lees, entiendes qué hace, y la adaptas a tu juego. Pero empezar desde cero (sin la plantilla) es un error que cuesta 1 semana de fricción con YAML.
cargo build --release para nativo, cargo build --target wasm32-unknown-unknown + wasm-bindgen para WASM. bevy_github_ci_template te da los workflows de GitHub Actions listos (Linux/Windows/macOS/WASM). bevy_cli es la herramienta emergente para automatizar builds y migraciones (aún en alpha).opt-level = 3 (o "z" si el tamaño es crítico), lto = "fat", codegen-units = 1, panic = "abort", strip = true. Para WASM, añade wasm-opt -Oz y wasm-strip. Compila en debug, perfila, configura release. El binario pasa de 80 MB a 8 MB.bevy_asset soporta .meta files para configurar import settings por asset.steamworks o steamcmd vía CI. Para itch.io, butler push automatiza el upload. Para Google Play / App Store, los wrappers de Tauri o los ports móviles de Bevy.bevy_diagnostic + un panel web de métricas te dice qué está pasando en producción. Nunca PII; solo FPS, load times, crash counts.git tag v0.1.0 + cargo release automatiza el bump de versión y la creación de GitHub Releases. Adjunta el binario y el changelog.Aquí se cierra el libro principal. Lo que viene después son los anexos: glosario maestro (Anexo A), compatibilidades Bevy 0.13→0.19 (Anexo B), recursos recomendados (libros, blogs, Discord, crates — Anexo C), plugins de terceros ordenados por bloque (Anexo D), y guía de WASM (Anexo E). El Anexo A es tu diccionario de cabecera; el B tu tabla de migración; el C tus siguientes lecturas; el D tu catálogo de crates cuando necesites algo concreto; el E tu puente al navegador. Si el libro principal era el camino, los anexos son el mapa que dejas en la mesita para no perderte en el próximo proyecto. Anexos a la vista, y un abrazo de quien esto escribe.