diff --git a/openspec/changes/gemini-adapter-fase-1/exploration.md b/openspec/changes/gemini-adapter-fase-1/exploration.md new file mode 100644 index 00000000..b2b26792 --- /dev/null +++ b/openspec/changes/gemini-adapter-fase-1/exploration.md @@ -0,0 +1,193 @@ +# Exploration: Gemini Adapter — Phase 1 + +## Current State + +Phase 1 (`llm-agent-core-fase-1`) shipped a **provider-agnostic adapter seam** with one concrete implementation (`OpenAICompatibleAdapter`). The architecture is: + +``` +Client (orchestrator.ts) → POST /api/agent/chat → route.ts → createProvider(config) → ProviderAdapter.chat() +``` + +### Key contracts already in place + +| Artifact | Location | Role | +|----------|----------|------| +| `ProviderAdapter` interface | `apps/web/src/agent/providers/types.ts` | Single `chat()` method, provider-agnostic | +| `ProviderConfig` | Same file | `{ provider, apiKey, model, baseUrl? }` | +| `ProviderResponse` | Same file | `{ content, toolCalls? }` | +| `ToolSchema` (internal) | `apps/web/src/agent/types.ts` | `{ name, description, parameters: Array<{ key, type, required }> }` | +| `ToolCall` (internal) | Same file | `{ id, name, args }` | +| `createProvider()` factory | `apps/web/src/agent/providers/index.ts` | Switch on `config.provider`, returns adapter | +| `OpenAICompatibleAdapter` | `apps/web/src/agent/providers/openai-compatible.ts` | Reference implementation | +| Route handler | `apps/web/src/app/api/agent/chat/route.ts` | Reads `LLM_*` env vars, calls `createProvider()` | +| Orchestrator (client) | `apps/web/src/agent/orchestrator.ts` | Multi-turn loop, MAX_ITERATIONS=8 | + +The orchestrator runs **client-side** and drives a multi-turn loop: it POSTs the full message history each turn, the route delegates to the adapter, and the orchestrator resolves tool calls via the registry before looping. The adapter is **stateless** — it receives the full conversation every call. + +### Test patterns + +- Tests use `bun:test` with `jest.mock()` for SDK mocking. +- Factory tests: verify correct adapter returned per provider string, unknown throws. +- Adapter tests: mock SDK `create()`, assert wire-format conversion of messages/tools, response parsing. +- Route tests: mock `createProvider`, assert 400/502/status paths. + +## Affected Areas + +| File | Why | +|------|-----| +| `apps/web/src/agent/providers/gemini.ts` | **NEW** — Gemini adapter implementation | +| `apps/web/src/agent/providers/index.ts` | Add `"gemini"` case to factory switch | +| `apps/web/src/agent/providers/types.ts` | May need minor extension if `baseUrl` semantics differ | +| `apps/web/.env.example` | Document `LLM_PROVIDER=gemini` option | +| `apps/web/package.json` | Add `@google/generative-ai` dependency | +| `apps/web/src/agent/providers/__tests__/gemini.test.ts` | **NEW** — adapter unit tests | +| `apps/web/src/agent/providers/__tests__/index.test.ts` | Add factory test for `"gemini"` | + +**Unchanged** (confirmed safe): +- `route.ts` — already provider-agnostic via factory +- `orchestrator.ts` — client-side, never touches provider details +- `types.ts` (agent types) — `ToolSchema`, `ToolCall`, `ChatMessage` are provider-agnostic +- `tools/registry.ts` — provider-agnostic schema export +- `system-prompt.ts` — pure function, no provider coupling + +## Gemini API — Function Calling Wire Format + +### Differences from OpenAI + +| Concern | OpenAI | Gemini | +|---------|--------|--------| +| SDK | `openai` npm package | `@google/generative-ai` npm package | +| Client init | `new OpenAI({ apiKey, baseURL? })` | `new GoogleGenerativeAI(apiKey)` then `getGenerativeModel({ model, tools })` | +| Tool declaration | `{ type: "function", function: { name, description, parameters } }` | `{ functionDeclarations: [{ name, description, parameters }] }` | +| Parameter types | lowercase JSON Schema: `object`, `string`, `number` | UPPERCASE: `OBJECT`, `STRING`, `NUMBER`, `BOOLEAN`, `ARRAY` | +| Required params | `required: ["key"]` array in schema | `required: ["key"]` array — same shape | +| Response — function call | `choice.message.tool_calls[].function` | `response.functionCalls()` array | +| Function call ID | Has `id` field | **No `id` field** — uses `name` for matching | +| Multi-turn history | Stateless: full message array each call | Stateful: `startChat()` accumulates, OR stateless via `generateContent` with full history | +| Tool result | `{ role: "tool", tool_call_id, content }` | `{ functionResponse: { name, response } }` | +| System prompt | `{ role: "system", content }` message | `systemInstruction` on model config, OR `user` message with system text | + +### Critical mapping: Gemini → Internal types + +``` +Gemini FunctionCall { name, args } → Internal ToolCall { id: , name, args } +Gemini FunctionDeclaration → Internal ToolSchema conversion needed (type casing) +Gemini functionResponse → mapped to internal tool_result role +``` + +**Key gotcha**: Gemini function calls have **no ID**. The internal `ToolCall` requires `id`. The adapter must synthesize IDs (e.g., `tc_${nanoid()}` or `gemini_${index}`). + +### Multi-turn approach + +The current adapter contract is **stateless** — `chat()` receives full messages every call. Gemini's `startChat()` is stateful. Two options: + +1. **Stateless via `generateContent()`**: Pass full conversation history as `contents` array each call. This matches the current contract perfectly. +2. **Stateful via `startChat()`**: Would require adapter to maintain session state — breaks the contract. + +**Recommendation**: Use `generateContent()` with full `contents` array — stateless, matches contract, simpler. + +## Approaches + +### 1. Native Gemini SDK Adapter (Recommended) + +Create `GeminiAdapter` class implementing `ProviderAdapter` using `@google/generative-ai` SDK. + +**Pros**: +- Type-safe SDK — handles auth, retries, streaming-ready for future +- Google's SDK manages edge cases (safety settings, grounding) +- Matches pattern of `OpenAICompatibleAdapter` using the `openai` SDK +- Future-proof for video/multimodal when that phase arrives + +**Cons**: +- New dependency (`@google/generative-ai`, ~50KB) +- SDK-specific quirks to learn (e.g., `generateContent` vs `startChat`) + +**Effort**: Medium — adapter + conversion logic + tests ≈ 150-200 lines + +### 2. Raw `fetch` to Gemini REST API + +Implement adapter using native `fetch` against `https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent`. + +**Pros**: +- Zero dependencies +- Full control over request shape +- Educational — understand every byte + +**Cons**: +- Manual auth header management +- No type safety for response shapes +- Must handle all edge cases manually (safety ratings, grounding, etc.) +- Deviates from established pattern (OpenAI adapter uses SDK) + +**Effort**: Medium-High — more boilerplate, more edge cases + +### 3. OpenAI-Compatible Proxy (Gemini via OpenAI format) + +Use an existing OpenAI-compatible proxy/endpoint that translates Gemini to OpenAI format, then reuse `OpenAICompatibleAdapter` with a custom `baseUrl`. + +**Pros**: +- Zero code changes — just config +- Immediate + +**Cons**: +- **External dependency on a proxy service** — unreliable, adds latency +- Gemini-specific features (multimodal, large context) may not translate +- Defeats the purpose of having a native adapter +- User would need to set up/maintain the proxy + +**Effort**: Low — but architecturally wrong + +## Recommendation + +**Approach 1: Native Gemini SDK Adapter.** + +Rationale: +1. Matches the established pattern (SDK-backed adapter, see `OpenAICompatibleAdapter`). +2. Clean adapter boundary — the `ProviderAdapter` contract needs **zero changes**. +3. Only the factory switch + new file needed — route, orchestrator, types all untouched. +4. Sets up cleanly for future multimodal/video upload phases. + +## Minimum Scope (Phase 1) + +### In scope +- `GeminiAdapter` class with `chat()` implementing `ProviderAdapter` +- Message conversion: internal `ChatMessage[]` → Gemini `contents[]` + `systemInstruction` +- Tool schema conversion: internal `ToolSchema[]` → Gemini `FunctionDeclaration[]` +- Response parsing: Gemini `functionCalls` → internal `ToolCall[]` (with synthesized IDs) +- Tool result conversion: internal `tool_result` messages → Gemini `functionResponse` parts +- Factory registration: `"gemini"` case in `createProvider()` +- Config: `LLM_PROVIDER=gemini`, `LLM_API_KEY`, `LLM_MODEL=gemini-2.5-flash` +- Tests: factory, adapter (message conversion, tool conversion, response parsing, error handling) +- `.env.example` update + +### Explicitly deferred +- Video/file upload API (multimodal input) — requires `FileAPI` integration +- `@asset` references in prompts — needs multimodal pipeline +- Streaming responses — requires `ProviderAdapter` contract change (streaming interface) +- Image understanding — requires multimodal content parts +- Safety settings / content filtering customization +- Grounding with Google Search +- Thinking/reasoning config + +## Risks + +1. **No native tool call IDs in Gemini** — adapter must synthesize them. If the orchestrator or client uses tool call IDs for matching, synthesized IDs must be deterministic or the orchestrator must handle mismatches gracefully. Current orchestrator matches by sequential position via `toolCallId` — this should work as long as the adapter generates unique IDs per call. + +2. **Parameter type mapping** — internal `ToolSchema` uses lowercase types (`"string"`, `"number"`, `"boolean"`). Gemini expects UPPERCASE. The adapter must transform these. If a new type is added to `ToolSchema` later (e.g., `"array"`, `"object"`), the adapter must handle it. + +3. **`baseUrl` unused for Gemini** — Google's endpoint is fixed. If someone sets `LLM_BASE_URL` with `LLM_PROVIDER=gemini`, the adapter should either ignore it or use it as an override for Vertex AI endpoints. Need a decision. + +4. **System prompt placement** — Gemini supports `systemInstruction` on model config OR as a user message. Using `systemInstruction` is cleaner but means the system prompt goes to the model constructor, not in the `contents` array. The adapter must handle this correctly. + +5. **Rate limits / pricing** — Gemini Flash is generous but not unlimited. Not a code risk, but worth documenting for users. + +6. **SDK version compatibility** — `@google/generative-ai` is marked as "deprecated" in the Context7 listing (the library ID is `/google-gemini/deprecated-generative-ai-js`). Need to verify the current recommended package. Google has been migrating to `@google/genai` as the newer SDK. Must check which one to use before implementation. + +## Ready for Proposal + +**Yes.** The scope is clear, the contract is well-defined, and the risks are identified. The next step is to run `sdd-propose` to formalize the change proposal. + +Key inputs for the proposal: +- Scope: single new adapter + factory registration + tests + env docs +- No changes to existing contracts (`ProviderAdapter`, `ToolSchema`, `ToolCall`) +- Verification: unit tests passing, `bun test` green, `LLM_PROVIDER=gemini` resolves correctly diff --git a/rust/crates/compositor/src/frame.rs b/rust/crates/compositor/src/frame.rs index 871a9759..01114280 100644 --- a/rust/crates/compositor/src/frame.rs +++ b/rust/crates/compositor/src/frame.rs @@ -24,6 +24,7 @@ pub struct CanvasClearDescriptor { pub enum FrameItemDescriptor { Layer(LayerDescriptor), SceneEffect { + #[serde(default, rename = "effectPassGroups")] effect_pass_groups: Vec>, }, } diff --git a/setup-steps.md b/setup-steps.md new file mode 100644 index 00000000..6a974e14 --- /dev/null +++ b/setup-steps.md @@ -0,0 +1,398 @@ +# Setup Steps - NeuralCut (Windows) + +Guia paso a paso para levantar el proyecto desde cero en Windows. +Si es tu primer proyecto de software, no te preocupes: cada paso esta explicado. + +> **Antes de empezar:** Abrí **PowerShell como Administrador** (click derecho en el menu Inicio > "Terminal (Administrador)" o "Windows PowerShell (Administrador)"). Vas a necesitarlo para todos los pasos de instalacion. + +--- + +## Indice + +1. [Instalar herramientas base (Git + Node.js + Docker)](#1-instalar-herramientas-base) +2. [Instalar Bun](#2-instalar-bun) +3. [Clonar el repositorio](#3-clonar-el-repositorio) +4. [Configurar variables de entorno](#4-configurar-variables-de-entorno) +5. [Levantar la base de datos con Docker](#5-levantar-la-base-de-datos-con-docker) +6. [Instalar dependencias del proyecto](#6-instalar-dependencias-del-proyecto) +7. [Correr migraciones de la base de datos](#7-correr-migraciones-de-la-base-de-datos) +8. [Iniciar el servidor de desarrollo](#8-iniciar-el-servidor-de-desarrollo) +9. [Configurar VS Code](#9-configurar-vs-code-recomendado) +10. [Comandos utiles](#10-comandos-utiles) +11. [Instalar y usar opencode](#11-instalar-y-usar-opencode) + +--- + +## 1. Instalar herramientas base + +Vamos a instalar todo de una con **winget**, el gestor de paquetes que ya viene en Windows. Abrí **PowerShell como Administrador** y ejecuta estos tres comandos: + +```powershell +winget install Git.Git --accept-source-agreements --accept-package-agreements +``` + +```powershell +winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements +``` + +```powershell +winget install Docker.DockerDesktop --accept-source-agreements --accept-package-agreements +``` + +> **Que son estas herramientas?** +> - **Git**: Control de versiones. Permite que varias personas trabajen en el mismo codigo sin pisarse. +> - **Node.js**: Entorno para ejecutar JavaScript fuera del navegador. Incluye `npm` automaticamente. +> - **Docker Desktop**: Permite correr servicios (base de datos, Redis) en contenedores sin instalarlos a mano. + +Cada uno se baja e instala solo. Cuando terminen los tres, **cierra la terminal y abri una nueva** (no hace falta que sea admin esta vez). Verifica que anden: + +```powershell +git --version +node --version +npm --version +docker --version +``` + +Si los cuatro te muestran numeros de version, todo bien. + +**Configura tu identidad en Git** (necesario para hacer commits): + +```powershell +git config --global user.name "Tu Nombre" +git config --global user.email "tu@email.com" +``` + +> **Si `winget` no existe** (muy poco probable en Windows 10/11): Instalá cada herramienta manualmente desde sus webs: [Git](https://git-scm.com/download/win), [Node.js LTS](https://nodejs.org/), [Docker Desktop](https://www.docker.com/products/docker-desktop/). + +> **Si Docker no arranca** despues de instalarlo, saltea a [Solucion de problemas de Docker](#solucion-de-problemas-de-docker-en-windows). + +--- + +## 2. Instalar Bun + +Bun es el gestor de paquetes que usa este proyecto (es mas rapido que npm). Se instala con npm: + +```powershell +npm install -g bun +``` + +Verifica: + +```powershell +bun --version +``` + +Deberias ver algo como `1.2.x`. + +--- + +## 3. Clonar el repositorio + +Clonar significa descargar una copia del codigo fuente a tu computadora. + +1. Navega a la carpeta donde quieras guardar el proyecto: + +```powershell +cd C:\Users\TuUsuario\Documents +``` + +2. Clona el repo: + +```powershell +git clone https://github.com/TU-USUARIO/NeuralCut.git +``` + +> **OJO:** Reemplaza `TU-USUARIO` con el usuario/organizacion correcta del repositorio. Si te dieron un fork propio, usa tu usuario. + +3. Entra a la carpeta del proyecto: + +```powershell +cd NeuralCut +``` + +--- + +## 4. Configurar variables de entorno + +Las variables de entorno son configuraciones que la aplicacion necesita para funcionar (conexion a la base de datos, claves secretas, etc.). + +El proyecto tiene un archivo de ejemplo con los valores por defecto. Copialo: + +```powershell +Copy-Item apps\web\.env.example apps\web\.env.local +``` + +> El `.env.example` ya tiene valores por defecto que coinciden con la configuracion de Docker. Para desarrollo local deberia funcionar tal cual. **No cambies los valores a menos que sepas lo que haces.** + +--- + +## 5. Levantar la base de datos con Docker + +**Antes de continuar:** Abri Docker Desktop desde el menu Inicio y espera a que diga "Docker Desktop is running" (el icono de la ballena en la barra de tareas deja de animarse). Esto puede tardar un minuto la primera vez. + +Desde la raiz del proyecto, ejecuta: + +```powershell +docker compose up -d db redis serverless-redis-http +``` + +Esto levanta tres servicios: +- **db**: Base de datos PostgreSQL (puerto 5432) +- **redis**: Cache en memoria (puerto 6379) +- **serverless-redis-http**: Interfaz HTTP para Redis (puerto 8079) + +Verifica que esten corriendo: + +```powershell +docker compose ps +``` + +Deberias ver tres servicios con estado `Up` o `healthy`. + +> **Para detener los contenedores** cuando termines de trabajar: `docker compose down` +> +> **Para detener y borrar los datos**: `docker compose down -v` (solo si queres empezar de cero) + +--- + +## 6. Instalar dependencias del proyecto + +Las dependencias son librerias externas que el proyecto necesita para funcionar (React, Next.js, etc.). + +```powershell +bun install +``` + +Esto lee el `package.json` y descarga todo. Puede tardar un par de minutos la primera vez. Si no hay errores rojos, todo bien (algunos warnings amarillos son normales). + +> **Si falla:** Prueba borrar `node_modules` y `bun.lock` y volver a correr `bun install`. + +--- + +## 7. Correr migraciones de la base de datos + +Las migraciones crean las tablas y estructuras necesarias en la base de datos. + +Asegurate de que Docker este corriendo (paso 5) y ejecuta: + +```powershell +bun run db:migrate +``` + +> **Si dice "no migrations to apply"**: No pasa nada, significa que ya estan aplicadas o que se aplicaran automaticamente. + +--- + +## 8. Iniciar el servidor de desarrollo + +```powershell +bun dev:web +``` + +Espera a que veas algo como: + +``` + ▲ Next.js 16.x.x + - Local: http://localhost:3000 +``` + +Abre tu navegador en **http://localhost:3000**. Deberias ver la aplicacion corriendo. Listo! + +> **Para detener el servidor:** Presiona `Ctrl + C` en la terminal. + +--- + +## 9. Configurar VS Code (recomendado) + +Si no tenes VS Code, instalalo con winget: + +```powershell +winget install Microsoft.VisualStudioCode --accept-source-agreements --accept-package-agreements +``` + +El proyecto ya tiene configuracion de VS Code en `.vscode/settings.json`. Para que funcione bien, instala estas extensiones: + +### Obligatoria + +1. **Biome** - Linter y formateador del proyecto. Corrige errores de codigo y formatea automaticamente al guardar. + +### Recomendadas + +2. **Tailwind CSS IntelliSense** - Autocompletado de clases CSS +3. **Error Lens** - Muestra errores inline directamente en el editor +4. **GitLens** - Muestra quien escribio cada linea de codigo y cuando + +Para instalar extensiones: abri VS Code, anda a la barra lateral izquierda (icono de cuadrados), busca cada extension por nombre y dale "Install". + +--- + +## 10. Comandos utiles + +Los comandos que van a usar dia a dia, desde la **raiz del proyecto**: + +| Comando | Que hace | +|---------|----------| +| `bun dev:web` | Inicia el servidor de desarrollo | +| `bun run build:web` | Compila el proyecto para produccion | +| `bun run lint:web` | Revisa errores de codigo | +| `bun run lint:web:fix` | Revisa y corrige errores automaticamente | +| `bun run format:web` | Formatea el codigo | +| `bun run db:migrate` | Aplica migraciones de base de datos | +| `docker compose up -d` | Levanta los servicios de Docker | +| `docker compose down` | Detiene los servicios de Docker | + +--- + +## 11. Instalar y usar opencode + +opencode es una herramienta de IA que te ayuda a escribir codigo, resolver bugs y navegar el proyecto directo desde la terminal. + +### Instalacion + +```powershell +npm install -g opencode +``` + +Verifica: + +```powershell +opencode --version +``` + +### Configuracion inicial + +opencode necesita una API key de un proveedor de IA. Preguntale al equipo cual usar y te pasan la config. + +### Como usarlo + +Desde la raiz del proyecto: + +```powershell +opencode +``` + +Se abre una interfaz interactiva en la terminal. Ahi podes: + +- **Preguntar sobre el codigo:** "Explicame que hace el archivo `apps/web/src/app/page.tsx`" +- **Pedir cambios:** "Agrega un boton que diga Hola en la pagina principal" +- **Buscar bugs:** "Revisa por que falla el login" +- **Buscar archivos:** "Donde esta el componente del timeline?" + +Para salir: `Ctrl + C` o `/exit` + +| Comando | Que hace | +|---------|----------| +| `/help` | Muestra la ayuda completa | +| `/exit` | Sale de opencode | +| `/compact` | Compacta la conversacion para ahorrar contexto | + +> **Tip:** opencode lee el archivo `AGENTS.md` del proyecto para entender las convenciones. No hace falta que le expliques la arquitectura cada vez. + +--- + +## Resumen rapido (para cuando ya lo configuraste una vez) + +```powershell +# 1. Abrir Docker Desktop manualmente + +# 2. Levantar servicios +docker compose up -d db redis serverless-redis-http + +# 3. Iniciar el servidor +bun dev:web + +# 4. Abrir http://localhost:3000 en el navegador + +# 5. Cuando termines de trabajar +# Ctrl+C en la terminal del servidor +docker compose down +``` + +--- + +## Solucion de problemas de Docker en Windows + +Docker Desktop en Windows necesita **virtualizacion** (hardware) y **WSL 2** (Windows). Si Docker tira un error al iniciar, segui estos pasos. + +### Paso 1: Verificar virtualizacion + +1. Abri **Administrador de tareas** (`Ctrl + Shift + Esc`) +2. Pestaña **Rendimiento** > **CPU** +3. Busca **Virtualización: Habilitada** + +Si dice **Deshabilitada**, hay que habilitarla en el BIOS: + +1. Reinicia la compu y presiona repetidamente la tecla para entrar al BIOS: + - **HP**: `F10` o `Esc` + - **Lenovo**: `F2` o `Fn + F2` + - **Dell**: `F2` + - **ASUS**: `F2` o `Supr` + - **Acer**: `F2` o `Supr` +2. Busca alguna de estas opciones (suele estar en **Advanced** > **CPU Configuration** o **Security**): + - `Intel Virtualization Technology` / `Intel VT-x` (Intel) + - `SVM Mode` / `AMD-V` (AMD) +3. Cambiala a **Enabled** +4. Guarda y sali (generalmente `F10`) + +### Paso 2: Habilitar WSL 2 + +Abri **PowerShell como Administrador** y ejecuta: + +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart +``` + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart +``` + +**Reinicia la computadora.** Despues, abri PowerShell normal y ejecuta: + +```powershell +wsl --set-default-version 2 +``` + +Si te pide instalar un kernel update, bajalo de https://aka.ms/wsl2kernel y ejecutalo. + +Verifica con `wsl --status` — deberia decir `Default Version: 2`. + +### Paso 3: Reiniciar Docker + +1. Abri Docker Desktop +2. Anda a **Settings** (engranaje) > **General** +3. Asegurate que **"Use the WSL 2 based engine"** este marcado +4. Docker deberia arrancar ahora + +> **Si despues de todo sigue sin funcionar**, desinstala Docker, reinicia, y vuelve a instalar: `winget install Docker.DockerDesktop`. Si aun asi falla, hablale a un companero. + +--- + +## Problemas comunes + +### "winget no se reconoce como un comando" +Tu Windows es muy viejo. Instalá las herramientas manualmente desde sus webs: [Git](https://git-scm.com/download/win), [Node.js LTS](https://nodejs.org/), [Docker Desktop](https://www.docker.com/products/docker-desktop/). + +### "bun no se reconoce como un comando" +Cerra la terminal y abrila de nuevo. Si persiste, corre `npm install -g bun` otra vez. + +### "docker compose up" falla con error de conexion +Asegurate de que Docker Desktop este abierto y mostrando "Docker Desktop is running". Espera unos segundos y vuelve a intentar. + +### "Error: connect ECONNREFUSED 127.0.0.1:5432" +La base de datos no esta levantada. Corre `docker compose up -d db` y espera a que diga `healthy`. + +### "Next.js build error" o errores de TypeScript +Borra la cache y reinstala: + +```powershell +Remove-Item -Recurse -Force apps\web\.next +bun install +bun dev:web +``` + +### Los estilos se ven mal o no cargan +Reinicia el servidor: `Ctrl+C` y `bun dev:web` de nuevo. + +--- + +Si llegaste hasta aca y la app corre en `http://localhost:3000`, estas listo para trabajar. Bienvenido al equipo!