Files
descarga_factura_transporte/database_utilities.py
T

164 lines
5.8 KiB
Python
Raw Normal View History

import os
2026-06-01 16:31:24 -05:00
import sqlite3
# ============================================================================
# CONFIGURACIÓN
# ============================================================================
# Junto al código, no relativa al directorio de trabajo: el demonio (por ejemplo como servicio de
# Windows) puede lanzarse desde cualquier carpeta y debe encontrar siempre la misma base.
DB_NAME = os.path.join(os.path.dirname(os.path.abspath(__file__)), "emails.db")
2026-06-01 16:31:24 -05:00
# ============================================================================
def init_db() -> None:
2026-06-01 16:31:24 -05:00
"""
Inicializa la base de datos SQLite y crea las tablas si no existen:
``emails`` (correos ya procesados) y ``comprobantes`` (códigos de
seguimiento y resultado de cada comprobante, ver ``seguimiento.py``).
2026-06-01 16:31:24 -05:00
"""
conn = sqlite3.connect(DB_NAME)
try:
cursor = conn.cursor()
# Crear tabla con id como llave primaria para evitar duplicados
cursor.execute('''
CREATE TABLE IF NOT EXISTS emails (
id TEXT PRIMARY KEY,
fecha TEXT,
sender TEXT,
subject TEXT,
has_attach INTEGER,
fecha_descarga TEXT
)
''')
# Un comprobante se identifica por (ruc_emisor, tipo, serie, numero). Los
# correos sin comprobante identificable llevan serie NULL y son únicos por correo.
cursor.execute('''
CREATE TABLE IF NOT EXISTS comprobantes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codigo_seguimiento TEXT UNIQUE,
email_id TEXT NOT NULL,
ruc_emisor TEXT,
tipo TEXT,
serie TEXT,
numero TEXT,
estado TEXT NOT NULL DEFAULT 'pendiente',
observaciones TEXT,
doc_entry INTEGER,
veces_recibido INTEGER NOT NULL DEFAULT 1,
notificacion_estado TEXT NOT NULL DEFAULT 'pendiente',
notificacion_error TEXT,
notificacion_fecha TEXT,
notificacion_destinatario TEXT,
intentos INTEGER NOT NULL DEFAULT 0,
creado_en TEXT NOT NULL,
actualizado_en TEXT NOT NULL
)
''')
# Bases creadas antes de agregar una columna: se añade sin tocar los datos.
columnas = {fila[1] for fila in cursor.execute("PRAGMA table_info(comprobantes)")}
for columna, definicion in (
("notificacion_destinatario", "TEXT"),
("intentos", "INTEGER NOT NULL DEFAULT 0"),
):
if columna not in columnas:
cursor.execute(f"ALTER TABLE comprobantes ADD COLUMN {columna} {definicion}")
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS ux_comprobantes_documento
ON comprobantes (ruc_emisor, tipo, serie, numero) WHERE serie IS NOT NULL
''')
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS ux_comprobantes_correo
ON comprobantes (email_id) WHERE serie IS NULL
''')
conn.commit()
finally:
conn.close()
2026-06-01 16:31:24 -05:00
# ============================================================================
def save_emails(emails_list: list[dict]) -> int:
2026-06-01 16:31:24 -05:00
"""
Guarda una lista de correos en la base de datos.
Retorna la cantidad de registros nuevos insertados.
"""
if not emails_list:
return 0
conn = sqlite3.connect(DB_NAME)
try:
cursor = conn.cursor()
from datetime import datetime
fecha_ahora = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Mapear lista de diccionarios a tuplas para executemany
# has_attach se guarda como 1 (True) o 0 (False)
data = [
(
e['id'],
e['fecha'],
e['sender'],
e['subject'],
1 if e['has_attach'] else 0,
fecha_ahora
)
for e in emails_list
]
# INSERT OR IGNORE evita errores si el ID ya existe
cursor.executemany('''
INSERT OR IGNORE INTO emails (id, fecha, sender, subject, has_attach, fecha_descarga)
VALUES (?, ?, ?, ?, ?, ?)
''', data)
conn.commit()
return cursor.rowcount
finally:
conn.close()
2026-06-01 16:31:24 -05:00
# ============================================================================
def get_existing_ids(ids_list: list[str]) -> list[str]:
2026-06-01 16:31:24 -05:00
"""
Recibe una lista de IDs y devuelve una lista con aquellos que ya existen en la BD.
"""
if not ids_list:
return []
conn = sqlite3.connect(DB_NAME)
try:
cursor = conn.cursor()
existing_ids = []
# Procesar por lotes para evitar límites de parámetros en SQLite
for i in range(0, len(ids_list), 900):
chunk = ids_list[i:i + 900]
placeholders = ','.join(['?'] * len(chunk))
query = f"SELECT id FROM emails WHERE id IN ({placeholders})"
cursor.execute(query, chunk)
existing_ids.extend([row[0] for row in cursor.fetchall()])
return existing_ids
finally:
conn.close()
2026-06-01 16:31:24 -05:00
# ============================================================================
def delete_email_by_id(email_id: str) -> bool:
2026-06-01 16:31:24 -05:00
"""
Elimina un correo de la base de datos por su ID.
Retorna True si se eliminó el registro, False si no se encontró.
"""
if not email_id:
return False
conn = sqlite3.connect(DB_NAME)
try:
cursor = conn.cursor()
cursor.execute("DELETE FROM emails WHERE id = ?", (email_id,))
conn.commit()
return cursor.rowcount > 0
finally:
conn.close()
# ============================================================================