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