primer commit
This commit is contained in:
commit
2be35d7e55
17 files changed
+2709
No files matched your search
@@ -0,0 +1,86 @@
|
||||
# Despliegue
|
||||
|
||||
## systemd (Linux)
|
||||
|
||||
### Requisitos adicionales
|
||||
- **Google Chrome** instalado (para `get_token_headless.py`)
|
||||
- **rsync** y **cifs-utils** (para montar SMB y sincronizar)
|
||||
|
||||
### Servicio
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=SUNAT CPE Downloader Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tu_usuario
|
||||
WorkingDirectory=/ruta/a/descarga_xml_sunat
|
||||
ExecStart=/usr/bin/python3 main.py
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo cp sunat-cpe.service /etc/systemd/system/
|
||||
sudo systemctl enable sunat-cpe
|
||||
sudo systemctl start sunat-cpe
|
||||
```
|
||||
|
||||
## NSSM (Windows)
|
||||
|
||||
### Requisitos
|
||||
- **Google Chrome** instalado (cualquier versión) — necesario para `get_token_headless.py`
|
||||
- Python 3.12+ con dependencias instaladas
|
||||
- **Unidad de red mapeable** (SMB) para sincronizar descargas
|
||||
|
||||
### Instalación como servicio
|
||||
|
||||
```cmd
|
||||
:: Instalar servicio
|
||||
nssm install SUNAT-CPE "C:\Python312\python.exe" "C:\ruta\descarga_xml_sunat\main.py"
|
||||
|
||||
:: Configurar directorio de trabajo
|
||||
nssm set SUNAT-CPE AppDirectory "C:\ruta\descarga_xml_sunat"
|
||||
|
||||
:: Configurar reinicio automático
|
||||
nssm set SUNAT-CPE AppExit Default Exit
|
||||
nssm set SUNAT-CPE AppThrottle 30000
|
||||
|
||||
:: Iniciar servicio
|
||||
nssm set SUNAT-CPE Start SERVICE_AUTO_START
|
||||
nssm start SUNAT-CPE
|
||||
```
|
||||
|
||||
### Logs
|
||||
NSSM redirige stdout/stderr a `C:\ruta\descarga_xml_sunat\logs\` por defecto (el logger del daemon también escribe allí).
|
||||
|
||||
### Renovación de token
|
||||
Como servicio, el daemon usa `get_token_headless()` (headless, Chrome real) para renovar el token automáticamente sin intervención. Si Chrome no está instalado, el daemon lo detecta y usa `get_token.py headless=False` como respaldo (requiere escritorio). Para renovar manualmente:
|
||||
|
||||
```cmd
|
||||
C:\ruta\descarga_xml_sunat\sunat-env\Scripts\python get_token.py
|
||||
```
|
||||
|
||||
### Actualización
|
||||
```cmd
|
||||
nssm stop SUNAT-CPE
|
||||
:: actualizar archivos
|
||||
nssm start SUNAT-CPE
|
||||
```
|
||||
|
||||
## Docker (opcional)
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.12-slim
|
||||
RUN pip install -r requirements.txt && playwright install chromium
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
CMD ["python", "main.py"]
|
||||
```
|
||||
|
||||
Asegúrate de que `requirements.txt` incluya: `playwright`, `python-dotenv`, `requests`, `hdbcli`, `matplotlib`.
|
||||
@@ -0,0 +1,62 @@
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Daemon["🚀 Daemon (main.py)"]
|
||||
A["Inicio"] --> B{"Día activo y<br>en horario?"}
|
||||
B -->|No| C["Esperar"]
|
||||
C --> B
|
||||
B -->|Sí| D["Token válido?"]
|
||||
D -->|No| E["get_token_headless()<br>channel=chrome headless=True<br>→ get_token.py headless=False"]
|
||||
E --> F["token.json"]
|
||||
D -->|Sí| F
|
||||
end
|
||||
|
||||
subgraph HANA["📊 Obtener pendientes"]
|
||||
F --> G["Consultar HANA<br>obtener_pendientes()"]
|
||||
G --> H["542 documentos<br>en OPCH"]
|
||||
H --> I["Filtrar contra<br>fallas_descarga.db"]
|
||||
I --> J["🆕 Nuevos<br>(sin falla previa)"]
|
||||
I --> K["🔄 Reintentos<br>(fallas pendientes)"]
|
||||
end
|
||||
|
||||
subgraph Nuevos["📄 Procesar nuevos"]
|
||||
L["Por cada nuevo:"] --> M["consultar_cpe()<br>GET api-cpe.sunat.gob.pe<br>/v1/.../{ruc}-{tipo}-{serie}-{num}-2"]
|
||||
M --> N{"Status?"}
|
||||
N -->|200| O["descargar_archivo()<br>XML (02) / PDF (01) / CDR (03)"]
|
||||
O --> P["ZIP → extraer en<br>descargas/"]
|
||||
P --> Q["marcar_pdf/xml_descargado()<br>UPDATE HANA OPCH"]
|
||||
Q --> R["✅ OK"]
|
||||
N -->|401| S["⚠️ TokenExpiredError"]
|
||||
S --> T["Renovar token"]
|
||||
T --> M
|
||||
N -->|500/404/otros| U["registrar_falla()<br>→ SQLite con backoff"]
|
||||
end
|
||||
|
||||
subgraph Reintentos["🔄 Procesar reintentos"]
|
||||
V["Por cada reintento<br>(backoff cumplido):"] --> W["consultar_cpe()"]
|
||||
W --> X{"Status?"}
|
||||
X -->|200| Y["descargar_archivo()"]
|
||||
Y --> Z["marcar_pdf/xml_descargado()"]
|
||||
Z --> AA["eliminar_falla()<br>DELETE FROM fallas"]
|
||||
AA --> AB["✅ OK + 🗑️ quitado de SQLite"]
|
||||
X -->|401| AC["Renovar token → reintentar"]
|
||||
X -->|500/404/otros| AD["actualizar_falla()<br>incrementar intento<br>→ próximo backoff"]
|
||||
end
|
||||
|
||||
AD --> AF["sincronizar_a_smb()<br>montar SMB<br>rsync PDF/XML → NAS<br>limpiar JSON/ZIP"]
|
||||
|
||||
AF --> AE["Esperar intervalo<br>→ próximo ciclo"]
|
||||
R --> AF
|
||||
U --> AD
|
||||
AB --> AF
|
||||
|
||||
style A fill:#1a1a2e,stroke:#e94560,color:#fff
|
||||
style E fill:#16213e,stroke:#0f3460,color:#fff
|
||||
style G fill:#0f3460,stroke:#e94560,color:#fff
|
||||
style M fill:#533483,stroke:#e94560,color:#fff
|
||||
style O fill:#1a1a2e,stroke:#e94560,color:#fff
|
||||
style P fill:#16213e,stroke:#0f3460,color:#fff
|
||||
style Q fill:#0f3460,stroke:#e94560,color:#fff
|
||||
style U fill:#3d0000,stroke:#ff4444,color:#fff
|
||||
style AA fill:#003300,stroke:#44ff44,color:#fff
|
||||
style AD fill:#3d0000,stroke:#ff4444,color:#fff
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# Módulos
|
||||
|
||||
| Archivo | Función |
|
||||
|---|---|
|
||||
| `main.py` | Daemon orquestador. Bucle infinito con horarios configurables, obtiene token, consulta HANA, procesa pendientes (nuevos + reintentos). |
|
||||
| `get_token_headless.py` | Login headless via Chrome real (`channel="chrome"`). Sin ventana visible. |
|
||||
| `get_token.py` | Login SOL via Playwright (headless=False). Navega menú → extrae JWT de `sessionStorage['SUNAT.token']`. |
|
||||
| `consulta_cpe.py` | Consulta API REST `api-cpe.sunat.gob.pe`. Retorna `(data, status_code)`. Lanza `TokenExpiredError` en 401. |
|
||||
| `hana_manager.py` | Conexión HANA vía `hdbcli`. Consulta pendientes en `OPCH`, actualiza flags de descarga. |
|
||||
| `gestor_fallas.py` | SQLite `fallas_descarga.db`. Registro de fallas con conteo de intentos y backoff progresivo (0min → 5min → 15min → ... → 12h, máx 20). |
|
||||
| `smb_manager.py` | Monta recurso SMB (`mount_smbfs`/`mount.cifs`/`net use`) y sincroniza PDF/XML al NAS. Limpia JSON/ZIP temporales. |
|
||||
| `login_api.py` | Intento de `password_grant()` / `authorization_code` directo a SUNAT (sin navegador, generalmente ya no funciona). Se mantiene como referencia. |
|
||||
| `logger_utilities.py` | Redirige stdout/stderr a `logs/YYYY-MM-DD.log` + terminal. Rotación diaria. |
|
||||
| `reporte_fallas.py` | Genera resumen textual + 3 gráficos PNG desde `fallas_descarga.db`. |
|
||||
| `token.json` | Token JWT auto-generado con expiración. |
|
||||
| `.env` | Credenciales HANA y SUNAT. |
|
||||
|
||||
## Flujo
|
||||
|
||||
1. `main.py` verifica día/horario → renueva token si expiró (get_token_headless → get_token.py headless=False)
|
||||
2. `obtener_pendientes()` trae todos los documentos de OPCH donde `tienePDF='N'` o `tieneXML='N'`
|
||||
3. Separa en **nuevos** (no están en fallas DB) y **reintentos** (fallas pendientes con backoff cumplido)
|
||||
4. Por cada doc:
|
||||
- `consultar_cpe()` → si 401, renueva token y reintenta
|
||||
- Si falla (500/404/etc) → `registrar_falla()` con backoff progresivo
|
||||
- Si OK → `descargar_archivo()` (XML + PDF + CDR)
|
||||
- `marcar_pdf_descargado()` / `marcar_xml_descargado()` actualizan HANA
|
||||
- Si era reintento exitoso → `eliminar_falla()`
|
||||
5. `sincronizar_a_smb()` monta NAS (si no está montado), copia PDF/XML via rsync (Mac/Linux) o shutil.move (Windows), y elimina JSON/ZIP temporales de `descargas/`
|
||||
6. Espera intervalo configurado (5 min modo rápido, 1 h modo full) y repite
|
||||
@@ -0,0 +1,70 @@
|
||||
# Configuración
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Python 3.10+
|
||||
- Playwright (chromium)
|
||||
- Acceso a HANA (hdbcli)
|
||||
- **Google Chrome** (para `get_token_headless.py`)
|
||||
- **rsync** (Mac/Linux) o **net use** (Windows) para SMB
|
||||
|
||||
## Instalación
|
||||
|
||||
```bash
|
||||
# Crear y activar entorno virtual (recomendado)
|
||||
python3 -m venv sunat-env
|
||||
source sunat-env/bin/activate
|
||||
|
||||
# Instalar dependencias
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
## Variables de Entorno (`.env`)
|
||||
|
||||
```ini
|
||||
# SUNAT
|
||||
SUNAT_RUC=20503662869
|
||||
SUNAT_USER=TAGUMED3
|
||||
SUNAT_PASSWORD=xxxxx
|
||||
|
||||
# HANA
|
||||
HANA_HOST=192.168.1.175
|
||||
HANA_PORT=30015
|
||||
HANA_USER=SYSTEM
|
||||
HANA_PASSWORD=SAPB1Admin.25
|
||||
HANA_SCHEMA=SBO_TAGUMEDICA_21
|
||||
|
||||
# SMB / NAS
|
||||
SMB_SERVER=192.168.1.175
|
||||
SMB_SHARE=FileSAP
|
||||
SMB_USER=tu_usuario
|
||||
SMB_PASSWORD=tu_contraseña
|
||||
SMB_MOUNT_MAC=/Volumes/FileSAP
|
||||
SMB_MOUNT_LINUX=/mnt/filesap
|
||||
SMB_MOUNT_WINDOWS=S:
|
||||
SMB_SUBFOLDER=COMPRAS/TAGUMEDICA/ADJUNTOS_CORREOS
|
||||
```
|
||||
|
||||
## Ejecución
|
||||
|
||||
```bash
|
||||
# Activar entorno virtual
|
||||
source sunat-env/bin/activate
|
||||
|
||||
# Obtener token manualmente (con navegador visible)
|
||||
python get_token.py
|
||||
|
||||
# Obtener token headless (sin ventana, requiere Chrome real)
|
||||
python get_token_headless.py
|
||||
|
||||
# Daemon completo (automático)
|
||||
python main.py
|
||||
|
||||
# Reporte de fallas (resumen texto + gráficos PNG)
|
||||
python reporte_fallas.py
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
Todos los logs se guardan en `logs/YYYY-MM-DD.log`. Rotación automática diaria, los archivos mayores a 7 días se eliminan.
|
||||
Reference in new issue
Block a user