#
.SYNOPSIS
Instala la API (que también sirve la web) como servicio de Windows usando WinSW.
.EXAMPLE
.\instalar-servicio.ps1 -WinSW C:\descargas\WinSW-x64.exe
.EXAMPLE
.\instalar-servicio.ps1 -WinSW C:\descargas\WinSW-x64.exe -Cuenta 'EMPRESA\svc-devolucion' -UsarCertificadosDelSistema
#>
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
# Ruta del ejecutable de WinSW (WinSW-x64.exe, de https://github.com/winsw/winsw/releases)
[Parameter(Mandatory)][string]$WinSW,
# Cuenta con la que corre el servicio (DOMINIO\usuario). Si se omite: LocalSystem.
[string]$Cuenta,
[string]$NombreServicio = 'DevolucionProveedores',
# Agrega NODE_OPTIONS=--use-system-ca: necesario si Fortinet u otro firewall inspecciona el tráfico
# HTTPS de salida (por ejemplo hacia el web service de BES) con un certificado propio.
[switch]$UsarCertificadosDelSistema
)
$ErrorActionPreference = 'Stop'
$raiz = $PSScriptRoot
$apiDir = Join-Path $raiz 'app\apps\api'
function Fallar($mensaje) { Write-Host "ERROR: $mensaje" -ForegroundColor Red; exit 1 }
# --- Verificaciones previas -------------------------------------------------------------------
if (-not (Test-Path $WinSW)) { Fallar "No se encontró WinSW en '$WinSW'." }
$node = Get-Command node -ErrorAction SilentlyContinue
if (-not $node) { Fallar 'Node.js no está instalado (o no está en el PATH). Instale Node 22 LTS.' }
$versionNode = [version](& $node.Source --version).TrimStart('v')
if ($versionNode.Major -lt 22) { Fallar "Se necesita Node 22 o superior; hay $versionNode." }
if (-not (Test-Path (Join-Path $apiDir 'dist\main.js'))) { Fallar "Falta $apiDir\dist\main.js (¿se copió la carpeta app completa?)." }
if (-not (Test-Path (Join-Path $apiDir 'node_modules')) -and -not (Test-Path (Join-Path $raiz 'app\node_modules'))) { Fallar 'Faltan las dependencias (app\node_modules).' }
if (-not (Test-Path (Join-Path $raiz 'app\apps\web\dist\web\browser\index.html'))) { Fallar 'Falta la web compilada (app\apps\web\dist\web\browser).' }
if (-not (Test-Path (Join-Path $apiDir '.env'))) { Fallar "Falta $apiDir\.env. Copie .env.example a .env y complételo (ver DEPLOY.md)." }
if (Get-Service -Name $NombreServicio -ErrorAction SilentlyContinue) {
Fallar "El servicio '$NombreServicio' ya existe. Ejecute primero .\desinstalar-servicio.ps1."
}
# --- Archivos del servicio ----------------------------------------------------------------------
$servicioDir = Join-Path $raiz 'servicio'
New-Item -ItemType Directory -Force -Path $servicioDir, (Join-Path $raiz 'logs') | Out-Null
$exe = Join-Path $servicioDir 'devolucion-api.exe'
$xml = Join-Path $servicioDir 'devolucion-api.xml'
Copy-Item -Path $WinSW -Destination $exe -Force
$nodeOptions = if ($UsarCertificadosDelSistema) { ' ' } else { '' }
# .Replace() sustituye texto literal (las rutas pueden tener espacios, puntos o signos que -replace trataría como patrón)
$contenido = (Get-Content (Join-Path $raiz 'devolucion-api.xml.plantilla') -Raw -Encoding UTF8).
Replace('__ID__', $NombreServicio).
Replace('__NODE__', $node.Source).
Replace('__RAIZ__', $raiz).
Replace('__NODE_OPTIONS__', $nodeOptions)
if ($contenido -match '__[A-Z_]+__') { Fallar "La plantilla tiene un marcador sin reemplazar: $($Matches[0])" }
# UTF-8 sin BOM
[System.IO.File]::WriteAllText($xml, $contenido, (New-Object System.Text.UTF8Encoding($false)))
# --- Instalación --------------------------------------------------------------------------------
Write-Host "Instalando el servicio '$NombreServicio'..."
& $exe install
if ($LASTEXITCODE -ne 0) { Fallar 'WinSW no pudo instalar el servicio.' }
if ($Cuenta) {
# La contraseña se pide aquí y se pasa a Windows; no queda escrita en ningún archivo.
$credencial = Get-Credential -UserName $Cuenta -Message "Contraseña de la cuenta del servicio"
$clave = $credencial.GetNetworkCredential().Password
& sc.exe config $NombreServicio obj= $credencial.UserName password= $clave | Out-Null
if ($LASTEXITCODE -ne 0) { Fallar "No se pudo asignar la cuenta '$Cuenta' al servicio." }
Write-Host "Cuenta del servicio: $($credencial.UserName). Recuerde darle 'Iniciar sesión como servicio' y permiso de lectura sobre $raiz."
}
Write-Host 'Iniciando...'
& $exe start
Start-Sleep -Seconds 3
& $exe status
Write-Host "`nLogs: $(Join-Path $raiz 'logs')" -ForegroundColor Green
Write-Host 'Compruebe: Invoke-WebRequest /api/health' -ForegroundColor Green