primer commit

This commit is contained in:
George committed 2026-07-17 12:21:31 -05:00
commit 2be35d7e55
17 files changed
+2709

No files matched your search

+86
View File
@@ -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`.
+62
View File
@@ -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
```
+30
View File
@@ -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
+70
View File
@@ -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.