From 877c95cead19cb72c3132e7064b2eeb486b1e771 Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 13 Feb 2025 11:03:54 +0100 Subject: [PATCH] Enviar correos con archivos adjuntos Ahoar nos permite enviar correos con archivos adjuntos, Pero a la hora de recibirlos no podemos recuperar esos archivos, eso sera la ampliacion d la sigueinte version. --- __pycache__/controlador.cpython-313.pyc | Bin 3001 -> 3032 bytes __pycache__/modelo.cpython-313.pyc | Bin 7683 -> 8586 bytes __pycache__/vista.cpython-313.pyc | Bin 16152 -> 17286 bytes controlador.py | 10 +++++----- modelo.py | 25 +++++++++++++++++++----- vista.py | 25 ++++++++++++++++++++---- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/__pycache__/controlador.cpython-313.pyc b/__pycache__/controlador.cpython-313.pyc index 2efa4c867a5920bf351a30b6f670afad8d2c7b4f..b21b685e91f3c3b047f35d0ba74d0bd76272828c 100644 GIT binary patch delta 578 zcmYk2zi-n}5Xb#oKifDz`YTPSMT3D-;ie@W45+1Qh1if9DypCiku2l9qzR2(-Af`i zCR8#23hxgQ)J{AyKv}J3CTg=Uuw+9X@(@_to9IJIv1Vk_70=W5 z$hsQQGJRr*lL`Btqcjv(Ex9VpNcTt!put@Y=41d_%%pu5^_l_rVGO`xxGa;dEMdQk%EGd80}682R-q&(C`*iUE(-^N zwD%(b7Qty0Q1M2unJ_1Fjuzs#euXrIWt1A9*S|jcQNR7|#Lf%#*vPyxkIa`D$V-tw z{Z1-eV{hsueI=DT*y^a_r*WQ8q0b;M&g?#^%9F5wuq139urE+V;3r@Ofj7fh#G-_) zK?d_C;vAxds7Z`Z?z514510##AM}~*DA(k^l>}r_w~RV#Gx_eOsG8TvU9o2#=I60k zLDUf|;*Z(3(C>LFRtQ~+wKOfprcO6VTp+X+YYHuiJG0GN(oM91H3T*zu^Joe`_a9A MmtBUo_%z%63v4uhQ~&?~ diff --git a/__pycache__/modelo.cpython-313.pyc b/__pycache__/modelo.cpython-313.pyc index 9a44d129e59364e5d2bc7a096d1535813a4d5b54..20fd2c9ed4ecb5bddc8635edbdba8f7e7755a9a8 100644 GIT binary patch delta 2368 zcmZuzZ%iE55r1!Q@BcsG;MhCvxHTq*b1(*Tm;__T1skv}LUQYCNe%^X?iO=|+a+(; z)Nw0QB~p|`Q4{*A*pbo%^^@DGsw=n3he%PBwvxY8!8Hy79p(RJk18y-Z?%rb!zJ5sA_6i zYWp<3WRx{#B3)K3&8k$j(yqcTx@wc17uUWi1`d)ORT`3Hk@BZ$KaJjao&K4o`b)#J z1rw~yKBuiz^x0}fV`bHxEiI@PUvLf4G+%dpaxe*|Y~OrEFV7jemK9kF7_0+fFG44P zDFVnaSem~p_R`c{A=;bGvAf8J*5j8TQ;&Twhy%(>w$_6eprru&;PT}vLFkp z24k9GdTN_F+kN5gqYrV}bCk+Ap79ji!NX|QvE9Qz3`#V`w}OR+?|Q+qd?eHX0nUXE z(}!=o6}l|!Z2~>;Y82HSuSATQwZYt^02td)^bjrZ#pqtx?B~&c3VRNsbRR-L!XbnK zgdD)x@oY1Hw`sI*961vRcrR=c;S_@7<}^^*@NU>oA~(+)Vp9JMl20Lg&-Wtm%rF2R z_=5r7O~;1m%#GJ#KcTI7ANAn}0{7jqM4oDSsYNUX>q~<*LU;3xczbujt+={?HRD!9 z=h>pTo##o#gU^BxAv&D#FXEYpJ&L!Uql0)nFl172#mMBxgy`NP9;Y+%&Ad^J(N?Ys zv1xBzw!5}k76_Sw+bt$)3{vnazPe?B=)NM}fHM@o5~#1}{(?WBMw>2QKEuC=bVOuQ zlxvbC-@%W^n?nVEeLtD-RbRY82`ZuQyT*m4ShDcIvj`aw>bpQh5Nvbb|Js3Ch0nJV zQ-38S2(cIIA(8xk*gQjf__=tR_VUZ|Hg}*9$oKPUZ_)!M1Ni}5;-ALbX%3ca%%K`H zCeFC?k3vXoVZ1gNj?NKOUxl6#Rw4xwAxg9$g0Esu=p=hc52@K*nyyy0qh~KQjyWYSYx+t>EgP~bUlRGWlyPNP zfIlxaulm3j+oc;n-mNOkdv*+Yo?VN~jT6BzZE;z(7Hp4pv0_>^f>=qN}R$ncB(oX zvO{Ifv?{u4sjOl+ZPNBaY^J)X*^MeIEmT$vb51QUUeGOYEjX`1)1AX04fYg2n1~}2lWpGw19Jo-pC8UHa~zY_!+q?KE&Ti%&dOikzG%0 zgi_m~=o_bBJ^jmz8*<HV*78$_TNdQ zubyAGHWNM9y>~KQ*TUB?-_DF*6}P3-mehSq>b}gpG$iYntw|0a%!FuUmwOeXv{V}7Jlfyp7)&3&H9L#~@W6zN0m$sv9{(#rpn zTuokf4i_?n8M+BA3C@+J;lUR9TWvD^HGjXYvq=U9ydmPq$2SC%_`lmm=8hnVg~cu+ z;PE>t$c`h`g3yY99gejl?7p3lnngH=Py(<+rUfNas%qAPQD!eAf04hEN+)C_9f$Bg z21AI#&-^L>Fg5eo_Qa_jvd{g( zW;Y=PL=Zhdkt#?AK_!$w0C4~*;syr}X$2RG$^k7G4&~N^Lk@7{y^Ygen8R=0ym>S8 zX5M@A@wr=L(Y;6{NTA&*em%bx--ssZ@<#sZp&$z?L?tS{6k?$y=^?6|BC3BxKwCms zc%Bvl8{d!fI86i_O0xGZAEq;s8cPv=mma5~?Vss)G(T1yoh{osvuAJUtA;sy#ndO5 zv8vBb+Er#a7Bj3_n-%$&;z`=cABw*mOTrOupk|nJ70c8M5=-Hk3Zfg)1F$840*$5l zuhJmRZu*YWEFbWFaZN#+Cx)(EUD4UHRS;PpPI#LK4HG6R_S z0MGJH>yd|OH&^8|G`qbamwllkZtLxRgztuuG{uAAa!f((6E9evTj3s>=9}SXX@2{s z@OwgU3@pH_aWwS2lJLr`6UH_L;MJ}-pP(=Ct>#`h?8oN6gl7iOdJu68F@!jdH~}b~ zFT~kN9GwF2`>ijmi#R%uxPb87ybMwyvVZL>sJ)7qLa2xmqKtSM@d`pgJiX4VFtj58 z?^O=K(x7qM7POm{CEc{O>pI_TJ3>p_KeW9=smSj&=lSaimJnw`VCc1xMsG^|PBJ@K zmQ)d+ep*r`uaB!fuP>@H_Nu=uM1jMi=_^ZopgouHseyyM9KmOUrVzf=k>$#`U%kos3{~1Wh3IQ>LQ~Yi+3wiTcZYaAO$~I|Qn(9U4=m@@{w<|0hjUk$5~vL7sjqF@U-C@RzcyuYu}*JzEL z5p$$Q3sHW*^Y8#h(Tl7rl&q)GeLE67cw_8c-}0hnn0z`lBF0&Of0~LP!W?x)(|T(^ z+E^ZJ40W>RI9hdq`C!0%(%aj1v99%$vwDxxm zMpr;*ABKp%PwG;NpX-_)s!Oyl@_Quy(TjCL+aq;RP+||lU3Hm6VxL^Reetuk2g=a5 z`G*~&zlBdfN_6p3W=;C{QFD^J8Ku7$HvAwpDUq#-e&CK2oXm+kQF$^Y?!+m|Dc@v{ zk7qwmy@@`{%bLS1)5iY`D;qjiT9S^R3IWaQ-RnqULCEQ|v9sB^^ zS22N#7XFRWP7QufDGoDCC3f?`j@?4ZTjrH_U~I{nTUpf4vJYVbE6zU3FL&n>nLkL= PKhh8%R1nt)Yni?((h*4C-rCe}&Qq-nzG$IKqx)=m2dZ6L%dZLRjl&bcp% zq1~+H_s%=_+;i_e=XcL}=f8gXo22xgB_&P)e)rygGoD(vQ0gH=3r+sH-BxN9g|HAd z@3HMJqD5RLd+gK>I9Uh@x2wSRzur**p2ZBJIaU$7np%{`Zx;dSm(4CWtrpa+- zOg*8yv?x`RNhOw6PNa3`kpzedIjP1aqOa~(DOJ>dHI)V%dEv)il3utz>8cW_J6ujH z;%=!T+eiLhL{_mj#|Yplhe+J)HOCHHIj-$ycO5mLE^$VJYd~=ePbFEX2M1RnR3Uf~ zS^%z?D4*vyWH;*Tvfpz)N7~v^?f}rt0HP@B9nr4QVNIdh=!9}Sks6&$C^RN(qtS6W zosGLB%g#2il#1wHc<9Aba&#hcM5S>hO&)D^Q6I`~_ES$?F^_r;zPGZ!d0HAV0lH}{Myo*>qP2imEYt`( zT45Hbk8SeylOt@#TRq1e#6g=W2Dp2XeM{V~Wm0LCj-wKj3(8aCfC4$7iTez{Blc$$ zI;B!f32g?rV%9CwRGy-1QG*PSDvU&ILPa4tU&B|-IqmL7L=NbyV~Q4)Xl6Ugf|w>SG)&3mRf&d)Mj z0b_W@Y><>w69aXt0=0z4b7u9hmVpDR;yPn>sf*(9t&M17GWfh2WFFIu3nYGrWKH?i zkK`_$te4F986{yn(~4GiFTVJ!*e_3xC1gs+&;ie)(+JFCsMw3Jg&h_Hq?;{>HE^i6 z#3tiV``GO&U-om+xpXj7&XV=l|G`X3S(~rNQ4_8Wo5Pl%g*{?|@)kHSBGlm-yHnOQpFwXlB1$^AFH_lw5yiTslW86rEJ#$H)@@NWnVC%KHZ7fqPCRWe@01{T1$r7?HvmA$7Epcx z!NDM=+7O{ghg>heDH(xg15Bn$5)3g5vJ!6hplWKTBv*Sp5jsfe*>nd(k_ zGYmB=_=dtLE>*7m$Oe1o6lk>)Q}*Xgvt;};o8YNFH}k^GrM0iOUT)283gr%t<_{iQ zguhBU@48{H$lJY(cJKLPIegZ^1b^Md@t4OhrC*=DJe%tt&W$E=sp&=EiFwOM_UapU zSKeN|Xs^!O#YMY#NzUPO9qW>s;c}IAc}@Qjn7@AQ-Y^N9f+R(T$vW5&{^Umm;V|x* z|At8bt1kS8g(O~g{tKH29m{gz$#Q{*oXh4Z6T8z^$X<(P+aX;8$+@ zU(|UXtTem=CxuVkwRv~j2ky39U}(`jJa7L~@nQET4bmIyUVUnQ$49RE8?|*84_!EP zDV=ZXS#0Y0u(o$Tbi-Aaclj4x{=BPk(bbr1>$}#Q>l@6qgmbRO4_rf^erddAfa`Zb z7rWfzCGTcWO0N*p!48%)RhR-6=5G%9T=XT}yBXT8ZiRp(rsz3P(DMiZcDA{?94k3} z1p%W4p9O;~+uX9A*Jo7g-un;!z->TR+6CrlVa54@(Zzw$kL^#=k?iG`tcm=94YxOu z@3Yxz&?e%?WT-w3ooQ5k(E$~j?Mx`Z@Rwn5Q z6m2n}QCcwy^AEWuk%EtrG(SdTN1IWRkFh5@{N&Z_Oh*ya9to`A zZ4qeq6VOUFii^{C(ZU6hi$LK@#;B4qy51^vEUl!JLeF~-Cz&{-=r2Ilu(;y=XO*3u zA}&jK#h6+^J62be9ek-Wo#fz~DKlYz%bNj;850VF%^V87o~ zN7~uHH?=>^y*QzSMp79C%IX8Yu7kba%&rG3nY_7z{EQvndhj;YHaTE&$-GQ(f zp&fzW7i2|9TBF+|5iBy1i0+KAGdF(NpWiLm8z9>n=qof`9EG>*#GL_tjYI?rklyK1%MDJFHKUdmbkDHtj=lN>3zGa50RVfrZ?NFBW=UGL7J%Q~EoE8wh-{f1tFo0o}fJQjKMjN-w_;?F|QheFRE1?i?oD7z^X|I2c~Oh(8(fj_vP{{pfmWyJsh delta 2559 zcmZ`)Yitx%6yCd!+3og0X)Cm)k7)~*v84~XwQOmZK4_I!X(>gJlFfEI-LCEK)HAyU zNq~q@9x>5+L5zY}BaeotF&hnnsKJ;ROf(ubaZo|@Ph$)OLrnam=iF&2XxwDKIdkrL z+lFYQkIBQ@1+!QYtYTU` zfh*fmZ0WY=TDl#zR&usjASuKYX^O-S#WsGha6D1QqT|;h5@d!ll+46t_M4NX@s&xN z5MN1>SqA?gX=TY=^36anBz4C~Alm3TeE=?e}Kk%M@)?vBP|P zKpH^~K{i1d!lV^MIC4apqJo^b=sv`}6(m<8=r#mdmW}1X`raO01-*Aj9Sui&*GJWr z5FS-~+w>rWV;Y3DUL8CMNKx!W_kbgjfkQ#%vsDaf4bp#%QHiStA zUHH%$f(#o--N3J>53(M9Y0mcz@EB?^pzz#Gt0EA^|5qT`g1URdtiUib-<=^zTgm$W z;)zfq==~e}PrPOk@1xAS@#YlgB!12O%sxMb;-?Z&aj_HmBxmqXvWt>M3l`9OIlq}* z{vdV3u=e#sE(#kV4{_2y-2t+qmOwsl&S~AFq8Lv{xN-c@&GXO68`MZRrbc5b)=Y#Q zAcJItTPOLmacIen0^kp70IH_LGTxY*$J+Sgx$UfrPvmw< zqOa>XQwkFE7JSLDxm3Kr^DbI93BJ-Pfyy{(Gmj>yO0}Azbv!VYbm!b2Jsv>5$&1ZY zNl!9$T%A zM=LuDlHmw##)dP;aOg1zkHAq>z>5SPZd5KvrRfI85X3f|Rpn-Hk8y8Rfph@%I_KM} zxZ_(_8%~XhB{_+#>X)tTRsMEOY5NIW7nc&R*O}QXSuY#R9jBJwa~JDn(V%JK&D=)R z_VEHwG1GaoCu_;;=wsONVn<**iBi8htj1Kc2NOaQj^g-@iQVq?RG7OR;qQ40*$av9 zJWk|d;-}hb#@^uRbq{o$MIS{19Ut%}`KMql{K3KSa6hO~G24wfS|3glb@s}>MNU>R zis2oUb)ucdIo4C-eqAXq`rOt6ligJ3-Y-6$9#xJb}RaGBr=!8Zhl`L>q)96FErrFW4;;KaYaf%BF{ zDRP#h=yrLMqn+KJ$FH<3>J{c>q__=dY$%K$KrnBI_!5btIw+7RhD6fG1fLU#4A)7z so7HeN(6NS9AKc)bt@GsSuPoI+T9m7n^sAQSKkZF6wu#-gh==g{2Ps2f7ytkO diff --git a/controlador.py b/controlador.py index a4bb3cb..bd5b751 100644 --- a/controlador.py +++ b/controlador.py @@ -23,13 +23,13 @@ class CorreoControlador: def obtener_correos(self): return self.modelo.obtener_correos() - def enviar_correo(self, destinatario, asunto, mensaje): - hilo = threading.Thread(target=self._enviar_correo(destinatario,asunto,mensaje)) - hilo.start(); + def enviar_correo(self, destinatario, asunto, mensaje, archivos_adjuntos=[]): + hilo = threading.Thread(target=self._enviar_correo, args=(destinatario, asunto, mensaje, archivos_adjuntos)) + hilo.start() - def _enviar_correo(self, destinatario, asunto, mensaje): + def _enviar_correo(self, destinatario, asunto, mensaje, archivos_adjuntos): self.vista.actualizar_footer("📨 Enviando correo...") - resultado, mensaje_respuesta = CorreoModelo.enviar_correo(destinatario, asunto, mensaje) + resultado, mensaje_respuesta = CorreoModelo.enviar_correo(destinatario, asunto, mensaje, archivos_adjuntos) if resultado: messagebox.showinfo("Éxito", mensaje_respuesta) else: diff --git a/modelo.py b/modelo.py index 61c598d..7425b2f 100644 --- a/modelo.py +++ b/modelo.py @@ -5,8 +5,12 @@ from email.utils import parsedate_to_datetime import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email import encoders from datetime import datetime import re +import os + class CorreoModelo: POP3_SERVER = "s1.ieslamar.org" #192.168.120.103 @@ -115,7 +119,7 @@ class CorreoModelo: return False @staticmethod - def enviar_correo(destinatario, asunto, mensaje): + def enviar_correo(destinatario, asunto, mensaje, archivos_adjuntos=[]): try: if not CorreoModelo.validar_correo(destinatario): return False, "Dirección de correo inválida" @@ -128,11 +132,22 @@ class CorreoModelo: msg["Subject"] = asunto msg["Date"] = fecha_envio - mensaje_completo = f""" - {mensaje} - """ + mensaje_completo = f"{mensaje}" msg.attach(MIMEText(mensaje_completo, "plain")) + # Agregar archivos adjuntos + for archivo in archivos_adjuntos: + if os.path.exists(archivo): + with open(archivo, "rb") as adjunto: + part = MIMEBase("application", "octet-stream") + part.set_payload(adjunto.read()) + encoders.encode_base64(part) + part.add_header( + "Content-Disposition", + f"attachment; filename={os.path.basename(archivo)}", + ) + msg.attach(part) + server = smtplib.SMTP(CorreoModelo.SMTP_SERVER, CorreoModelo.SMTP_PORT) server.ehlo() server.login(CorreoModelo.EMAIL_USER, CorreoModelo.EMAIL_PASS) @@ -143,7 +158,7 @@ class CorreoModelo: except Exception as e: return False, f"Error enviando correo: {e}" - + @staticmethod def validar_correo(destinatario): patron = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" diff --git a/vista.py b/vista.py index b7a834c..08f2fb1 100644 --- a/vista.py +++ b/vista.py @@ -1,5 +1,5 @@ import tkinter as tk -from tkinter import ttk, messagebox, scrolledtext +from tkinter import ttk, messagebox, scrolledtext, filedialog from controlador import CorreoControlador import threading import time @@ -109,7 +109,6 @@ class CorreoVista: btn_cambiar = ttk.Button(self.frame_f2, text="🔄 Cambiar", command=self.cambiar_credenciales) btn_cambiar.pack(pady=10) - def crear_frame_f3(self): label_envio = tk.Label(self.frame_f3, text="📤 Enviar Correo", font=("Arial", 16), bg="#f4f4f4") label_envio.pack(pady=20) @@ -129,9 +128,26 @@ class CorreoVista: self.text_mensaje = scrolledtext.ScrolledText(self.frame_f3, wrap=tk.WORD, height=10, font=("Arial", 12)) self.text_mensaje.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) + # Botón para seleccionar archivos + btn_adjuntar = ttk.Button(self.frame_f3, text="📎 Adjuntar Archivos", command=self.seleccionar_archivos) + btn_adjuntar.pack(pady=5) + + # Lista de archivos seleccionados + self.label_archivos = tk.Label(self.frame_f3, text="No hay archivos adjuntos", font=("Arial", 10), bg="#f4f4f4") + self.label_archivos.pack(pady=5) + btn_enviar = ttk.Button(self.frame_f3, text="📨 Enviar", command=self.enviar_correo) btn_enviar.pack(pady=10) - + + def seleccionar_archivos(self): + archivos = filedialog.askopenfilenames(title="Seleccionar archivos adjuntos") + if archivos: + self.archivos_adjuntos = list(archivos) + archivos_texto = "\n".join(self.archivos_adjuntos) + self.label_archivos.config(text=f"Archivos adjuntos:\n{archivos_texto}") + else: + self.label_archivos.config(text="No hay archivos adjuntos") + def enviar_correo(self): destinatario = self.entry_destinatario.get().strip() asunto = self.entry_asunto.get().strip() @@ -141,7 +157,8 @@ class CorreoVista: messagebox.showwarning("⚠️ Advertencia", "Todos los campos son obligatorios.") return - self.controlador.enviar_correo(destinatario, asunto, mensaje) + self.controlador.enviar_correo(destinatario, asunto, mensaje, self.archivos_adjuntos) + def mostrar_correo(self): seleccionado = self.tree.selection()