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>
87 lines
4.4 KiB
PowerShell
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
|