versión 1.2.2: horarios nocturnos unificados en dos tramos
HORARIOS pasa a 5 bloques que siguen cubriendo las 24 horas: 00:00-06:30 cada 30 minutos (antes 00:00-05:00 cada 60 s y 05:00-06:30 cada 30 min) y 18:30-24:00 cada 60 minutos (antes dos bloques iguales). El día no cambia. De madrugada un correo puede esperar hasta 30 minutos y los reintentos por SAP fuera de línea, uno por ciclo, van igual de espaciados. Se ordena el formato de la lista, test_horarios fija el primer y el último minuto de cada bloque (y que nunca se devuelva None en el día), y se actualizan docs/main.md, la guía de despliegue y el CHANGELOG. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6030320822
commit
1d23e87b9b
7 files changed
+34
-22
No files matched your search
+6
-1
@@ -1,6 +1,11 @@
|
|||||||
# Historial de cambios
|
# Historial de cambios
|
||||||
|
|
||||||
Las versiones siguen [SemVer](https://semver.org/lang/es/) y se publican como etiquetas de git (`v1.2.1`). La versión vigente está en `pyproject.toml` y se muestra en el log al arrancar.
|
Las versiones siguen [SemVer](https://semver.org/lang/es/) y se publican como etiquetas de git (`v1.2.2`). La versión vigente está en `pyproject.toml` y se muestra en el log al arrancar.
|
||||||
|
|
||||||
|
## [1.2.2] — 2026-10-22
|
||||||
|
|
||||||
|
### Cambiado
|
||||||
|
- **Horarios del demonio:** la noche pasa a dos tramos, `00:00–06:30` cada 30 minutos y `18:30–24:00` cada 60 (antes: `00:00–05:00` cada 60 segundos, `05:00–06:30` cada 30 minutos, `18:30–21:30` y `21:30–24:00` cada 60). El día sigue igual. De madrugada un correo puede esperar hasta 30 minutos y los reintentos por SAP fuera de línea (uno por ciclo) van igual de espaciados. Los 5 bloques siguen cubriendo las 24 horas sin huecos.
|
||||||
|
|
||||||
## [1.2.1] — 2026-10-22
|
## [1.2.1] — 2026-10-22
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ El demonio trabaja por bloques horarios (cada 1, 5, 30 o 60 minutos según la ho
|
|||||||
|
|
||||||
## Despliegue
|
## 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.1`); el historial está en [`CHANGELOG.md`](CHANGELOG.md).
|
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.2`); el historial está en [`CHANGELOG.md`](CHANGELOG.md).
|
||||||
|
|
||||||
## Pruebas
|
## Pruebas
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ En PowerShell **como administrador**:
|
|||||||
cd C:\apps
|
cd C:\apps
|
||||||
git clone https://unasgeorge.vaquita-hadar.ts.net/george/descarga_factura_transporte.git
|
git clone https://unasgeorge.vaquita-hadar.ts.net/george/descarga_factura_transporte.git
|
||||||
cd descarga_factura_transporte
|
cd descarga_factura_transporte
|
||||||
git checkout v1.2.1 # siempre una versión etiquetada, no main
|
git checkout v1.2.2 # siempre una versión etiquetada, no main
|
||||||
|
|
||||||
py -3 -m venv venv
|
py -3 -m venv venv
|
||||||
venv\Scripts\python -m pip install -r requirements.txt
|
venv\Scripts\python -m pip install -r requirements.txt
|
||||||
@@ -66,7 +66,7 @@ Get-Service descarga_factura_transporte
|
|||||||
Get-Content logs\descarga_factura_transporte.log -Wait -Tail 30
|
Get-Content logs\descarga_factura_transporte.log -Wait -Tail 30
|
||||||
```
|
```
|
||||||
|
|
||||||
En el log, al arrancar, debe verse `🚀 Demonio iniciado … (versión 1.2.1)`, y `config_validator` anuncia el modo de notificación (`PRUEBA` o `REAL`).
|
En el log, al arrancar, debe verse `🚀 Demonio iniciado … (versión 1.2.2)`, y `config_validator` anuncia el modo de notificación (`PRUEBA` o `REAL`).
|
||||||
|
|
||||||
## Operación
|
## Operación
|
||||||
|
|
||||||
@@ -85,8 +85,8 @@ En el log, al arrancar, debe verse `🚀 Demonio iniciado … (versión 1.2.1)`,
|
|||||||
Siempre a una **versión etiquetada** (ver [CHANGELOG](../CHANGELOG.md)):
|
Siempre a una **versión etiquetada** (ver [CHANGELOG](../CHANGELOG.md)):
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\deploy\actualizar.ps1 -Version v1.2.1
|
.\deploy\actualizar.ps1 -Version v1.2.2
|
||||||
.\deploy\actualizar.ps1 -Version v1.2.0 # volver a la anterior
|
.\deploy\actualizar.ps1 -Version v1.2.1 # 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.
|
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.
|
||||||
|
|||||||
+4
-6
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
main.py
|
main.py
|
||||||
├── Constantes: HORARIOS (7 bloques que cubren las 24 horas)
|
├── Constantes: HORARIOS (5 bloques que cubren las 24 horas)
|
||||||
├── obtener_intervalo(dt) → (intervalo, modo) | None
|
├── obtener_intervalo(dt) → (intervalo, modo) | None
|
||||||
├── segundos_hasta_proximo_inicio(dt) → int
|
├── segundos_hasta_proximo_inicio(dt) → int
|
||||||
├── non_negative_int(value) → int (validador argparse)
|
├── non_negative_int(value) → int (validador argparse)
|
||||||
@@ -81,15 +81,13 @@ main()
|
|||||||
|
|
||||||
| Desde | Hasta | Intervalo | Modo |
|
| Desde | Hasta | Intervalo | Modo |
|
||||||
|---------|---------|-----------|----------|
|
|---------|---------|-----------|----------|
|
||||||
| 00:00 | 05:00 | 60s | rápido |
|
| 00:00 | 06:30 | 1800s | full |
|
||||||
| 05:00 | 06:30 | 1800s | full |
|
|
||||||
| 06:30 | 13:15 | 300s | rápido |
|
| 06:30 | 13:15 | 300s | rápido |
|
||||||
| 13:15 | 14:00 | 1800s | full |
|
| 13:15 | 14:00 | 1800s | full |
|
||||||
| 14:00 | 18:30 | 300s | rápido |
|
| 14:00 | 18:30 | 300s | rápido |
|
||||||
| 18:30 | 21:30 | 3600s | full |
|
| 18:30 | 24:00 | 3600s | full |
|
||||||
| 21:30 | 24:00 | 3600s | full |
|
|
||||||
|
|
||||||
Los bloques cubren las 24 horas sin huecos ni solapes (lo comprueba `tests/test_horarios.py`). El modo (`rápido`/`full`) solo se registra en el log: `run_pipeline` se ejecuta igual en ambos. Antes de la versión 1.2.0 los tramos 21:30–00:00 y 05:00–06:30 no tenían bloque y el demonio dormía hasta las 06:30.
|
Los bloques cubren las 24 horas sin huecos ni solapes (lo comprueba `tests/test_horarios.py`). El modo (`rápido`/`full`) solo se registra en el log: `run_pipeline` se ejecuta igual en ambos. Antes de la versión 1.2.0 los tramos 21:30–00:00 y 05:00–06:30 no tenían bloque y el demonio dormía hasta las 06:30; desde la 1.2.2 la noche son solo dos tramos (00:00–06:30 cada 30 minutos y 18:30–24:00 cada 60). De madrugada un correo puede esperar hasta 30 minutos, y los reintentos por SAP fuera de línea (uno por ciclo) van igual de espaciados.
|
||||||
|
|
||||||
## Entry point
|
## Entry point
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,11 @@ logger = logging.getLogger(__name__)
|
|||||||
# (hora inicio, min inicio, hora fin, min fin, intervalo en segundos, modo). El fin no se incluye.
|
# (hora inicio, min inicio, hora fin, min fin, intervalo en segundos, modo). El fin no se incluye.
|
||||||
# Los bloques cubren las 24 horas sin huecos ni solapes (lo comprueba tests/test_horarios.py).
|
# Los bloques cubren las 24 horas sin huecos ni solapes (lo comprueba tests/test_horarios.py).
|
||||||
HORARIOS = [
|
HORARIOS = [
|
||||||
(0, 0, 5, 0, 60, "rapido"), # 00:00 - 05:00 -> cada 60 segundos
|
(0, 0, 6, 30, 1800, "full"), # 00:00 - 06:30 -> cada 30 minutos
|
||||||
(5, 0, 6, 30, 1800, "full"), # 05:00 - 06:30 -> cada 30 minutos
|
|
||||||
(6, 30, 13, 15, 300, "rapido"), # 06:30 - 13:15 -> cada 5 minutos
|
(6, 30, 13, 15, 300, "rapido"), # 06:30 - 13:15 -> cada 5 minutos
|
||||||
(13, 15, 14, 0, 1800, "full"), # 13:15 - 14:00 -> cada 30 minutos
|
(13, 15, 14, 0, 1800, "full"), # 13:15 - 14:00 -> cada 30 minutos
|
||||||
(14, 0, 18, 30, 300, "rapido"), # 14:00 - 18:30 -> cada 5 minutos
|
(14, 0, 18, 30, 300, "rapido"), # 14:00 - 18:30 -> cada 5 minutos
|
||||||
(18, 30, 21, 30, 3600, "full"), # 18:30 - 21:30 -> cada 60 minutos
|
(18, 30, 24, 0, 3600, "full"), # 18:30 - 24:00 -> cada 60 minutos
|
||||||
(21, 30, 24, 0, 3600, "full"), # 21:30 - 24:00 -> cada 60 minutos
|
|
||||||
]
|
]
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
def obtener_intervalo(dt: datetime) -> tuple[int, str] | None:
|
def obtener_intervalo(dt: datetime) -> tuple[int, str] | None:
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "descarga_factura_transporte"
|
name = "descarga_factura_transporte"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
description = "Descarga y procesamiento de facturas de transporte desde Office365"
|
description = "Descarga y procesamiento de facturas de transporte desde Office365"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
+16
-5
@@ -23,11 +23,22 @@ def test_los_bloques_estan_ordenados_y_se_encadenan():
|
|||||||
assert inicios[1:] == fines[:-1] # cada bloque empieza donde termina el anterior
|
assert inicios[1:] == fines[:-1] # cada bloque empieza donde termina el anterior
|
||||||
|
|
||||||
|
|
||||||
def test_los_huecos_de_antes_ahora_tienen_horario():
|
def test_los_limites_de_cada_bloque():
|
||||||
assert _intervalo(21, 29) == (3600, "full") and _intervalo(21, 30) == (3600, "full")
|
"""Primer y último minuto de cada bloque, con su intervalo."""
|
||||||
assert _intervalo(23, 59) == (3600, "full")
|
esperado = [
|
||||||
assert _intervalo(5, 0) == (1800, "full") and _intervalo(6, 29) == (1800, "full")
|
((0, 0), (1800, "full")), ((6, 29), (1800, "full")), # madrugada
|
||||||
assert _intervalo(6, 30) == (300, "rapido")
|
((6, 30), (300, "rapido")), ((13, 14), (300, "rapido")), # mañana
|
||||||
|
((13, 15), (1800, "full")), ((13, 59), (1800, "full")), # mediodía
|
||||||
|
((14, 0), (300, "rapido")), ((18, 29), (300, "rapido")), # tarde
|
||||||
|
((18, 30), (3600, "full")), ((23, 59), (3600, "full")), # noche
|
||||||
|
]
|
||||||
|
for (hora, minuto), valor in esperado:
|
||||||
|
assert _intervalo(hora, minuto) == valor, f"{hora:02d}:{minuto:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nunca_se_devuelve_none_en_el_dia():
|
||||||
|
"""``obtener_intervalo`` devuelve None fuera de horario: con los bloques actuales no debe pasar nunca."""
|
||||||
|
assert all(_intervalo(h, m) is not None for h in range(24) for m in range(60))
|
||||||
|
|
||||||
|
|
||||||
def test_la_version_del_proyecto_sale_de_pyproject():
|
def test_la_version_del_proyecto_sale_de_pyproject():
|
||||||
|
|||||||
Reference in new issue
Block a user