alexp97 commited on
Commit
4eccaa5
·
1 Parent(s): 26f1bf8

feat(deploy): deploy_prod lee SHINYAPPS_* del .env, soporta -New y fija --title

Browse files
Files changed (2) hide show
  1. docs/ejecucion.md +47 -31
  2. scripts/deploy_prod.ps1 +54 -21
docs/ejecucion.md CHANGED
@@ -174,36 +174,47 @@ docker compose up --build # gateway en :8000 (sirve API + UI Astro compilada)
174
 
175
  ### 5.2 Modelo de despliegue (Agro-Stack)
176
 
177
- Un solo servicio: el **gateway FastAPI** (`backend.main:app`) sirve la **UI Astro compilada** en `/`, la **API** en `/api` y (opcional) el Shiny legacy en `/shiny`. Se despliega a **ShinyApps.io** con `rsconnect` (como app ASGI), aplicando la **Regla de Oro** (JS inline + rutas relativas vía `scripts/inline_js.py`). Todo esto lo orquesta `deploy_prod.ps1`; no hay pasos manuales más allá del registro del token.
178
 
179
- Registrar el token (**una sola vez**, acción manual previa al primer deploy):
180
- ```powershell
181
- uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
 
 
182
  ```
183
- > El TOKEN/SECRET se obtienen en ShinyApps.io → *Account → Tokens*. Quedan en la config local de `rsconnect` (no en el repo).
 
 
184
 
185
  ### 5.3 Ejecución del Despliegue (automatizada con `deploy_prod.ps1`)
186
 
187
- El script ejecuta el pipeline completo de extremo a extremo. **Tú solo corres un comando:**
188
 
189
  ```powershell
190
- # Compilar la UI:
191
- .\scripts\build.ps1
192
- # Primer deploy (crea la app en ShinyApps.io y te devuelve su app-id):
193
- .\scripts\deploy_prod.ps1 -Name <cuenta>
194
- # Redeploy (cuando ya tengas el app-id del primer deploy):
195
- .\scripts\deploy_prod.ps1 -Name <cuenta> -AppId <id>
 
 
 
196
  ```
197
 
 
 
198
  Internamente `deploy_prod.ps1` hace, sin intervención:
199
- 1. **(0)** Compila la UI con `build.ps1` (`pnpm build` → `inline_js.py` → `backend/static`).
200
  2. **(1)** Genera `requirements.txt` (`uv export --no-dev --no-hashes --no-emit-project`) — ShinyApps instala desde ahí, no usa `uv`.
201
  3. **(2)** Traduce cada línea de `.rscignore` a flags `--exclude` (rsconnect-python no lee `.rscignore` solo).
202
- 4. **(3)** `rsconnect deploy fastapi . --entrypoint backend.main:app --name <cuenta> [--app-id <id>]` *(es `deploy fastapi`, no `deploy shiny`: el gateway es una app FastAPI/ASGI)*.
 
 
203
 
204
- > Parámetros (`-Name`, `-AppId`) y el detalle paso a paso del script están en **§8.5**.
205
- > **BYOK / seguridad:** el `.env` **NO** viaja en el bundle (está en `.rscignore`); en producción las llaves las pone el usuario por sesión (cabeceras `X-User-*`).
206
- > **Estado del app-id:** AgroVisión **aún no se ha publicado**, así que todavía no hay `app-id`. El primer deploy con `-Name` lo crea; a partir de ahí, redespliega siempre con `-AppId <id>`.
207
  > **Alternativa:** backend en **Render** con `backend/Dockerfile` (etapa Node compila Astro y el gateway sirve `/`).
208
 
209
  ### 5.4 Verificación Post-Despliegue
