Files
descarga_xml_sunat/reporte_fallas.py
T

168 lines
6.0 KiB
Python

import sqlite3
from pathlib import Path
from datetime import datetime
from collections import Counter
# ============================================================================
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
HAS_MPL = True
except ImportError:
HAS_MPL = False
DB_PATH = Path(__file__).parent / "fallas_descarga.db"
REPORTES_DIR = Path(__file__).parent / "reportes"
REPORTES_DIR.mkdir(exist_ok=True)
COLORS = {
"Error HTTP 500": "#e74c3c",
"falló: PDF": "#e67e22",
"falló: XML": "#3498db",
"falló: PDF, XML": "#9b59b6",
}
CMAP = list(COLORS.values())
# ============================================================================
def _query(sql, params=()):
with sqlite3.connect(str(DB_PATH)) as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute(sql, params).fetchall()
return [dict(r) for r in rows]
# ============================================================================
def reporte_texto():
print("\n" + "=" * 60)
print("📊 REPORTE DE FALLAS - SUNAT")
print("=" * 60)
total = _query("SELECT COUNT(*) AS c FROM fallas")[0]["c"]
print(f"\n📄 Total fallas registradas: {total}")
if total == 0:
print("\n✅ No hay fallas registradas.")
return
print("\n── Distribución por tipo de error ──")
for r in _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC"):
pct = r["c"] / total * 100
print(f" {r['error']:25s} {r['c']:>5d} ({pct:5.1f}%)")
print("\n── Top 10 RUC con más fallas ──")
for i, r in enumerate(_query("SELECT COUNT(*) AS c, ruc FROM fallas GROUP BY ruc ORDER BY c DESC LIMIT 10"), 1):
print(f" {i:2d}. {r['ruc']} ({r['c']} fallas)")
print("\n── Distribución por intentos ──")
for r in _query("SELECT intentos, COUNT(*) AS c FROM fallas GROUP BY intentos ORDER BY intentos"):
print(f" {r['intentos']:2d} intento(s) → {r['c']:>5d} documento(s)")
print("\n── Fallas por día ──")
for r in _query("SELECT DATE(fecha_ultimo) AS dia, COUNT(*) AS c FROM fallas GROUP BY dia ORDER BY dia"):
print(f" {r['dia']} → {r['c']} falla(s)")
# ============================================================================
def _color_error(error):
return COLORS.get(error, "#95a5a6")
# ============================================================================
def grafico_barras_errores():
data = _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC")
if not data:
return None
labels = [r["error"] for r in data]
values = [r["c"] for r in data]
colors = [_color_error(l) for l in labels]
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.barh(labels, values, color=colors, edgecolor="white", height=0.6)
for b, v in zip(bars, values):
ax.text(b.get_width() + max(values) * 0.01, b.get_y() + b.get_height() / 2,
str(v), va="center", fontsize=11, fontweight="bold")
ax.set_xlabel("Cantidad de fallas")
ax.set_title("Distribución de fallas por tipo de error", fontsize=14, fontweight="bold")
ax.margins(x=0.15)
ax.spines[["top", "right"]].set_visible(False)
fig.tight_layout()
path = REPORTES_DIR / "barras_errores.png"
fig.savefig(path, dpi=150)
plt.close(fig)
return path
# ============================================================================
def grafico_barras_ruc():
data = _query("SELECT COUNT(*) AS c, ruc FROM fallas GROUP BY ruc ORDER BY c DESC LIMIT 10")
if not data:
return None
data.reverse()
labels = [r["ruc"] for r in data]
values = [r["c"] for r in data]
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.barh(labels, values, color="#e67e22", edgecolor="white", height=0.6)
for b, v in zip(bars, values):
ax.text(b.get_width() + 0.3, b.get_y() + b.get_height() / 2,
str(v), va="center", fontsize=10, fontweight="bold")
ax.set_xlabel("Cantidad de fallas")
ax.set_title("Top 10 RUC con más fallas", fontsize=14, fontweight="bold")
ax.margins(x=0.15)
ax.spines[["top", "right"]].set_visible(False)
fig.tight_layout()
path = REPORTES_DIR / "barras_ruc.png"
fig.savefig(path, dpi=150)
plt.close(fig)
return path
# ============================================================================
def grafico_torta():
data = _query("SELECT COUNT(*) AS c, error FROM fallas GROUP BY error ORDER BY c DESC")
if not data:
return None
labels = [r["error"] for r in data]
values = [r["c"] for r in data]
colors = [_color_error(l) for l in labels]
fig, ax = plt.subplots(figsize=(7, 7))
wedges, texts, autotexts = ax.pie(
values, labels=labels, autopct="%1.1f%%",
colors=colors, startangle=90, textprops={"fontsize": 11}
)
for at in autotexts:
at.set_fontweight("bold")
ax.set_title("Proporción de fallas por tipo", fontsize=14, fontweight="bold")
fig.tight_layout()
path = REPORTES_DIR / "torta_errores.png"
fig.savefig(path, dpi=150)
plt.close(fig)
return path
# ============================================================================
def main():
reporte_texto()
if not HAS_MPL:
print("\n⚠️ matplotlib no instalado. Ejecuta: pip3 install matplotlib")
return
paths = []
for name, fn in [("barras_errores", grafico_barras_errores),
("barras_ruc", grafico_barras_ruc),
("torta_errores", grafico_torta)]:
p = fn()
if p:
paths.append(p)
if paths:
print(f"\n📁 Gráficos guardados en: {REPORTES_DIR}")
for p in paths:
print(f" {p.name}")
print(f"\n💡 Abre con: open {REPORTES_DIR}")
# ============================================================================
if __name__ == "__main__":
main()
# ============================================================================