versión 1.2.0: despliegue como servicio de Windows con NSSM

- deploy/: scripts de PowerShell para instalar el servicio (cuenta de servicio
  con acceso a la ruta de red, reinicio automático, parada con Ctrl+C,
  rotación de la salida, PYTHONUTF8), actualizar a una versión etiquetada o
  volver a una anterior, y desinstalar.
- docs/despliegue_windows.md: requisitos, instalación, operación, rollback y
  problemas frecuentes.
- tools/verificar_conexiones.py: comprueba .env, Graph, SAP, HANA y la ruta de
  red sin modificar nada.
- CHANGELOG.md, README e inventario; pyproject.toml pasa a 1.2.0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-22 12:08:38 -05:00
1 parent f01f21b1c3
commit cdcecc2b56
9 files changed
+416 -15

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
# Historial de cambios
Las versiones siguen [SemVer](https://semver.org/lang/es/) y se publican como etiquetas de git (`v1.2.0`). La versión vigente está en `pyproject.toml` y se muestra en el log al arrancar.
## [1.2.0] — 2026-10-22
### Agregado
- **Respuesta al proveedor** con las plantillas HTML de aceptado y rechazado (`plantilla/`), enviada a quien envió el correo. Ver `docs/respuesta_proveedor.md`.
- `NOTIFICACION_MODO`: `prueba` (por defecto; la respuesta va al equipo, no al proveedor) o `real` (al remitente, con copia oculta al equipo).
- Protecciones: no responde a remitentes desconocidos o sin dominio válido, al propio buzón, a `no-reply`, a mensajes automáticos ni rebotes, a respuestas de nuestras notificaciones, y limita a 3 respuestas por remitente cada 24 h en correos sin comprobante.
- Rechazo con observaciones estructuradas (título, detalle, cómo corregir) también para correos sin comprobante válido.
- **Se valida si la factura ya existe en SAP** (factura de compra no anulada o draft) antes de crear el draft; si existe, no se duplica y se responde que ya está registrada.
- **Servicio fuera de línea:** si SAP Service Layer o HANA no responden, se avisa una vez al proveedor ("en cola, no necesita reenviar") y el correo se reintenta en los ciclos siguientes, hasta 24 veces. Plantilla `plantilla-fuera-de-linea.html`.
- **Código de seguimiento** (`ABR-AAMMDD-NNNNN`) por comprobante, con su resultado y el estado de la notificación (`seguimiento.py`, tabla `comprobantes`).
- **Despliegue en Windows como servicio con NSSM** (`deploy/`, `docs/despliegue_windows.md`).
- Validación del `.env` al arrancar (`config_validator.py`), que además anuncia el modo de notificación.
- Versión del proyecto en el log al arrancar y `tools/verificar_conexiones.py` (comprueba el `.env`, Microsoft Graph, SAP, HANA y la ruta de red sin modificar nada).
- Suite de pruebas pytest (más de 280) y herramientas de vista previa y envío de prueba de las plantillas (`tools/previsualizar_plantillas.py`).
- Documentación: `README.md`, guía de MSAL (`docs/configurar_msal.md`) y una página por módulo principal.
### Cambiado
- El correo técnico (tablas de SAP y JSON adjuntos) va **solo al equipo**; antes iba también al remitente.
- Un error de SAP/HANA o un fallo inesperado ya no se le comunica al proveedor como rechazo y ya no interrumpe el ciclo: solo esa factura.
- "Guía no encontrada" exige que HANA responda: si no, se trata como servicio fuera de línea.
- `DueDate` de las cuotas del draft = fecha de vencimiento (antes, fecha de emisión).
- **Horarios sin huecos:** se agregan 21:30–24:00 (cada 60 min) y 05:00–06:30 (cada 30 min); antes el demonio dormía hasta las 06:30 en esos tramos.
- La base `emails.db` y la carpeta de resultados se resuelven junto al código, no relativas al directorio de trabajo.
- `copiar_a_red` ya no crea carpetas locales cuando `RUTA_RED_FACTURACION` no es una ruta absoluta en el sistema operativo (una ruta UNC fuera de Windows); antes el draft podía referenciar archivos que no estaban en el recurso compartido.
- `pipeline.py` se divide en módulos (`adjuntos`, `documentos`, `persistencia`, `draft_sap`, `notificaciones`).
- Los `print` de los módulos pasan a `logging` con nivel explícito (`LOG_LEVEL` filtra por severidad).
- `pyproject.toml` y `requirements.txt` alineados, con las dependencias directas fijadas.
- `consulta_rapida.py` y los scripts manuales pasan a `tools/`; los correos de ejemplo, a `tests/fixtures/`.
### Eliminado
- `tools/documento.json` (token de ngrok sin uso). **El token sigue en el historial de git: revóquelo.**
### Seguridad
- Pendiente de rotar: la contraseña de SAP y el token de ngrok, presentes en el historial de git (`tools/aberio.http`, `tools/documento.json`).
## [1.1.0]
Versión anterior a este registro: descarga de adjuntos, extracción de XML y PDF, validación de precios contra SAP B1, creación del draft y notificación por correo.
+10 -1
View File
@@ -8,8 +8,10 @@ Demonio que descarga las facturas de transporte (PDF/XML SUNAT) recibidas en un
Bandeja de entrada (Microsoft Graph)
→ descarga adjuntos, descomprime ZIP, se queda con PDF/XML
→ extrae datos de PDF y XML y asigna las guías de remisión por línea
→ comprueba que la factura no exista ya en SAP
→ valida precios de cada guía contra SAP (Service Layer, con fallback a HANA)
→ crea el Draft en SAP B1 si todos los precios coinciden
→ si SAP no responde, avisa "en cola" y reintenta en los ciclos siguientes
→ responde a quien envió el correo con la plantilla de aceptado o rechazado
(por defecto en modo prueba: la respuesta va al equipo; ver docs/respuesta_proveedor.md)
```
@@ -38,10 +40,15 @@ El `.env` nunca se sube a git. Todas las variables están documentadas en [`.env
python main.py # demonio; consulta los últimos 2 días (--dias 2)
python main.py --dias 5 # ventana de búsqueda de 5 días
python tools/consulta_rapida.py --set <nombre> # reprocesar correos por ID (sets en tools/ids_prueba.json)
python tools/verificar_conexiones.py # comprueba .env, Graph, SAP, HANA y la ruta de red
```
El demonio trabaja por bloques horarios (cada 1, 5, 30 o 60 minutos según la hora) y duerme fuera de ellos. Los bloques están en `HORARIOS` de `main.py` y se explican en [`docs/main.md`](docs/main.md).
## Despliegue
En producción corre como **servicio de Windows con NSSM**: arranca con el servidor y se reinicia solo. La carpeta [`deploy/`](deploy/) trae los scripts (instalar, actualizar a una versión etiquetada y volver atrás, desinstalar) y [`docs/despliegue_windows.md`](docs/despliegue_windows.md) la guía completa. Antes de instalar, `python tools/verificar_conexiones.py` comprueba el `.env`, Microsoft Graph, SAP, HANA y la ruta de red. Las versiones son etiquetas de git (`v1.2.0`); el historial está en [`CHANGELOG.md`](CHANGELOG.md).
## Pruebas
```bash
@@ -77,7 +84,8 @@ Los scripts de `tools/` (`procesar_pdfs_prueba.py`, `procesar_xmls_prueba.py`, e
| `sunat_schema.py`, `pdf_utilities.py` | Lectura de XML SUNAT y de PDF |
| `database_utilities.py`, `seguimiento.py`, `email_utilities.py` | SQLite local; códigos de seguimiento y resultado por comprobante; ZIP y filtrado de adjuntos |
| `logger_manager.py`, `config_validator.py`, `pipeline_utils.py` | Logging, validación del `.env`, utilidades |
| `tools/` | Scripts manuales: reprocesar correos, regenerar/inspeccionar los correos de prueba, `aberio.http` |
| `deploy/` | Scripts de PowerShell para instalar el servicio de Windows con NSSM |
| `tools/` | Scripts manuales: verificar conexiones, vista previa de las plantillas, reprocesar correos, regenerar los correos de prueba, `aberio.http` |
| `tests/` | Suite pytest, correos de ejemplo (`tests/fixtures/`) y resultados esperados (`tests/golden/`) |
## Documentación
@@ -92,6 +100,7 @@ Los scripts de `tools/` (`procesar_pdfs_prueba.py`, `procesar_xmls_prueba.py`, e
| [`docs/construir_draft.md`](docs/construir_draft.md) | Construcción del Draft SAP B1 |
| [`docs/respuesta_proveedor.md`](docs/respuesta_proveedor.md) | Respuesta de aceptado/rechazado al proveedor: modos, protecciones y observaciones |
| [`docs/seguimiento.md`](docs/seguimiento.md) | Códigos de seguimiento y resultado por comprobante |
| [`docs/despliegue_windows.md`](docs/despliegue_windows.md) | Instalación como servicio de Windows con NSSM, operación y rollback |
| [`docs/configurar_msal.md`](docs/configurar_msal.md) | Cómo obtener `CLIENT_ID`, `TENANT_ID` y `CLIENT_SECRET` en Azure |
| [`docs/outlook_utilities.md`](docs/outlook_utilities.md) | Cliente de Microsoft Graph |
| [`docs/hana_manager.md`](docs/hana_manager.md) | Consultas directas a SAP HANA |
+40
View File
@@ -0,0 +1,40 @@
<#
.SYNOPSIS
Actualiza el proyecto a una versión (etiqueta de git) y reinicia el servicio. Sirve también para volver a una
versión anterior (rollback). Ejecutar en PowerShell como administrador.
.EXAMPLE
.\deploy\actualizar.ps1 -Version v1.2.0
.\deploy\actualizar.ps1 -Version v1.1.0 # volver a la anterior
#>
param(
[Parameter(Mandatory = $true)][string]$Version,
[string]$Nombre = "descarga_factura_transporte",
[string]$Nssm = "nssm"
)
$ErrorActionPreference = "Stop"
$Proyecto = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
Set-Location $Proyecto
if (git status --porcelain --untracked-files=no) {
throw "Hay cambios locales sin guardar en $Proyecto. Revíselos (git status) antes de actualizar."
}
function Comprobar($paso) { if ($LASTEXITCODE -ne 0) { throw "Falló: $paso (código $LASTEXITCODE)" } }
git fetch --tags origin
Comprobar "git fetch"
if (-not (git tag --list $Version)) { throw "No existe la etiqueta $Version. Etiquetas disponibles: $(git tag --list | Out-String)" }
& $Nssm stop $Nombre confirm | Out-Null
try {
git checkout $Version
Comprobar "git checkout $Version"
& (Join-Path $Proyecto "venv\Scripts\python.exe") -m pip install -r requirements.txt
Comprobar "pip install"
} finally {
& $Nssm start $Nombre | Out-Null # arranca siempre: si algo falló, vuelve a levantar lo que había
}
Write-Host "Servicio '$Nombre' en la versión $Version."
Write-Host "Compruebe la versión en el log: Select-String 'versión' logs\descarga_factura_transporte.log | Select-Object -Last 1"
+15
View File
@@ -0,0 +1,15 @@
<#
.SYNOPSIS
Detiene y elimina el servicio de Windows del demonio. No borra el proyecto, el .env ni emails.db.
Ejecutar en PowerShell como administrador.
#>
param(
[string]$Nombre = "descarga_factura_transporte",
[string]$Nssm = "nssm"
)
$ErrorActionPreference = "Stop"
if (-not (Get-Service -Name $Nombre -ErrorAction SilentlyContinue)) { Write-Host "El servicio '$Nombre' no existe."; return }
& $Nssm stop $Nombre confirm | Out-Null
& $Nssm remove $Nombre confirm
Write-Host "Servicio '$Nombre' eliminado. El proyecto, el .env y emails.db no se tocaron."
+99
View File
@@ -0,0 +1,99 @@
<#
.SYNOPSIS
Instala (o reinstala) el demonio descarga_factura_transporte como servicio de Windows con NSSM.
.DESCRIPTION
Ejecutar en PowerShell COMO ADMINISTRADOR, desde cualquier carpeta. Ver docs/despliegue_windows.md.
No arranca el servicio: primero revisa el .env y luego ejecuta nssm start <nombre>.
.PARAMETER Cuenta
Cuenta con la que corre el servicio, en formato DOMINIO\usuario. Debe tener acceso de lectura/escritura a la
ruta de red de RUTA_RED_FACTURACION: la cuenta LocalSystem (por defecto de los servicios) NO ve recursos de red.
.PARAMETER Clave
Contraseña de la cuenta (SecureString). Si se omite, se pide por consola.
.EXAMPLE
.\deploy\instalar_servicio.ps1 -Cuenta "ABERIO\svc_facturas"
#>
param(
[string]$Nombre = "descarga_factura_transporte",
[string]$Cuenta = "",
[securestring]$Clave,
[string]$Nssm = "nssm",
[string]$Python = ""
)
$ErrorActionPreference = "Stop"
# --- Rutas (la carpeta del proyecto es la que contiene deploy\) ---------------------------------------------
$Proyecto = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
if (-not $Python) { $Python = Join-Path $Proyecto "venv\Scripts\python.exe" }
# --- Comprobaciones previas ----------------------------------------------------------------------------------
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Ejecute este script como administrador."
}
if (-not (Get-Command $Nssm -ErrorAction SilentlyContinue)) {
throw "No se encontró nssm. Descárguelo de https://nssm.cc y agréguelo al PATH, o use -Nssm 'C:\ruta\nssm.exe'."
}
if (-not (Test-Path $Python)) {
throw "No existe $Python. Cree el entorno virtual: py -3 -m venv venv ; venv\Scripts\pip install -r requirements.txt"
}
if (-not (Test-Path (Join-Path $Proyecto ".env"))) {
throw "Falta $Proyecto\.env. Copie .env.example a .env y complételo (ver docs/configurar_msal.md)."
}
if (-not $Cuenta) {
Write-Warning "Sin -Cuenta el servicio corre como LocalSystem y NO podrá escribir en la ruta de red de facturación electrónica."
}
# --- Instalación ----------------------------------------------------------------------------------------------
if (Get-Service -Name $Nombre -ErrorAction SilentlyContinue) {
Write-Host "El servicio '$Nombre' ya existe: se detiene y se reconfigura."
& $Nssm stop $Nombre confirm | Out-Null
} else {
& $Nssm install $Nombre $Python "main.py" | Out-Null
}
$LogsNssm = Join-Path $Proyecto "logs"
New-Item -ItemType Directory -Force -Path $LogsNssm | Out-Null
& $Nssm set $Nombre Application $Python
& $Nssm set $Nombre AppParameters "main.py"
& $Nssm set $Nombre AppDirectory $Proyecto # el código no depende del cwd, pero el .env y los logs sí cuentan con él
& $Nssm set $Nombre DisplayName "Descarga de facturas de transporte"
& $Nssm set $Nombre Description "Lee el buzón, valida las facturas contra SAP B1, crea el draft y responde al proveedor."
& $Nssm set $Nombre Start SERVICE_AUTO_START
& $Nssm set $Nombre AppEnvironmentExtra "PYTHONUTF8=1" "PYTHONIOENCODING=utf-8" "PYTHONUNBUFFERED=1" # emojis del log en la consola de Windows
# Salida estándar y de error del proceso (el log de la aplicación sigue en logs\descarga_factura_transporte.log)
& $Nssm set $Nombre AppStdout (Join-Path $LogsNssm "servicio.out.log")
& $Nssm set $Nombre AppStderr (Join-Path $LogsNssm "servicio.err.log")
& $Nssm set $Nombre AppRotateFiles 1
& $Nssm set $Nombre AppRotateOnline 1
& $Nssm set $Nombre AppRotateBytes 10485760 # 10 MB
# Reinicio automático si el proceso muere; espera 30 s para no entrar en un bucle rápido
& $Nssm set $Nombre AppExit Default Restart
& $Nssm set $Nombre AppRestartDelay 30000
& $Nssm set $Nombre AppThrottle 10000
# Parada limpia: Ctrl+C (el demonio lo atiende con KeyboardInterrupt) y 20 s de gracia antes de forzar
& $Nssm set $Nombre AppStopMethodSkip 0
& $Nssm set $Nombre AppStopMethodConsole 20000
& $Nssm set $Nombre AppStopMethodWindow 0
& $Nssm set $Nombre AppStopMethodThreads 0
if ($Cuenta) {
if (-not $Clave) { $Clave = Read-Host "Contraseña de $Cuenta" -AsSecureString }
$Texto = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Clave))
& $Nssm set $Nombre ObjectName $Cuenta $Texto
Remove-Variable Texto
}
Write-Host ""
Write-Host "Servicio '$Nombre' instalado. Pasos siguientes:"
Write-Host " 1. Revise $Proyecto\.env (NOTIFICACION_MODO, NOTIFICACION_EQUIPO, LOGO_URL)."
Write-Host " 2. nssm start $Nombre"
Write-Host " 3. Get-Content '$LogsNssm\descarga_factura_transporte.log' -Wait -Tail 30"
+109
View File
@@ -0,0 +1,109 @@
# Despliegue en Windows (servicio con NSSM)
El demonio (`main.py`) corre como **servicio de Windows** gestionado con [NSSM](https://nssm.cc): arranca con el servidor, se reinicia solo si se cae y se detiene de forma ordenada. La carpeta `deploy/` trae los scripts.
> Los scripts de `deploy/` se revisaron con el analizador de PowerShell (sintaxis correcta) pero **no se ejecutaron en un Windows real**. Pruébelos primero en un servidor de pruebas.
## Requisitos del servidor
| Requisito | Detalle |
|-----------|---------|
| Windows | Server 2016 o superior, o Windows 10/11 |
| Python | 3.11 o superior, **64 bits** (el cliente de SAP HANA, `hdbcli`, lo requiere) |
| Git y NSSM | `git` en el PATH y `nssm.exe` en el PATH (<https://nssm.cc>) |
| Red hacia fuera | `login.microsoftonline.com` y `graph.microsoft.com` (443) |
| Red hacia SAP | Service Layer (`SAP_B1_URL`, puerto 50000 por defecto) y HANA (`HANA_SERVER`, puerto 30015) |
| Ruta de red | Acceso de lectura y escritura a `RUTA_RED_FACTURACION` (recurso UNC) |
| Cuenta de servicio | Una cuenta de dominio, por ejemplo `ABERIO\svc_facturas`, con acceso a esa ruta de red |
| Zona horaria | El código asume hora de Lima (UTC-5) para la fecha y hora de recepción que ve el proveedor |
**La cuenta de servicio es importante.** Por defecto un servicio corre como `LocalSystem`, que **no ve recursos de red**: la copia a la ruta de facturación electrónica fallaría. Use una cuenta con permiso sobre ese recurso.
## Instalación
En PowerShell **como administrador**:
```powershell
cd C:\apps
git clone https://unasgeorge.vaquita-hadar.ts.net/george/descarga_factura_transporte.git
cd descarga_factura_transporte
git checkout v1.2.0 # siempre una versión etiquetada, no main
py -3 -m venv venv
venv\Scripts\python -m pip install -r requirements.txt
copy .env.example .env # y completar los valores
```
Complete el `.env` (cómo obtener las credenciales de Microsoft: [configurar_msal.md](configurar_msal.md)). **Empiece con `NOTIFICACION_MODO=prueba`**: las respuestas van al equipo y no a los proveedores. Defina `NOTIFICACION_EQUIPO` y `LOGO_URL`.
### Verificar antes de instalar el servicio
```powershell
venv\Scripts\python tools\verificar_conexiones.py
```
Revisa, sin modificar nada ni imprimir secretos: el `.env`, el token y la lectura del buzón de Microsoft Graph, SAP Service Layer, SAP HANA y la ruta de red de facturación (que debe ser accesible y escribible con la cuenta del servicio). Termina con código 1 si algo falla. Opcionalmente, las pruebas (no tocan SAP ni Outlook):
```powershell
venv\Scripts\python -m pip install pytest==9.1.1
venv\Scripts\python -m pytest -q
```
### Instalar y arrancar el servicio
```powershell
.\deploy\instalar_servicio.ps1 -Cuenta "ABERIO\svc_facturas" # pide la contraseña
nssm start descarga_factura_transporte
```
El script configura: arranque automático, directorio de trabajo del proyecto, reinicio automático a los 30 s si el proceso muere, parada con Ctrl+C (20 s de gracia), rotación de la salida del servicio a 10 MB y las variables `PYTHONUTF8=1` y `PYTHONIOENCODING=utf-8` (sin ellas los emojis del log dan error en la consola de Windows).
## Comprobar que funciona
```powershell
Get-Service descarga_factura_transporte
Get-Content logs\descarga_factura_transporte.log -Wait -Tail 30
```
En el log, al arrancar, debe verse `🚀 Demonio iniciado … (versión 1.2.0)`, y `config_validator` anuncia el modo de notificación (`PRUEBA` o `REAL`).
## Operación
| Tema | Detalle |
|------|---------|
| Logs de la aplicación | `logs\descarga_factura_transporte.log`, rotación diaria, 30 días |
| Salida del servicio | `logs\servicio.out.log` y `servicio.err.log` (10 MB, rotan). Mire `servicio.err.log` si el servicio arranca y se detiene |
| Cambiar el `.env` | `nssm restart descarga_factura_transporte` |
| Horarios | `HORARIOS` en `main.py` (24 h cubiertas, ver [main.md](main.md)) |
| Copia de seguridad | `emails.db` guarda los correos procesados y los códigos de seguimiento. Cópielo con el servicio detenido, o con `sqlite3 emails.db ".backup respaldo.db"` |
| Espacio en disco | `adjuntos\` y `resultado\` **no se limpian solos**: planifique una limpieza periódica |
| Pasar a modo real | Cuando el equipo haya validado las respuestas: `NOTIFICACION_MODO=real` y reiniciar |
## Actualizar y volver atrás (rollback)
Siempre a una **versión etiquetada** (ver [CHANGELOG](../CHANGELOG.md)):
```powershell
.\deploy\actualizar.ps1 -Version v1.2.0
.\deploy\actualizar.ps1 -Version v1.1.0 # volver a la anterior
```
El script detiene el servicio, hace `git checkout` de la etiqueta, instala las dependencias y arranca de nuevo (aunque algo falle, vuelve a levantar lo que había). No toca el `.env` ni `emails.db`. Se niega a actuar si hay cambios locales sin guardar.
## Desinstalar
```powershell
.\deploy\desinstalar_servicio.ps1 # no borra el proyecto, el .env ni emails.db
```
## Problemas frecuentes
| Síntoma | Causa probable |
|---------|----------------|
| El servicio arranca y se detiene enseguida | Falta el `.env`, o `config_validator` rechazó una variable: ver `logs\servicio.err.log` |
| `Access is denied` al copiar a la ruta de red | El servicio corre como `LocalSystem`. Reinstale con `-Cuenta` o cambie la cuenta en `services.msc` |
| `UnicodeEncodeError` en la salida del servicio | Falta `PYTHONUTF8=1`: reinstale con `instalar_servicio.ps1` |
| `AADSTS7000222` / `AADSTS700016` | Secret vencido / `CLIENT_ID` y `TENANT_ID` de aplicaciones distintas: ver [configurar_msal.md](configurar_msal.md) |
| `ConnectTimeout` hacia SAP o HANA | VPN o firewall: abra el puerto 50000 (Service Layer) y 30015 (HANA) desde el servidor |
| Los correos llegan sin logo | `LOGO_URL` vacío o no accesible públicamente |
+15 -13
View File
@@ -4,31 +4,33 @@
| Archivo | Líneas | Rol |
|---------|--------|-----|
| `main.py` | 137 | Demonio orquestador (loop de horarios) |
| `pipeline.py` | 344 | Orquestador del pipeline (autenticar, descargar correos, procesar un correo) |
| `pipeline_utils.py` | 27 | `log()` (logging con indentación de árbol y `severity`) y `ruta_carpeta()` |
| `main.py` | 142 | Demonio orquestador (loop de horarios) |
| `pipeline.py` | 437 | Orquestador del pipeline (autenticar, descargar correos, procesar un correo) |
| `pipeline_utils.py` | 38 | `log()` (logging con indentación de árbol y `severity`) y `ruta_carpeta()` |
| `adjuntos.py` | 82 | Descarga de adjuntos (Graph) y filtrado PDF/XML |
| `documentos.py` | 372 | Extracción PDF/XML, asignación de guías, copia a red |
| `persistencia.py` | 163 | Guardado de JSON de resultados y de drafts |
| `draft_sap.py` | 442 | Validación de precios contra SAP, valor referencial y construcción del draft |
| `notificaciones.py` | 117 | Cuerpos HTML de los correos de notificación |
| `documentos.py` | 378 | Extracción PDF/XML, asignación de guías, copia a red |
| `persistencia.py` | 165 | Guardado de JSON de resultados y de drafts |
| `draft_sap.py` | 444 | Validación de precios contra SAP, valor referencial y construcción del draft |
| `notificaciones.py` | 121 | Cuerpos HTML de los correos de notificación |
| `sunat_schema.py` | 722 | Parseo de XML SUNAT (Invoice, DespatchAdvice) |
| `pdf_utilities.py` | 471 | Parseo de PDFs (factura, guías) |
| `sapb1_manager.py` | 401 | Comunicación con SAP B1 Service Layer |
| `sapb1_manager.py` | 469 | Comunicación con SAP B1 Service Layer |
| `outlook_utilities.py` | 274 | Comunicación con Microsoft Graph |
| `hana_manager.py` | 141 | Comunicación con SAP HANA directa (fallback de `sapb1_manager` y valor referencial) |
| `database_utilities.py` | 156 | SQLite local (cache de correos) |
| `database_utilities.py` | 164 | SQLite local (cache de correos) |
| `tools/*_prueba.py`, `tools/completar_guias_pipeline.py`, `tools/explorar_notes_xml.py` | — | Scripts manuales sobre `tests/fixtures/correos_prueba/` (anteriores a la suite pytest) |
| `tools/verificar_conexiones.py` | 85 | Comprueba `.env`, Graph, SAP, HANA y la ruta de red sin modificar nada |
| `deploy/*.ps1` | — | Instalar, actualizar (versión etiquetada / rollback) y desinstalar el servicio de Windows con NSSM |
| `tools/consulta_rapida.py` | 120 | Script auxiliar para reprocesar correos por ID (sets en `tools/ids_prueba.json`) |
| `seguimiento.py` | 206 | Códigos de seguimiento y resultado por comprobante (tabla `comprobantes`) |
| `respuesta_proveedor.py` | 285 | Respuesta de aceptado/rechazado al remitente (modo prueba/real, protecciones, contextos) |
| `observaciones.py` | 157 | Catálogo de motivos de rechazo y sus textos |
| `seguimiento.py` | 226 | Códigos de seguimiento y resultado por comprobante (tabla `comprobantes`) |
| `respuesta_proveedor.py` | 300 | Respuesta de aceptado/rechazado al remitente (modo prueba/real, protecciones, contextos) |
| `observaciones.py` | 165 | Catálogo de motivos de rechazo y sus textos |
| `plantillas.py` | 123 | Renderizador de las plantillas HTML de correo |
| `config_validator.py` | 98 | Valida el `.env` al arrancar (obligatorias, ruta de red, avisos de SSL) |
| `email_utilities.py` | 55 | Utilitarios de adjuntos (ZIP, filtrado) |
| `logger_manager.py` | 150 | Logging a archivo rotado (30 días) + consola + captura de `print()` |
Documentación por módulo: `respuesta_proveedor.py`/`observaciones.py`/`plantillas.py` → [respuesta_proveedor.md](respuesta_proveedor.md), `seguimiento.py` → [seguimiento.md](seguimiento.md), `outlook_utilities.py` → [outlook_utilities.md](outlook_utilities.md), `hana_manager.py` → [hana_manager.md](hana_manager.md), validación y draft → [validar_precios.md](validar_precios.md) y [construir_draft.md](construir_draft.md).
Documentación por módulo: despliegue → [despliegue_windows.md](despliegue_windows.md), `respuesta_proveedor.py`/`observaciones.py`/`plantillas.py` → [respuesta_proveedor.md](respuesta_proveedor.md), `seguimiento.py` → [seguimiento.md](seguimiento.md), `outlook_utilities.py` → [outlook_utilities.md](outlook_utilities.md), `hana_manager.py` → [hana_manager.md](hana_manager.md), validación y draft → [validar_precios.md](validar_precios.md) y [construir_draft.md](construir_draft.md).
---
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "descarga_factura_transporte"
version = "1.1.0"
version = "1.2.0"
description = "Descarga y procesamiento de facturas de transporte desde Office365"
requires-python = ">=3.11"
dependencies = [
+85
View File
@@ -0,0 +1,85 @@
"""
Comprueba, sin modificar nada, que el servidor puede trabajar: configuración del .env, credenciales de
Microsoft Graph y lectura del buzón, SAP Service Layer, SAP HANA y la ruta de red de facturación.
Uso (desde cualquier carpeta):
python tools/verificar_conexiones.py
Termina con código 1 si alguna comprobación falla. No imprime secretos.
"""
import logging
import os
import re
import sys
from datetime import datetime, timedelta
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
sys.path.insert(0, RAIZ)
from dotenv import load_dotenv
load_dotenv(dotenv_path=os.path.join(RAIZ, ".env"))
logging.disable(logging.CRITICAL) # solo interesa el resultado de cada comprobación
import config_validator
import hana_manager
import outlook_utilities
import sapb1_manager
fallos = 0
def resultado(ok: bool, nombre: str, detalle: str = "") -> None:
global fallos
fallos += 0 if ok else 1
print(f"{'✅' if ok else '❌'} {nombre}{(': ' + detalle) if detalle else ''}")
def resumen_error(ex: Exception) -> str:
return ", ".join(re.findall(r"AADSTS\d+", str(ex))) or f"{type(ex).__name__}: {str(ex)[:110]}"
# 1. Configuración
errores, avisos = config_validator.validar_configuracion()
resultado(not errores, "Configuración (.env)", "; ".join(errores))
for aviso in avisos:
print(f" ⚠️ {aviso}")
# 2. Microsoft Graph: token y lectura de la bandeja
token = None
try:
token = outlook_utilities.get_access_token()
resultado(bool(token), "Microsoft Graph: token")
except Exception as ex:
resultado(False, "Microsoft Graph: token", resumen_error(ex))
if token:
try:
hoy = datetime.now()
correos = outlook_utilities.get_emails(os.getenv("EMAIL", ""), token, (hoy - timedelta(days=1)).strftime("%Y-%m-%d"), hoy.strftime("%Y-%m-%d"))
resultado(True, "Microsoft Graph: lectura de la bandeja", f"{len(correos)} correos en el último día")
except Exception as ex:
resultado(False, "Microsoft Graph: lectura de la bandeja", resumen_error(ex))
# 3. SAP Service Layer
try:
resultado(True, "SAP Service Layer", f"{len(sapb1_manager.get_zonas())} zonas")
except Exception as ex:
resultado(False, "SAP Service Layer", resumen_error(ex))
# 4. SAP HANA
conexion = hana_manager.get_connection()
resultado(conexion is not None, "SAP HANA")
if conexion is not None:
conexion.close()
# 5. Ruta de red de facturación electrónica (opcional)
ruta = os.getenv("RUTA_RED_FACTURACION", "").strip()
if ruta:
existe = os.path.isdir(ruta)
resultado(existe and os.access(ruta, os.W_OK), "Ruta de red de facturación", "" if existe else "no existe o no es accesible con esta cuenta")
else:
print(" ⚠️ RUTA_RED_FACTURACION no definida: no se copia a la red (no se comprueba)")
print()
print("Todo listo." if not fallos else f"{fallos} comprobación(es) con fallo.")
sys.exit(1 if fallos else 0)