@@ -283,7 +294,7 @@ Todos los `.ps1` viven en `scripts/`, se ejecutan **desde la raíz del repo** (r
283
  | `run_ui.ps1` | UI **Shiny legacy** (`:8001`) | `.\scripts\run_ui.ps1` |
284
  | `dev.ps1` | Levanta **backend + Astro dev** en 2 ventanas | `.\scripts\dev.ps1` |
285
  | `build.ps1` | Compila la UI Astro → `backend/static` | `.\scripts\build.ps1` |
286
- | `deploy_prod.ps1` | Despliega a ShinyApps.io (Agro-Stack) | `.\scripts\deploy_prod.ps1 -Name <cuenta> [-AppId <id>]` |
287
  | `inline_js.py` | Post-proceso del HTML (lo invoca `build.ps1`) | *(automático; ver abajo)* |
288
  | `make_sample_orthomosaic.py` | Genera ortomosaico mock para el conteo | `uv run python scripts/make_sample_orthomosaic.py` |
289
 
@@ -359,33 +370,38 @@ Levanta la UI **Shiny** antigua (`backend.dashboard:app`) en `:8001`. Desde la F
359
 
360
  ### 8.5 `deploy_prod.ps1` — despliegue a ShinyApps.io (Agro-Stack)
361
 
362
- Despliega el repo como una app ASGI cuyo entrypoint es el **gateway** (`backend.main:app`). Pasos:
363
 
364
- 0. **`build.ps1`** (compila la UI a `backend/static`).
365
  1. **`uv export --no-dev --no-hashes --no-emit-project -o requirements.txt`** (ShinyApps instala desde `requirements.txt`, no usa `uv`).
366
  2. Traduce cada línea de **`.rscignore`** a flags `--exclude` (rsconnect-python no lee `.rscignore` solo).
367
- 3. **`rsconnect deploy fastapi . --entrypoint backend.main:app --name <cuenta> [--app-id <id>] @exclude`** — subcomando **`fastapi`** (no `shiny`): el entrypoint es una app FastAPI/ASGI, y `rsconnect` soporta FastAPI en shinyapps.io.
 
 
368
 
369
  **Parámetros:**
370
 
371
  | Parámetro | Obligatorio | Descripción |
372
  |-----------|:-----------:|-------------|
373
- | `-Name <cuenta>` | **Sí** | Tu cuenta/destino de ShinyApps.io. Sin él (queda el placeholder), el script **avisa y sale** sin desplegar. |
374
- | `-AppId <id>` | No | Vacío = **primer deploy** (crea la app). Una vez creada, reutiliza el id para **redeploy**. |
 
 
 
375
 
376
  ```powershell
377
- # 0) (Una sola vez) registrar el token de ShinyApps:
378
- uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
379
 
380
- # 1) Primer deploy (crea la app y devuelve su app-id):
381
- .\scripts\deploy_prod.ps1 -Name <cuenta>
382
 
383
- # 2) Redeploy (cuando ya tengas el app-id):
384
- .\scripts\deploy_prod.ps1 -Name <cuenta> -AppId <id>
385
  ```
386
 
387
- > **BYOK / seguridad:** `.env` está en `.rscignore`, así que **no viaja** en el bundle; en producción las llaves las pone el usuario por sesión (cabeceras `X-User-*`). Lo que **sí** va: `backend/` (incl. `backend/static`), `pyproject.toml`, `uv.lock`, `requirements.txt` y `.env.example`.
388
- > **Aún no hay app-id** para AgroVisión: el primer deploy con `-Name` lo crea; guárdalo para los redeploys con `-AppId`.
389
 
390
  ### 8.6 `inline_js.py` — post-proceso del HTML (auxiliar)
391
 
 
174
 
175
  ### 5.2 Modelo de despliegue (Agro-Stack)
176
 
177
+ Un solo servicio: el **gateway FastAPI** (`backend.main:app`) sirve la **UI Astro compilada** en `/`, la **API** en `/api` y (opcional) el Shiny legacy en `/shiny`. Se despliega a **ShinyApps.io** con `rsconnect deploy fastapi` (app ASGI), aplicando la **Regla de Oro** (JS inline + rutas relativas vía `scripts/inline_js.py`). Todo lo orquesta `deploy_prod.ps1`.
178
 
179
+ **Credenciales: directamente desde el `.env`** (sin paso manual). Pon en tu `.env`:
180
+ ```bash
181
+ SHINYAPPS_ACCOUNT=<tu_cuenta>
182
+ SHINYAPPS_TOKEN=<token>
183
+ SHINYAPPS_SECRET=<secret>
184
  ```
185
+ > El TOKEN/SECRET se obtienen en ShinyApps.io → *Account → Tokens*. `deploy_prod.ps1` los **lee del `.env`** y los exporta al entorno; `rsconnect` toma `--account/--token/--secret` de esas mismas variables. **No necesitas `rsconnect add`.** *(Alternativa equivalente, registrar el servidor una vez: `uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>`.)*
186
+ >
187
+ > **shinyapps.io no soporta variables de entorno de runtime**, por eso el modelo es **BYOK**: no se ponen secretos de datos en el server; cada usuario pega sus llaves por sesión.
188
 
189
  ### 5.3 Ejecución del Despliegue (automatizada con `deploy_prod.ps1`)
190
 
191
+ El script ejecuta el pipeline completo de extremo a extremo (compila, empaqueta y despliega). **Tú solo corres un comando** — con las `SHINYAPPS_*` en el `.env` ni siquiera pasas la cuenta:
192
 
193
  ```powershell
194
+ # Primer deploy (toma la cuenta del .env; crea la app):
195
+ .\scripts\deploy_prod.ps1
196
+ # (equivalente, cuenta explícita): .\scripts\deploy_prod.ps1 -Name <cuenta>
197
+
198
+ # Redeploy de una app existente (la actualiza):
199
+ .\scripts\deploy_prod.ps1 -AppId <id>
200
+
201
+ # Forzar una app NUEVA aparte (aunque exista metadata de un deploy previo):
202
+ .\scripts\deploy_prod.ps1 -New
203
  ```
204
 
205
+ > **No hace falta** correr a mano nada como `rsconnect deploy ... --entrypoint ... --new`: el script ya lo hace por ti, con `--title "AgroVisión"` y el manejo de nuevo/actualizar.
206
+
207
  Internamente `deploy_prod.ps1` hace, sin intervención:
208
+ 1. **(0)** Lee `SHINYAPPS_*` del `.env` y compila la UI con `build.ps1` (`pnpm build` → `inline_js.py` → `backend/static`).
209
  2. **(1)** Genera `requirements.txt` (`uv export --no-dev --no-hashes --no-emit-project`) — ShinyApps instala desde ahí, no usa `uv`.
210
  3. **(2)** Traduce cada línea de `.rscignore` a flags `--exclude` (rsconnect-python no lee `.rscignore` solo).
211
+ 4. **(3)** `rsconnect deploy fastapi . --entrypoint backend.main:app --title "AgroVisión" --account <cuenta> [--app-id <id> | --new] @exclude` *(es `deploy fastapi`, no `deploy shiny`: el gateway es FastAPI/ASGI)*.
212
+
213
+ **Nuevo vs. actualizar** (semántica de `rsconnect`): por defecto reutiliza la *metadata* del deploy anterior (actualiza la misma app si existe, o la crea si no). `-AppId <id>` actualiza una app concreta por su id; `-New` fuerza una **app nueva** (no combinar con `-AppId`).
214
 
215
+ > Parámetros (`-Name`, `-AppId`, `-New`) y el detalle paso a paso están en **§8.5**.
216
+ > **BYOK / seguridad:** el `.env` **NO** viaja en el bundle (está en `.rscignore`); en producción las llaves de datos las pone el usuario por sesión (cabeceras `X-User-*`). Las `SHINYAPPS_*` solo se usan **en tu máquina** para autenticar el deploy.
217
+ > **Estado del app-id:** AgroVisión **aún no se ha publicado**; el primer deploy crea la app y ShinyApps te muestra su `app-id`. A partir de ahí redespliega con `-AppId <id>` (o sin nada, reutilizando la metadata local).
218
  > **Alternativa:** backend en **Render** con `backend/Dockerfile` (etapa Node compila Astro y el gateway sirve `/`).
219
 
220
  ### 5.4 Verificación Post-Despliegue
 
294
  | `run_ui.ps1` | UI **Shiny legacy** (`:8001`) | `.\scripts\run_ui.ps1` |
295
  | `dev.ps1` | Levanta **backend + Astro dev** en 2 ventanas | `.\scripts\dev.ps1` |
296
  | `build.ps1` | Compila la UI Astro → `backend/static` | `.\scripts\build.ps1` |
297
+ | `deploy_prod.ps1` | Despliega a ShinyApps.io (Agro-Stack) | `.\scripts\deploy_prod.ps1` (cuenta/token del `.env`; `-AppId`/`-New` opcionales) |
298
  | `inline_js.py` | Post-proceso del HTML (lo invoca `build.ps1`) | *(automático; ver abajo)* |
299
  | `make_sample_orthomosaic.py` | Genera ortomosaico mock para el conteo | `uv run python scripts/make_sample_orthomosaic.py` |
300
 
 
370
 
371
  ### 8.5 `deploy_prod.ps1` — despliegue a ShinyApps.io (Agro-Stack)
372
 
373
+ Despliega el repo como una app FastAPI/ASGI cuyo entrypoint es el **gateway** (`backend.main:app`). Pasos:
374
 
375
+ 0. Lee `SHINYAPPS_*` del `.env` y corre **`build.ps1`** (compila la UI a `backend/static`).
376
  1. **`uv export --no-dev --no-hashes --no-emit-project -o requirements.txt`** (ShinyApps instala desde `requirements.txt`, no usa `uv`).
377
  2. Traduce cada línea de **`.rscignore`** a flags `--exclude` (rsconnect-python no lee `.rscignore` solo).
378
+ 3. **`rsconnect deploy fastapi . --entrypoint backend.main:app --title "AgroVisión" --account <cuenta> [--app-id <id> | --new] @exclude`** — subcomando **`fastapi`** (no `shiny`): el entrypoint es una app FastAPI/ASGI, y `rsconnect` la soporta en shinyapps.io.
379
+
380
+ **Credenciales (desde `.env`):** el script carga `SHINYAPPS_ACCOUNT/TOKEN/SECRET` del `.env` al entorno; `rsconnect` las consume directamente (esas variables alimentan `--account/--token/--secret`). Si **no** están, cae a `--name <cuenta>` asumiendo un `rsconnect add` previo.
381
 
382
  **Parámetros:**
383
 
384
  | Parámetro | Obligatorio | Descripción |
385
  |-----------|:-----------:|-------------|
386
+ | `-Name <cuenta>` | No | Cuenta de ShinyApps.io. Si se omite, se toma de `SHINYAPPS_ACCOUNT` (`.env`). Sin cuenta por ningún lado, el script **avisa y sale**. |
387
+ | `-AppId <id>` | No | Actualiza (**reemplaza**) una app existente por su id. Mutuamente excluyente con `-New`. |
388
+ | `-New` | No | Fuerza una **app nueva** aunque exista metadata de un deploy previo. Útil si quieres una copia aparte. |
389
+
390
+ Sin `-AppId` ni `-New`, `rsconnect` **reutiliza la metadata** del último deploy (actualiza la misma app, o la crea si es la primera vez).
391
 
392
  ```powershell
393
+ # Primer deploy (cuenta y token salen del .env):
394
+ .\scripts\deploy_prod.ps1
395
 
396
+ # Redeploy de una app concreta:
397
+ .\scripts\deploy_prod.ps1 -AppId <id>
398
 
399
+ # Forzar una app nueva aparte:
400
+ .\scripts\deploy_prod.ps1 -New
401
  ```
402
 
403
+ > **BYOK / seguridad:** `.env` está en `.rscignore`, así que **no viaja** en el bundle; en producción las llaves de datos las pone el usuario por sesión (cabeceras `X-User-*`). Las `SHINYAPPS_*` solo autentican el deploy **en tu máquina**. Lo que **sí** va en el bundle: `backend/` (incl. `backend/static`), `pyproject.toml`, `uv.lock`, `requirements.txt` y `.env.example`.
404
+ > **Aún no hay app-id** para AgroVisión: el primer deploy lo crea; guárdalo para los redeploys con `-AppId`.
405
 
406
  ### 8.6 `inline_js.py` — post-proceso del HTML (auxiliar)
407
 
scripts/deploy_prod.ps1 CHANGED
@@ -1,20 +1,28 @@
1
  # scripts/deploy_prod.ps1 — Despliegue de AgroVisión a ShinyApps.io (Agro-Stack).
2
  #
3
  # El entrypoint es el gateway FastAPI (backend.main:app), que sirve la UI Astro
4
- # compilada en / y la API en /api. El bundle es el repo (`.`) MENOS las exclusiones
5
- # de `.rscignore` (rsconnect-python no lee .rscignore solo: aquí se traducen a --exclude).
 
6
  #
7
- # BYOK: el `.env` (secretos) NO viaja en el bundle (está en .rscignore); en producción
8
- # las credenciales las pone el usuario por sesión (cabeceras X-User-*).
9
- #
10
- # Aún no hay app-id para esta app: pásalo cuando exista para hacer redeploy.
11
- # Primer deploy: .\scripts\deploy_prod.ps1 -Name <tu_cuenta>
12
- # Redeploy: .\scripts\deploy_prod.ps1 -Name <tu_cuenta> -AppId <id>
13
- # Registrar token (una vez):
14
  # uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
 
 
 
 
 
 
 
 
 
15
  param(
16
- [string]$Name = "<TU_CUENTA_SHINYAPPS>",
17
- [string]$AppId = "" # vacío = primer deploy (crea la app); luego usa el id para redeploy
 
18
  )
19
  $ErrorActionPreference = "Stop"
20
  $root = Split-Path -Parent $PSScriptRoot
@@ -23,6 +31,18 @@ $env:UV_PROJECT_ENVIRONMENT = "$env:LOCALAPPDATA\agrovision-venv"
23
 
24
  Write-Host "=== Deploy AgroVisión -> ShinyApps.io ===" -ForegroundColor Cyan
25
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  # 0) Compilar la UI (Astro -> backend/static)
27
  Write-Host "`n[0/3] Compilando la UI..." -ForegroundColor Yellow
28
  & "$PSScriptRoot\build.ps1"
@@ -46,20 +66,33 @@ $excludeArgs = @()
46
  foreach ($p in $patterns) { $excludeArgs += "--exclude"; $excludeArgs += $p }
47
  Write-Host ("[2/3] {0} patrones de exclusión cargados desde .rscignore" -f $patterns.Count)
48
 
49
- if ($Name -eq "<TU_CUENTA_SHINYAPPS>") {
50
- Write-Warning "Falta tu cuenta de ShinyApps. Uso: .\scripts\deploy_prod.ps1 -Name <cuenta> [-AppId <id>]"
51
- Write-Warning "Y registra el token primero (ver cabecera de este script)."
 
52
  exit 1
53
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- # 3) Desplegar como app FastAPI (ASGI). OJO: NO es 'deploy shiny' (eso espera una app
56
- # Shiny); el gateway es FastAPI, así que el subcomando correcto es 'deploy fastapi'
57
- # (rsconnect lo soporta para shinyapps.io). El entrypoint es el gateway que sirve
58
- # la UI Astro en / y la API en /api.
59
  Write-Host "`n[3/3] Desplegando (deploy fastapi, entrypoint backend.main:app)..." -ForegroundColor Yellow
60
- $appIdArgs = @()
61
- if ($AppId) { $appIdArgs = @("--app-id", $AppId) }
62
  & uv run python -c "from rsconnect.main import cli; cli()" deploy fastapi . `
63
- --entrypoint backend.main:app --name $Name @appIdArgs @excludeArgs
64
 
65
  Write-Host "`n=== Despliegue completado ===" -ForegroundColor Green
 
1
  # scripts/deploy_prod.ps1 — Despliegue de AgroVisión a ShinyApps.io (Agro-Stack).
2
  #
3
  # El entrypoint es el gateway FastAPI (backend.main:app), que sirve la UI Astro
4
+ # compilada en / y la API en /api. Se publica con `rsconnect deploy fastapi` (NO
5
+ # `deploy shiny`: el gateway es FastAPI/ASGI). El bundle es el repo (`.`) MENOS las
6
+ # exclusiones de `.rscignore` (rsconnect-python no lee .rscignore solo: se traducen a --exclude).
7
  #
8
+ # CREDENCIALES (sin paso manual): el script lee `SHINYAPPS_ACCOUNT/TOKEN/SECRET` del
9
+ # `.env` y las exporta al entorno; `rsconnect` las toma automáticamente (--account/--token/
10
+ # --secret son settables por esas mismas variables). Así NO necesitas `rsconnect add`.
11
+ # Alternativa: registrar el servidor una vez y desplegar por nombre:
 
 
 
12
  # uv run rsconnect add --account <cuenta> --name <cuenta> --token <TOKEN> --secret <SECRET>
13
+ #
14
+ # BYOK: el `.env` NO viaja en el bundle (está en .rscignore); en producción las llaves de
15
+ # datos las pone el usuario por sesión (cabeceras X-User-*). Nota: shinyapps.io NO soporta
16
+ # gestión de variables de entorno de runtime, por eso el modelo es BYOK (sin secretos en el server).
17
+ #
18
+ # Primer deploy: .\scripts\deploy_prod.ps1 (toma la cuenta del .env)
19
+ # .\scripts\deploy_prod.ps1 -Name <cuenta> (o explícita)
20
+ # Redeploy: .\scripts\deploy_prod.ps1 -AppId <id> (actualiza esa app)
21
+ # Forzar nueva: .\scripts\deploy_prod.ps1 -New (crea otra app aparte)
22
  param(
23
+ [string]$Name = "", # cuenta de ShinyApps; si se omite, se toma de SHINYAPPS_ACCOUNT (.env)
24
+ [string]$AppId = "", # id de una app existente para REEMPLAZAR (redeploy)
25
+ [switch]$New # fuerza una app NUEVA aunque haya metadata de un deploy previo
26
  )
27
  $ErrorActionPreference = "Stop"
28
  $root = Split-Path -Parent $PSScriptRoot
 
31
 
32
  Write-Host "=== Deploy AgroVisión -> ShinyApps.io ===" -ForegroundColor Cyan
33
 
34
+ # Cargar SHINYAPPS_* del .env al entorno (sin pisar lo que ya exista en la sesión).
35
+ # rsconnect lee --account/--token/--secret desde estas mismas variables.
36
+ if (Test-Path ".env") {
37
+ Get-Content ".env" | ForEach-Object {
38
+ if ($_ -match '^\s*(SHINYAPPS_ACCOUNT|SHINYAPPS_TOKEN|SHINYAPPS_SECRET)\s*=\s*(.+)$') {
39
+ $k = $matches[1]; $v = $matches[2].Trim().Trim('"').Trim("'")
40
+ if ($v -and -not [Environment]::GetEnvironmentVariable($k)) { Set-Item "env:$k" $v }
41
+ }
42
+ }
43
+ }
44
+ if (-not $Name) { $Name = $env:SHINYAPPS_ACCOUNT }
45
+
46
  # 0) Compilar la UI (Astro -> backend/static)
47
  Write-Host "`n[0/3] Compilando la UI..." -ForegroundColor Yellow
48
  & "$PSScriptRoot\build.ps1"
 
66
  foreach ($p in $patterns) { $excludeArgs += "--exclude"; $excludeArgs += $p }
67
  Write-Host ("[2/3] {0} patrones de exclusión cargados desde .rscignore" -f $patterns.Count)
68
 
69
+ # Validación de cuenta
70
+ if (-not $Name) {
71
+ Write-Warning "Falta la cuenta de ShinyApps. Pon SHINYAPPS_ACCOUNT en .env o pasa -Name <cuenta>."
72
+ Write-Warning "Y SHINYAPPS_TOKEN/SHINYAPPS_SECRET en .env (o registra antes con 'rsconnect add')."
73
  exit 1
74
  }
75
+ if ($AppId -and $New) { Write-Error "-AppId y -New son mutuamente excluyentes."; exit 1 }
76
+
77
+ # Autenticación: si hay token+secret (del .env), rsconnect los toma del entorno y solo
78
+ # necesita la cuenta. Si no, se asume un servidor ya registrado con 'rsconnect add'.
79
+ if ($env:SHINYAPPS_TOKEN -and $env:SHINYAPPS_SECRET) {
80
+ $authArgs = @("--account", $Name) # token/secret vienen de $env:SHINYAPPS_TOKEN/SECRET
81
+ Write-Host "Auth: credenciales de ShinyApps leídas del .env (cuenta '$Name')."
82
+ } else {
83
+ $authArgs = @("--name", $Name)
84
+ Write-Host "Auth: usando servidor registrado '$Name' (rsconnect add previo)."
85
+ }
86
+
87
+ # Destino: -AppId reemplaza esa app; -New fuerza una nueva; por defecto rsconnect
88
+ # reutiliza la metadata del deploy anterior (actualiza si existe, crea si no).
89
+ $targetArgs = @()
90
+ if ($AppId) { $targetArgs = @("--app-id", $AppId) }
91
+ elseif ($New) { $targetArgs = @("--new") }
92
 
93
+ # 3) Desplegar como app FastAPI (ASGI). El entrypoint es el gateway (UI Astro en / + /api).
 
 
 
94
  Write-Host "`n[3/3] Desplegando (deploy fastapi, entrypoint backend.main:app)..." -ForegroundColor Yellow
 
 
95
  & uv run python -c "from rsconnect.main import cli; cli()" deploy fastapi . `
96
+ --entrypoint backend.main:app --title "AgroVisión" @authArgs @targetArgs @excludeArgs
97
 
98
  Write-Host "`n=== Despliegue completado ===" -ForegroundColor Green