Files
GeorgeandClaude Sonnet 5 c2503745bf 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>
2026-08-31 12:31:48 -05:00

167 lines
5.4 KiB
Python

import os
import json
from pathlib import Path
from dotenv import load_dotenv
from logger_manager import get_logger
# ============================================================================
load_dotenv()
logger = get_logger(__name__)
HANA_HOST = os.getenv("HANA_HOST", "")
HANA_PORT = int(os.getenv("HANA_PORT", 30015))
HANA_USER = os.getenv("HANA_USER", "")
HANA_PASSWORD = os.getenv("HANA_PASSWORD", "")
HANA_SCHEMA = os.getenv("HANA_SCHEMA", "")
_conn = None
# ============================================================================
def get_connection():
global _conn
if _conn is None:
try:
from hdbcli import dbapi
_conn = dbapi.connect(
address=HANA_HOST,
port=HANA_PORT,
user=HANA_USER,
password=HANA_PASSWORD
)
except Exception as e:
logger.error(f"Error conectando a HANA: {e}")
return None
return _conn
# ============================================================================
def obtener_pendientes() -> list[dict]:
conn = get_connection()
if not conn:
return []
try:
query = f"""
SELECT
"DocEntry",
"DocNum",
"DocDate",
EXTRACT(MONTH FROM "DocDate") "Mes",
"Indicator",
"LicTradNum",
"FolioPref",
"FolioNum",
"ObjType",
"U_PGX_REPIMP" "urlPDF",
"U_PGX_PDF" "tienePDF",
"U_PGX_REPIMPXML" "urlXML",
"U_PGX_XML" "tieneXML"
FROM "{HANA_SCHEMA}".OPCH
WHERE ("U_PGX_PDF" = 'N' OR "U_PGX_XML" = 'N')
AND EXTRACT(YEAR FROM "DocDate") = 2026
AND "Indicator" IN ('01','02','03')
AND "CANCELED" = 'N'
AND "LicTradNum" NOT IN ('20100047218','20100130204','20100053455')
AND SUBSTRING("FolioPref", 1, 1) NOT BETWEEN '0' AND '9'
UNION ALL
SELECT
"DocEntry",
"DocNum",
"DocDate",
EXTRACT(MONTH FROM "DocDate") "Mes",
"Indicator",
"LicTradNum",
"FolioPref",
"FolioNum",
"ObjType",
"U_PGX_REPIMP" "urlPDF",
"U_PGX_PDF" "tienePDF",
"U_PGX_REPIMPXML" "urlXML",
"U_PGX_XML" "tieneXML"
FROM "{HANA_SCHEMA}".ORPC
WHERE ("U_PGX_PDF" = 'N' OR "U_PGX_XML" = 'N')
AND EXTRACT(YEAR FROM "DocDate") = 2026
AND "Indicator" IN ('07')
AND "CANCELED" = 'N'
AND "LicTradNum" NOT IN ('20100047218','20100130204','20100053455')
AND SUBSTRING("FolioPref", 1, 1) NOT BETWEEN '0' AND '9'
"""
cursor = conn.cursor()
cursor.execute(query)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
cursor.close()
result = [dict(zip(columns, row)) for row in rows]
return result
except Exception as e:
logger.error(f"Error en query HANA: {e}")
return []
# ============================================================================
def marcar_pdf_descargado(docentry: int, url: str, ruta: str, obj_type: int = 18):
conn = get_connection()
if not conn:
return False
tabla = "ORPC" if int(obj_type) == 19 else "OPCH"
try:
cursor = conn.cursor()
cursor.execute(f"""
UPDATE "{HANA_SCHEMA}".{tabla}
SET "U_PGX_PDF" = 'Y'
WHERE "DocEntry" = ?
""", (docentry,))
conn.commit()
cursor.close()
return True
except Exception as e:
logger.error(f"Error actualizando PDF HANA: {e}")
return False
# ============================================================================
def marcar_xml_descargado(docentry: int, url: str, ruta: str, obj_type: int = 18):
conn = get_connection()
if not conn:
return False
tabla = "ORPC" if int(obj_type) == 19 else "OPCH"
try:
cursor = conn.cursor()
cursor.execute(f"""
UPDATE "{HANA_SCHEMA}".{tabla}
SET "U_PGX_XML" = 'Y'
WHERE "DocEntry" = ?
""", (docentry,))
conn.commit()
cursor.close()
return True
except Exception as e:
logger.error(f"Error actualizando XML HANA: {e}")
return False
# ============================================================================
def cerrar_conexion():
global _conn
if _conn:
try:
_conn.close()
except:
pass
_conn = None
# ============================================================================
if __name__ == "__main__":
print("=" * 60)
print("📋 HANA MANAGER - Prueba")
print("=" * 60)
docs = obtener_pendientes()
print(f"\n📄 Documentos pendientes: {len(docs)}")
for d in docs[:5]:
print(f" DocEntry={d['DocEntry']} DocNum={d['DocNum']} "
f"DocDate={d['DocDate']} Indicator={d['Indicator']} "
f"Folio={d['FolioPref']}-{d['FolioNum']} "
f"PDF={d['tienePDF']} XML={d['tieneXML']}")
if len(docs) > 5:
print(f" ... y {len(docs) - 5} más")
# ============================================================================