""" mailer.py — Envío de email con PDF adjunto via SMTP """ import smtplib import ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication import sqlite3, os DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'marine_maintenance.db') def get_email_config(company_id): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row cfg = None if company_id: cfg = conn.execute("SELECT * FROM email_config WHERE company_id=?", (company_id,)).fetchone() if not cfg: cfg = conn.execute("SELECT * FROM email_config WHERE company_id IS NULL").fetchone() if not cfg: cfg = conn.execute("SELECT * FROM email_config LIMIT 1").fetchone() conn.close() return dict(cfg) if cfg else None def _smtp_send(host, port, user, pwd, use_tls, from_addr, to_addrs, msg_str): """Envío SMTP puro — abre conexión fresca cada vez.""" if port == 465: with smtplib.SMTP_SSL(host, port, timeout=30) as s: s.login(user, pwd) s.sendmail(from_addr, to_addrs, msg_str) else: with smtplib.SMTP(host, port, timeout=30) as s: s.ehlo() if use_tls: s.starttls() s.ehlo() s.login(user, pwd) s.sendmail(from_addr, to_addrs, msg_str) def send_report_email(to_email, to_name, subject, body_html, pdf_bytes, pdf_filename, company_id): cfg = get_email_config(company_id) if not cfg or not cfg.get('smtp_host'): return False, "Email no configurado. Ve a Configuración → Email." host = cfg['smtp_host'] port = int(cfg.get('smtp_port', 587)) user = cfg['smtp_user'] pwd = cfg['smtp_password'] from_addr = cfg.get('from_email') or user from_name = cfg.get('from_name', '') use_tls = bool(cfg.get('use_tls', 1)) if not host or not user or not pwd: return False, "Configuración SMTP incompleta." # ── Email al cliente (con PDF adjunto) ──────────────────────────────────── msg = MIMEMultipart('mixed') msg['From'] = f"{from_name} <{from_addr}>" if from_name else from_addr msg['To'] = f"{to_name} <{to_email}>" if to_name else to_email msg['Subject'] = subject body_part = MIMEMultipart('alternative') body_part.attach(MIMEText(body_html, 'html', 'utf-8')) msg.attach(body_part) if pdf_bytes: pdf_part = MIMEApplication(pdf_bytes, _subtype='pdf') pdf_part.add_header('Content-Disposition', 'attachment', filename=pdf_filename) msg.attach(pdf_part) try: _smtp_send(host, port, user, pwd, use_tls, from_addr, [to_email], msg.as_string()) except smtplib.SMTPAuthenticationError as e: return False, f"Error de autenticación: {e}" except smtplib.SMTPConnectError as e: return False, f"No se pudo conectar a {host}:{port}: {e}" except smtplib.SMTPServerDisconnected as e: return False, f"Servidor cerró la conexión: {e}" except smtplib.SMTPException as e: return False, f"Error SMTP: {e}" except OSError as e: return False, f"Error de red: {e}" except Exception as e: return False, f"{type(e).__name__}: {e}" # ── Copia al remitente CON PDF adjunto ──────────────────────────────────── try: msg_copy = MIMEMultipart('mixed') msg_copy['From'] = f"{from_name} <{from_addr}>" if from_name else from_addr msg_copy['To'] = from_addr msg_copy['Subject'] = f"[COPIA] {subject}" body_copy = MIMEMultipart('alternative') body_copy.attach(MIMEText(body_html, 'html', 'utf-8')) msg_copy.attach(body_copy) if pdf_bytes: pdf_copy = MIMEApplication(pdf_bytes, _subtype='pdf') pdf_copy.add_header('Content-Disposition', 'attachment', filename=pdf_filename) msg_copy.attach(pdf_copy) _smtp_send(host, port, user, pwd, use_tls, from_addr, [from_addr], msg_copy.as_string()) except Exception: pass # copia falla silenciosamente return True, '' def build_email_body(order, vessel, company_name, app_url=''): order_num = order.get('order_number', '') scope = order.get('scope') or order.get('description', '')[:100] status = order.get('status', '').replace('_', ' ').title() total = float(order.get('total_labor_cost') or 0) + float(order.get('total_parts_cost') or 0) vessel_n = vessel.get('name', '') date_end = order.get('end_date') or order.get('start_date') or '' pdf_link = f"{app_url}/work-orders/{order.get('id')}/pdf" if app_url else '' return f"""
{company_name.upper()}
REPORTE DE MANTENIMIENTO
Orden de Trabajo
{order_num}
Embarcación {vessel_n}
Trabajo {scope}
Fecha {date_end}
Estado {status}
Total ${total:.2f}

Adjunto encontrará el reporte completo en PDF con todos los detalles del trabajo realizado.

{'
Ver Reporte Online
' if pdf_link else ''}
{company_name} · Reporte generado automáticamente por Marine Maintenance Pro
""" def send_wo_notification(company_id, to_emails, subject, body_html, pdf_path=None, pdf_name=None): cfg = get_email_config(company_id) if not cfg or not cfg.get('smtp_host'): return False, "Sin configuración SMTP" if isinstance(to_emails, str): to_emails = [e.strip() for e in to_emails.replace(',',';').split(';') if e.strip()] host = cfg['smtp_host'] port = int(cfg.get('smtp_port', 587)) user = cfg['smtp_user'] pwd = cfg['smtp_password'] from_addr = cfg.get('from_email') or user from_name = cfg.get('from_name', '') use_tls = bool(cfg.get('use_tls', 1)) msg = MIMEMultipart() msg['From'] = f"{from_name} <{from_addr}>" if from_name else from_addr msg['To'] = ', '.join(to_emails) msg['Subject'] = subject msg.attach(MIMEText(body_html, 'html', 'utf-8')) if pdf_path and os.path.exists(pdf_path): with open(pdf_path, 'rb') as f: part = MIMEApplication(f.read(), _subtype='pdf') part.add_header('Content-Disposition', 'attachment', filename=pdf_name or os.path.basename(pdf_path)) msg.attach(part) try: _smtp_send(host, port, user, pwd, use_tls, from_addr, to_emails + [from_addr], msg.as_string()) return True, "OK" except smtplib.SMTPAuthenticationError as e: return False, f"Error de autenticación: {e}" except smtplib.SMTPConnectError as e: return False, f"No se pudo conectar a {host}:{port}: {e}" except smtplib.SMTPServerDisconnected as e: return False, f"Servidor cerró la conexión: {e}" except OSError as e: return False, f"Error de red: {e}" except Exception as e: return False, f"{type(e).__name__}: {e}"