Files
devolucion_proveedores/deploy/windows/instalar-servicio.ps1
T
georgeandClaude Sonnet 5.5 287b187c14 Aplicación web de Devolución a Proveedores para SAP Business One
Reemplaza al addon .NET Framework (SolucionLibros). Genera Devoluciones de mercancías
enlazadas a sus Entradas por compra a partir de un listado pegado desde Excel.

- libs/shared (@dp/shared): modelos y lógica de dominio pura (FIFO, consolidación por
  proveedor, parseo del portapapeles), portada del addon.
- apps/api (NestJS): cliente del Service Layer con cola por sesión, login con la sesión de
  SAP, metadatos (UDT/UDF/UDO) y botón Reconstruir, catálogos, planificaciones (pegar,
  procesar, reprocesar, marcar líneas, crear Devoluciones, cerrar), listado paginado con
  filtros y descarga de PDF de la Guía de despacho desde BES. Sirve también la web
  compilada, con HTTPS opcional.
- apps/web (Angular): login, Configuración → Metadatos y Planificaciones (listado,
  nueva, detalle).
- deploy/windows y scripts/publicar.mjs: despliegue como servicio de Windows con WinSW.

Verificado contra el Service Layer de SQL Server. Sin probar aún: SAP HANA y la
instalación del servicio en Windows (ver CLAUDE.md, sección "Sin verificar").

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 08:09:07 -05:00

87 lines
4.4 KiB
PowerShell

<#
.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) { ' <env name="NODE_OPTIONS" value="--use-system-ca"/>' } 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 <su-url>/api/health' -ForegroundColor Green