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>
This commit is contained in:
1 parent
d5a82ea025
commit
7ae2580565
7 files changed
+1012
No files matched your search
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
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()
|
||||
Reference in new issue
Block a user