Files
descarga_factura_transporte/tools/previsualizar_plantillas.py
T
GeorgeandClaude Sonnet 5.5 7ae2580565 se agregan las plantillas de aceptado/rechazado y su renderizador
plantilla/ con los dos HTML de correo y ejemplos.json (6 escenarios ficticios);
plantillas.py renderiza el subconjunto de Mustache que usan (variables con
escape de HTML, secciones e invertidas), en modo estricto: un dato que falta
da error en vez de un correo con huecos. tools/previsualizar_plantillas.py
genera las vistas previas y envía correos de prueba (--enviar). En la
plantilla de rechazo el RUC, la fila Comprobante y la tabla de guías son
opcionales, para el rechazo de un correo sin comprobante.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 08:55:54 -05:00

117 lines
4.9 KiB
Python

"""
Renderiza las plantillas de correo (plantilla/plantilla-*.html) con los datos de
ejemplo de plantilla/ejemplos.json, para revisarlas en el navegador o enviarse
un correo de prueba.
Uso (desde cualquier directorio):
python tools/previsualizar_plantillas.py --listar
python tools/previsualizar_plantillas.py --abrir # todos los escenarios
python tools/previsualizar_plantillas.py --escenario aceptado_con_pago --abrir
python tools/previsualizar_plantillas.py --escenario rechazado_dos_observaciones --enviar yo@dominio.com otro@dominio.com
Los HTML se guardan en tools/previews/ (ignorada por git). Para probar otros textos,
edita plantilla/ejemplos.json: el renderizado es estricto y avisa si falta algún dato.
``--enviar`` manda el correo real con Microsoft Graph desde el buzón EMAIL del .env,
con el asunto prefijado con "[PRUEBA]". Exige indicar el escenario (o ``todos``).
En el correo enviado el logo queda vacío mientras ``logo_url`` no tenga una URL pública.
"""
import argparse
import json
import os
import sys
import webbrowser
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
sys.path.insert(0, RAIZ)
from dotenv import load_dotenv
import plantillas
RUTA_EJEMPLOS = os.path.join(plantillas.CARPETA_PLANTILLAS, "ejemplos.json")
SALIDA_POR_DEFECTO = os.path.join(os.path.dirname(os.path.abspath(__file__)), "previews")
# Solo para la vista previa local: los clientes de correo no muestran imágenes data:
LOGO_PROVISIONAL = (
"data:image/svg+xml;utf8,"
"<svg xmlns='http://www.w3.org/2000/svg' width='117' height='28'>"
"<rect width='117' height='28' fill='%23FF6900'/>"
"<text x='58' y='19' font-family='Arial' font-size='14' font-weight='bold' fill='%23282828' text-anchor='middle'>Aberio</text>"
"</svg>"
)
def cargar_ejemplos() -> dict:
with open(RUTA_EJEMPLOS, encoding="utf-8") as f:
return json.load(f)
def seleccionar(ejemplos: dict, escenario: str | None) -> dict:
if escenario in (None, "todos"):
return ejemplos
if escenario not in ejemplos:
sys.exit(f"❌ Escenario '{escenario}' no existe. Disponibles: {', '.join(ejemplos)}")
return {escenario: ejemplos[escenario]}
def renderizar(ejemplo: dict, para_navegador: bool) -> str:
contexto = dict(ejemplo["contexto"])
if para_navegador and not contexto.get("logo_url"):
contexto["logo_url"] = LOGO_PROVISIONAL
return plantillas.renderizar_plantilla(ejemplo["plantilla"], contexto)
def main() -> None:
parser = argparse.ArgumentParser(description="Vista previa y envío de prueba de las plantillas de correo.")
parser.add_argument("--listar", action="store_true", help="Lista los escenarios disponibles y termina.")
parser.add_argument("--escenario", "-e", help="Nombre del escenario, o 'todos'. Por defecto, todos (obligatorio con --enviar).")
parser.add_argument("--salida", default=SALIDA_POR_DEFECTO, help="Carpeta donde se guardan los HTML.")
parser.add_argument("--abrir", action="store_true", help="Abre cada HTML en el navegador.")
parser.add_argument("--enviar", metavar="CORREO", nargs="+", help="Envía el escenario a estos correos como prueba (uno o varios).")
args = parser.parse_args()
ejemplos = cargar_ejemplos()
if args.listar:
for nombre, e in ejemplos.items():
print(f"{nombre:32} ({e['plantilla']}) {e['contexto']['asunto']}")
return
if args.enviar and not args.escenario:
sys.exit("❌ Con --enviar indica el escenario: --escenario <nombre> (o --escenario todos).")
elegidos = seleccionar(ejemplos, args.escenario)
os.makedirs(args.salida, exist_ok=True)
for nombre, ejemplo in elegidos.items():
ruta = os.path.join(args.salida, f"{nombre}.html")
with open(ruta, "w", encoding="utf-8") as f:
f.write(renderizar(ejemplo, para_navegador=True))
print(f"📄 {os.path.relpath(ruta)}")
if args.abrir:
webbrowser.open("file://" + os.path.abspath(ruta))
if args.enviar:
import outlook_utilities
load_dotenv(dotenv_path=os.path.join(RAIZ, ".env"))
buzon = os.getenv("EMAIL", "")
if not buzon:
sys.exit("❌ EMAIL no configurado en .env")
try:
token = outlook_utilities.get_access_token()
except Exception as ex:
sys.exit(f"❌ No se pudo obtener el token de Microsoft Graph, no se envió nada:\n {ex}")
if not token:
sys.exit("❌ No se pudo obtener el token de Microsoft Graph, no se envió nada")
for nombre, ejemplo in elegidos.items():
asunto = f"[PRUEBA] {ejemplo['contexto']['asunto']}"
ok = outlook_utilities.send_email(token, buzon, args.enviar, asunto, renderizar(ejemplo, para_navegador=False))
print(f"{'✉️ Enviado' if ok else '❌ Falló'}: {asunto} → {', '.join(args.enviar)}")
if __name__ == "__main__":
main()