diff --git a/consulta_cpe.py b/consulta_cpe.py index 66aa0f7..8da2389 100644 --- a/consulta_cpe.py +++ b/consulta_cpe.py @@ -59,7 +59,7 @@ def get_headers(token): return { "Authorization": f"Bearer {token}", "Accept": "application/json", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0", "Origin": "https://e-factura.sunat.gob.pe", "Referer": "https://e-factura.sunat.gob.pe/", } diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 7f912c1..f93ab4a 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -3,7 +3,7 @@ ## systemd (Linux) ### Requisitos adicionales -- **Google Chrome** instalado (para `get_token_headless.py`) +- **Mozilla Firefox** instalado (para `get_token_headless.py`) - **rsync** y **cifs-utils** (para montar SMB y sincronizar) ### Servicio @@ -34,7 +34,7 @@ sudo systemctl start sunat-cpe ## NSSM (Windows) ### Requisitos -- **Google Chrome** instalado (cualquier versión) — necesario para `get_token_headless.py` +- **Mozilla Firefox** instalado (cualquier versión) — necesario para `get_token_headless.py` - Python 3.12+ con dependencias instaladas - **Unidad de red mapeable** (SMB) para sincronizar descargas @@ -60,7 +60,7 @@ nssm start SUNAT-CPE 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: +Como servicio, el daemon usa `get_token_headless()` (headless, Firefox real) para renovar el token automáticamente sin intervención. Si Firefox 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 @@ -77,7 +77,7 @@ nssm start SUNAT-CPE ```dockerfile FROM python:3.12-slim -RUN pip install -r requirements.txt && playwright install chromium +RUN pip install -r requirements.txt && playwright install firefox WORKDIR /app COPY . . CMD ["python", "main.py"] diff --git a/docs/FLOWCHART.md b/docs/FLOWCHART.md index 61a166e..2d5af46 100644 --- a/docs/FLOWCHART.md +++ b/docs/FLOWCHART.md @@ -5,7 +5,7 @@ flowchart TD B -->|No| C["Esperar"] C --> B B -->|Sí| D["Token válido?"] - D -->|No| E["get_token_headless()
channel=chrome headless=True
→ get_token.py headless=False"] + D -->|No| E["get_token_headless()
headless=True
→ get_token.py headless=False"] E --> F["token.json"] D -->|Sí| F end diff --git a/docs/MODULES.md b/docs/MODULES.md index f28e117..67069c7 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -3,7 +3,7 @@ | 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_headless.py` | Login headless via Firefox real. 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. | diff --git a/docs/SETUP.md b/docs/SETUP.md index 2be7c48..bc46ca0 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -3,9 +3,9 @@ ## Requisitos - Python 3.10+ -- Playwright (chromium) +- Playwright (firefox) - Acceso a HANA (hdbcli) -- **Google Chrome** (para `get_token_headless.py`) +- **Mozilla Firefox** (para `get_token_headless.py`) - **rsync** (Mac/Linux) o **net use** (Windows) para SMB ## Instalación @@ -17,7 +17,7 @@ source sunat-env/bin/activate # Instalar dependencias pip install -r requirements.txt -playwright install chromium +playwright install firefox ``` ## Variables de Entorno (`.env`) @@ -55,7 +55,7 @@ source sunat-env/bin/activate # Obtener token manualmente (con navegador visible) python get_token.py -# Obtener token headless (sin ventana, requiere Chrome real) +# Obtener token headless (sin ventana, requiere Firefox real) python get_token_headless.py # Daemon completo (automático) diff --git a/get_token.py b/get_token.py index d675953..6845242 100644 --- a/get_token.py +++ b/get_token.py @@ -28,9 +28,8 @@ def save_screenshot(page, name): def get_token(headless=False, timeout=60000): print("🚀 Iniciando navegador...") with sync_playwright() as p: - browser = p.chromium.launch( + browser = p.firefox.launch( headless=headless, - args=["--disable-blink-features=AutomationControlled"], ) context = browser.new_context( viewport={"width": 1280, "height": 800}, diff --git a/get_token_headless.py b/get_token_headless.py index f74d40c..2a4ad96 100644 --- a/get_token_headless.py +++ b/get_token_headless.py @@ -12,39 +12,31 @@ from get_token import ( ) # ============================================================================ USER_AGENT = ( - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/120.0.0.0 Safari/537.36" + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) " + "Gecko/20100101 Firefox/121.0" ) # ============================================================================ -def chrome_instalado(): +def firefox_instalado(): sistema = platform.system().lower() if sistema == "windows": - return bool(shutil.which("chrome")) or any( + return bool(shutil.which("firefox")) or any( os.path.exists(p) for p in [ - "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", - "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", + "C:\\Program Files\\Mozilla Firefox\\firefox.exe", + "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe", ] ) elif sistema == "darwin": - return os.path.exists("/Applications/Google Chrome.app") + return os.path.exists("/Applications/Firefox.app") elif sistema == "linux": - return bool(shutil.which("google-chrome") or shutil.which("google-chrome-stable")) + return bool(shutil.which("firefox") or shutil.which("firefox-esr")) return False # ============================================================================ def get_token_headless(timeout=90000): with sync_playwright() as p: - browser = p.chromium.launch( - channel="chrome", + browser = p.firefox.launch( headless=True, - args=[ - "--disable-blink-features=AutomationControlled", - "--disable-popup-blocking", - "--no-sandbox", - "--window-size=1280,800", - ], ) context = browser.new_context( viewport={"width": 1280, "height": 800}, diff --git a/login_api.py b/login_api.py index 917d2e0..30204d3 100644 --- a/login_api.py +++ b/login_api.py @@ -67,7 +67,7 @@ def try_password_grant(): "client_id": CLIENT_ID, } headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0", "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", } @@ -97,7 +97,7 @@ def try_auth_code_flow(): session = requests.Session() session.verify = False session.headers.update({ - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }) diff --git a/main.py b/main.py index 644aa79..f5f8bef 100644 --- a/main.py +++ b/main.py @@ -102,9 +102,9 @@ def obtener_token(): print("\n🔑 Token no disponible o expirado. Obteniendo nuevo...") - from get_token_headless import get_token_headless as obtener_headless, chrome_instalado - if not chrome_instalado(): - print(" ⚠️ Google Chrome no instalado. Saltando headless...") + from get_token_headless import get_token_headless as obtener_headless, firefox_instalado + if not firefox_instalado(): + print(" ⚠️ Firefox no instalado. Saltando headless...") else: try: if obtener_headless(): diff --git a/requirements.txt b/requirements.txt index c8e486c..22baac4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,22 @@ -playwright -python-dotenv -requests -hdbcli -matplotlib +certifi==2026.6.17 +charset-normalizer==3.4.9 +contourpy==1.3.3 +cycler==0.12.1 +fonttools==4.63.0 +greenlet==3.5.3 +hdbcli==2.29.25 +idna==3.18 +kiwisolver==1.5.0 +matplotlib==3.11.0 +numpy==2.5.1 +packaging==26.2 +pillow==12.3.0 +playwright==1.61.0 +pyee==13.0.1 +pyparsing==3.3.2 +python-dateutil==2.9.0.post0 +python-dotenv==1.2.2 +requests==2.34.2 +six==1.17.0 +typing_extensions==4.16.0 +urllib3==2.7.0