Reemplaza logging por consola/archivo, agrega historial de intentos y soporte para notas de crédito

- Nuevo logger_manager.py (reemplaza logger_utilities.py): logging con formato de árbol,
  rotación diaria a sunat.log, y consola por stdout (antes iba por stderr y se duplicaba
  en logs/error.log del launchd).
- Nuevo historial_manager.py: registra cada intento de consulta/descarga en historial.db
  para auditoría, con limpieza automática de registros de más de 180 días.
- consulta_cpe.py: corrige el id de notas de crédito/débito (usa F7/F8, no 07/08) y
  agrega reintentos ante errores 5xx.
- hana_manager.py: suma consulta a ORPC para notas de crédito (Indicator 07) y soporte
  de ObjType para actualizar la tabla correcta (OPCH/ORPC) al marcar PDF/XML descargado.
- gestor_fallas.py / main.py: propagan objtype end-to-end y dejan constancia de cada
  intento en historial_manager.
- Elimina login_api.py y reporte_fallas.py (código no usado / script manual sin uso reciente).
- Actualiza .gitignore (sunat-venv/, historial.db*) y docs/ para reflejar el estado real
  del proyecto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5 committed 2026-08-31 12:31:48 -05:00
1 parent 99a587c4da
commit c2503745bf
18 files changed
+647 -862

No files matched your search

+10 -7
View File
@@ -10,6 +10,9 @@ from get_token import (
fill_login_form, save_token, save_screenshot,
check_for_token, navigate_sol_menu, extract_token_from_menu,
)
from logger_manager import get_logger
logger = get_logger(__name__)
# ============================================================================
USER_AGENT = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) "
@@ -47,16 +50,16 @@ def get_token_headless(timeout=90000):
page = context.new_page()
page.set_default_timeout(timeout)
print("1. Sesión desde SOL...")
logger.info("1. Sesión desde SOL...")
page.goto("https://www.sunat.gob.pe/sol.html", wait_until="domcontentloaded")
print("2. Navegando a menú directo...")
logger.info("2. Navegando a menú directo...")
page.goto(MENU_URL, wait_until="domcontentloaded")
page.wait_for_load_state("networkidle")
has_inputs = len(page.locator("input[type='password']").all()) > 0
if has_inputs or any(k in page.url.lower() for k in ["login", "autentica", "auth"]):
print("3. Login detectado, rellenando credenciales...")
logger.info("3. Login detectado, rellenando credenciales...")
fill_login_form(page)
page.keyboard.press("Enter")
try:
@@ -65,9 +68,9 @@ def get_token_headless(timeout=90000):
pass
page.wait_for_timeout(3000)
else:
print("3. Sin login (sesión activa)")
logger.info("3. Sin login (sesión activa)")
print("4. Buscando token en menú...")
logger.info("4. Buscando token en menú...")
page.wait_for_timeout(2000)
token = check_for_token(page)
if not token:
@@ -79,11 +82,11 @@ def get_token_headless(timeout=90000):
browser.close()
if token:
print(" ✅ Token obtenido (headless)")
logger.info("Token obtenido (headless)")
save_token(token)
return token
print(" ❌ No se encontró token")
logger.error("No se encontró token")
return None
# ============================================================================