From 0884f98a50c307f0673fafb08c1051109c169fec Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 11 Feb 2025 12:29:56 +0100 Subject: [PATCH] Version estable Ahora envia correos desde la interfaz asignando un destinatario, un asunto y un cuerpo de correo. Tambien recibe correos. Descarga y envia correos por hilos independientes. Hay que tener en cuenta que no son demonios. --- Main2.py | 46 ---------- Main3.py | 117 ------------------------ __pycache__/controlador.cpython-313.pyc | Bin 1462 -> 3001 bytes __pycache__/modelo.cpython-313.pyc | Bin 5707 -> 7683 bytes __pycache__/vista.cpython-313.pyc | Bin 10288 -> 16152 bytes controlador.py | 20 ++++ modelo.py | 62 ++++++++++--- vista.py | 77 +++++++++++++++- 8 files changed, 146 insertions(+), 176 deletions(-) delete mode 100644 Main2.py delete mode 100644 Main3.py diff --git a/Main2.py b/Main2.py deleted file mode 100644 index f2aae06..0000000 --- a/Main2.py +++ /dev/null @@ -1,46 +0,0 @@ -import smtplib -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from datetime import datetime - -# Configuración del servidor SMTP (Sin SSL) -SMTP_SERVER = "192.168.120.103" -SMTP_PORT = 25 # También puedes probar 587 si 25 no funciona -EMAIL_USER = "pruebas@psp.ieslamar.org" -EMAIL_PASS = "1234" - -def enviar_correo(destinatario, asunto, mensaje): - try: - # Obtener la fecha y hora actual - fecha_envio = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Crear mensaje con la fecha incluida - msg = MIMEMultipart() - msg["From"] = EMAIL_USER - msg["To"] = destinatario - msg["Subject"] = asunto - msg["Date"] = fecha_envio # Agregar la fecha en la cabecera del correo - - # Formato del mensaje con la fecha en el cuerpo - mensaje_completo = f""" - asdasdsad - - {mensaje} - """ - - msg.attach(MIMEText(mensaje_completo, "plain")) # Mensaje en texto plano - - # Conectar al servidor SMTP SIN SSL - server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) - server.ehlo() - server.login(EMAIL_USER, EMAIL_PASS) # Iniciar sesión - server.sendmail(EMAIL_USER, destinatario, msg.as_string()) - server.quit() - - print(f"Correo enviado a {destinatario} el {fecha_envio}") - - except Exception as e: - print(f"Error enviando correo: {e}") - -# Uso del script -enviar_correo("kevin@psp.ieslamar.org", "Prueba de Correo", "Este es un mensaje de prueba sin SSL.") diff --git a/Main3.py b/Main3.py deleted file mode 100644 index 0bf156b..0000000 --- a/Main3.py +++ /dev/null @@ -1,117 +0,0 @@ -import poplib -import email -import pymongo -from email.utils import parsedate_to_datetime - -# Configuración del servidor POP3 (Sin SSL) -POP3_SERVER = "192.168.120.103" -POP3_PORT = 110 # Puerto POP3 estándar sin SSL -EMAIL_USER = "kevin@psp.ieslamar.org" -EMAIL_PASS = "1234" - -# Configuración de la base de datos MongoDB -MONGO_CLIENT = "mongodb://localhost:27017/" -DB_NAME = "correo_db" -COLLECTION_NAME = "correos" - -# Conectar a MongoDB -client = pymongo.MongoClient(MONGO_CLIENT) -db = client[DB_NAME] -collection = db[COLLECTION_NAME] - -def correo_existe(remitente, asunto, fecha): - """ Verifica si un correo ya existe en la base de datos. """ - return collection.find_one({"remitente": remitente, "asunto": asunto, "fecha": fecha}) is not None - -def guardar_correo(remitente, asunto, fecha, cuerpo): - """ Guarda un correo en la base de datos si no existe. """ - if correo_existe(remitente, asunto, fecha): - print("⚠️ Correo ya guardado, se omite.") - return - - correo = { - "remitente": remitente, - "asunto": asunto, - "fecha": fecha, - "cuerpo": cuerpo - } - collection.insert_one(correo) - print("✅ Correo guardado en la base de datos.") - -def descargar_correos(): - """ Descarga correos desde el servidor y solo guarda los nuevos. """ - try: - print("📡 Conectando al servidor POP3 para descargar correos...\n") - mail = poplib.POP3(POP3_SERVER, POP3_PORT) - mail.user(EMAIL_USER) - mail.pass_(EMAIL_PASS) - - num_mensajes = len(mail.list()[1]) - print(f"📩 Se encontraron {num_mensajes} correos en la bandeja de entrada.\n") - - for i in range(1, num_mensajes + 1): - response, lines, octets = mail.retr(i) - raw_email = b"\n".join(lines) - msg = email.message_from_bytes(raw_email) - - remitente = msg["From"] - asunto = msg["Subject"] - fecha = msg["Date"] - - if fecha: - try: - fecha = parsedate_to_datetime(fecha).strftime("%Y-%m-%d %H:%M:%S") - except Exception: - pass - - cuerpo = "" - if msg.is_multipart(): - for part in msg.walk(): - if part.get_content_type() == "text/plain": - cuerpo = part.get_payload(decode=True).decode(errors="ignore") - break - else: - cuerpo = msg.get_payload(decode=True).decode(errors="ignore") - - guardar_correo(remitente, asunto, fecha, cuerpo.strip()) - - mail.quit() - print("✅ Descarga de correos completada.\n") - - except Exception as e: - print(f"❌ Error al descargar correos: {e}") - -def mostrar_correos(): - """ Muestra todos los correos almacenados en MongoDB. """ - print("📂 Mostrando correos almacenados en la base de datos...\n") - correos = collection.find() - - for correo in correos: - print(f"📅 Fecha: {correo['fecha']}") - print(f"🔹 Remitente: {correo['remitente']}") - print(f"📌 Asunto: {correo['asunto']}") - print(f"📝 Mensaje:\n{correo['cuerpo']}") - print("-" * 40) - -def menu(): - """ Menú interactivo para ejecutar las opciones del programa. """ - while True: - print("\n📬 MENÚ:") - print("1. Descargar correos nuevos") - print("2. Mostrar correos almacenados") - print("3. Salir") - - opcion = input("Seleccione una opción: ") - - if opcion == "1": - descargar_correos() - elif opcion == "2": - mostrar_correos() - elif opcion == "3": - print("👋 Saliendo...") - break - else: - print("❌ Opción no válida, intente de nuevo.") - -# Ejecutar el menú interactivo -menu() diff --git a/__pycache__/controlador.cpython-313.pyc b/__pycache__/controlador.cpython-313.pyc index 8fecb21b0fe0690d6eb7c4971bac170731ac0a6c..2efa4c867a5920bf351a30b6f670afad8d2c7b4f 100644 GIT binary patch literal 3001 zcmbVO&2JM|5Pxg$+Ut+RNoqn!5m`cz>oi#9BUKwBXd0qcq!`JIRU|BJ7iZ&aF}rl$ zI#9T9s=@)G3eomZsaz@*@elM^sTYoM5@fUuy|fo@C=sgEQ)hPl6`QI`N6Gko%)F18 z-~8Tg_w+;=%J(N8m!3Cg-6qy;xVYXcbeIeWn<+t`m@H8|Hx#%23Pw&*+fTBvvG`xY1o zDuaM3Ku`?~GZ57vYKiuOkSd`Lt0B~~8b%#aWr&s{>8RW9Z#ZjP4%lUV!G;%jMOJLA zPutK*gZRHais}Ko)SR2dK1rUHn`0&RNXVuGKn`x`s&|n`f-7>PyvWH)ac7ECg*-Qa z7p@q~rd2p^8E3#;HVSjR2&Q9$X%{%0DELRyW|wrQP;4DAbL~r4)-=;H9ZhqGyDpGv z%!P0_9z%7TZ4Qp!w^qcvv2E<`yd7oGyWKa~cz}1wkx7T-$f843VX}T}gTxr?lj_&sBMW>>d&6_%Ci?;0;;6|J!F!Tk}D!HKxUeg`W7V0a4R$tGR z&>bAiVjOpM#U-P2D``=Z7netSs2v$4}AGuNszPxY8Ve4LA3DKZ;&%nnD9;Q_9~% z9~m4=6z7!2ibs@V?;M*> zZxJwfrR*RXwSx{YNz5JVhFLd$d(o0Fp+cM`hrXTvdcKxCT1_5ZPaa>1Jc}j26W3z< zH{*k~_{ds(-7`#y9} zKb1e|#%7Z<-^FA6A8d{xOQS2sgre#u?8}Z}8O;&}q70u;y^@3!S=E!=#^#w0zrEx} z!N$-e-ne(`&aGNvyqXwaPfV=J6aGCYc?}6;!>h3}Hjw)p<19xv>=2LVs46?;;y^w) zS!YmHK+Y5MVvbhskQV9@zkw_a;CxzyLHq$axZA54*0PCoR`iGu_K|_)vs4^`?U7)Dc0XH!Ug8td7ynmf{stsMs55^v!W_@1+7jctBH$Ul+_;2CS~>RlCpO5*tCbcf@!)omlx^SBp%f@3F~##g47krH$P91>h@l0 z+0%Q_A!r|Lx8KDBD()aGDO;f=<0JQzGDm4{y>R%z!2z6u5mE}Obl77Ok2kz;4am?G z#TpKh8YQEU2Mu_e1|~=yAvK1|?XLqvL$bM6LFnk5*wRCX4-Xh8yE@CYm()%W(z$pM zRoA0-YC}G~nL1ue&8($newkRksI8|y!{=@#@_Q_~gMLmvjZe4J&-lrW`1HT=Oj__L zA)AiTXOM;Djh{hLO~Y@kN}1YmP5T1h{rXHo(-uwOPT8~!%hog?cJx`A7}X;VKX~4K z_h=f%!ZC}Q?l@pxt~dtQv`5UN2A^suPC$y#%qh@cv8{k09o`BBr9E5GurwoV4G7Ya ztx!N3d?i!gcPA%IiuX?Gh%YF%TDf#`+VM8R6@m+5Q- z5qtyj=N;^97V-dEnFJB6yf<-*k8@_`Ffh;Q$K*&qm9m+t-|Ag`Jr<5kK_Oh2nVe!d zwrfnYU9%L^W5&>|^YGA3SBG{;_&5E0c>fag zcEE)qT|o&ry?{$wRsteg6H62s_whROM0u}GujY>F#nGlwW3q^*IuTjHiM(i>9m^=8 zRSi*M#LpW+%a0tt+G_jF#-)$@P2HgepEJi*j9kMNmJ&gQo_K}7TP`|bH$$u91hVuX z^5D>$$k`sCenQ!^=*C_aI*VEQvE7F@-_w4~K_fZ2$lO diff --git a/__pycache__/modelo.cpython-313.pyc b/__pycache__/modelo.cpython-313.pyc index 1db84433cdbc8b634f2fcc5ee53a0aac4320087a..9a44d129e59364e5d2bc7a096d1535813a4d5b54 100644 GIT binary patch delta 2934 zcmZuzT}&I<6}~f`8UGt&n?FN>0Vl)@IQ%6cl%z@cslY%|58h@2i92f#FkO3wy)#gj zuIi{Owa{*r&}p^RcDGflN=?<5Nc16<`cg$v)$K!#k`-u@X!DSlyt&Im|D?9}%wTZS z+=uU;d(OG%o_pq;bLY>U?+y9(yj~X~8q!fHrr#9JmRSYi&-+;65NCx z+ark3Ou{kY#7>iD6Rrstc9}GlaAS8pYDGL3M!fwL4JHx7o&_rA*j#LGiBX1kYJ6&R zYB{G#O9Ivi!vTyLan+T_-iBm`Q z0j86n$b4`k0!jy(2A5eD@sbj=;V1}9>n=%FMXZ_jxmVf8?L?PsHLt32WC4en$6aH) zsTTcv>^bV_)}PoE8|??PrtQ~Y6RT=iS~8fomnF@xkwwgp!}=w6${zv61IKW?{wsGY z)u?~sK25c6QJ!zoZGNIb9K8UFrlSmm0Ye~FiU3^q7rtKV%lfCjHgf(ye4o%mNcLRx zEI4>`o~P!~Ia@QDr((`&+y#c>04D*u0eS#>3CxVeLb?(d$Xx=?Fu(}FD1Z%M4B$%u z=Jlos74ur*x(wVa07(EIU`oDec{c!k8BZW>u0LVCDO(;v&3)C#Lztmf}nnLSA0wszqfo0*w#z_P-}(yD~w%bl5ND7M(_SNH(fdg-W5ovbn>ngA~AKR zY(CV-^wUw)f@Y7GPqia~L{zr(9^RX>6AikbR_SKad1d=Wdvt%UC<)(mj2>GCuSX;- zW-`+KEjf}EBUYV9Bzfij&vQ~%fchv{MPtx2%3R|jgZN?iRs{3Jv?ajMYYdyLe7#_mALKV4 z0wyPDnvhwl7)gMU`a7+*RbbBSrxt}8t!>9vnZtD znu=NAhJRl~{Le4tL`^X`(ppzUjNc$eRk&k0Vz?0;@R~8g`}Z}%R^+VbBwisah{`fp zIAL`NChQz|5^UZH#TLZ*v=o-TLTlW@CK!NZW1a#`JFZ7%)o=74`0VL+j%uMAdprHag-j zdYTF)4tf2zCN?I1xLS;K-)(tnl~z^%ka;{7QgCO0NOYT9Y&-frmrZRbw!_;&C3BhLg#E^l1^XmY2iZ@a1Q?%W^G z{o&mGFFx|{2b-S->xzw?cM5ZNrtd}XedAuf*f3cPP8L0re}7(8zlSL2z;3XN)eTl8p%dskV`reJec5ppQq66MVq9Mw@L9eB5Z*_8>$79cBq;_PZ(3 zMhJ2Y>TmBffgh1y8JU_z`uo(;!c~8{o_RD$MNy2NUhBRwD|D@0?7G(7bv7OE=(^r< z;gCUAbMcC0{CY<;MjJF14K^=mnZ+25-zKLR+>)SSMONV_2HsUN;du}=RwTrf693=w z(20f$x;N3Y>XCKsNmcc&)$gwUn0r{&b$6y%b++g@i=n>BD+(>td_^%d%jsC1{#odB z;Vf_m0p0@00YG_~)q#UR)d181Gyy~bpta&_05<>x0)|J`1Wn51MQu^Z;uYYVH>gRI zx&ld>R4kU>T-HQ2o&Gff$9j=TZloRr@cbBV5vo7F ztVuc5dgdiyhb(qp%8T)Q`L7d0W*Ajs{vFt2$~5u=YPKM=Q{i{Ou6ddFfP&ClW$qZt zE7|3ocpiU;@F3gNP68xLDe5uuK1QC$$oCX=nHYbHqEFG0$H@6NCP7n!pCdri+J6B> C--fyX delta 1104 zcmZwG&1(}u6aes@X0xWrZjv^&jj`3CL1j%FlU8GuRQy2kvsTwC2wGw@iL2>uebW^5 zU<-nH63U2PM8uot-bL`Hh(}9`e}E^&B7$CgZ>xgh5`LL^v+vEv?%pZC8Hlf1R#d|) z=RT`LVlkd(<;87wI<56 zont`6SqJS-1fouRMuV6WMNKD$T8@dv9Sco3aWv^9prf9&J6h%;A3*r0{Hkd99}TR1 zD^c7tlrNS>^2MP-zE~Lk=ux!$x|nml{WEeV?+V$dOjjW9gL=tIOfG$6#~CZAgP|>K zSj~oVEUBJ`-tFkZ#8#x{deuqa6SfZB7=T`)kJyAr9pQLEk9wl#*?RR>A7B~vU4L}> z?>k$@R8zo=Z-;O(T(gta=V7gD?0!u4yjIzwqWn&^7 zD^OWZY*jB}Y1XaY$0m}0uL82FZ1%GrHEEXErlp7G?Y69mDY&XKWd^I#Y0t18`;s0P z?QM5rh#ghe5?P$=e&Ti8e7k2JFzXQ~_^+{=HX7Qj)tDV^Uci*-c>!+CovLu@@~f4b z{MQD!*A#QUOlo0ixb7Ve5y@SLdf)mFO+gdyL6BIZ}j1i}a zapDYdmT-uF4s-%z(n5mg@*C*ENGpX~pPUw+tXvanGPT{JIb;qGbwT?aS$dfASQnmr z5~IP%LnpSz>`t}1u{8Q;^)N!?dx-?mL8OQ@!HKQJdEz2*N!{!1%VnrZ3xV)&=nayG zk-OS*XLjMlax%4WY`Mc$t=?kaSEE0?0fH<_u#L5|+>dRr)S15SQ$d|7_5ajvMat8D pwb>B+poRnV!bti&K8F}%E1I>UnJZfSgEsh0uQRsci$(_NKLH!smCWP%Bw9^!mNrFAST%6}>)?55!}o_DG`?1TLrA># zi)R}>I$Ew+0V|Ypz)Gb8(56%Z+7%nE8ny?jSfkJ^j+e4t;Rn`MT}T%x*rBX2$sdgB+YW9{v zM$7(GWwsT{d=9S6>?lkZVxJnE?2zRAj9zxtUyqA)K{N^?J81Od?Dd6D(etiDFl(y7cT#zYoU~#i2NrY7pEp`i1rEyxrxjQ9Md*mjTzbFuHF8J73~zZ7aMl zmx=@$EQ(jD>oUS7))rb|zd8#`>|fUNId&=airgh~gMMUmvDXcRNQ+vyD-?v@&kVkrnBh(0c2Uue~Ep?yvn-adCmNRo@3!O zyJ8NCWqPKXcaXUT|AgT-sk`PbGYRbdvWl*!vU?6^Bk}AD>$fL-A&IqCzfh-vsshkFrDMb?ijBUkI^R%WM7wgUY_N9~6Kse9{A`nA?D+J!AuY z4rIj^JO&mtls$?)`w{*h=H}CK16F?jXXbt!zuvO`iRHhobC)PI3IcxdXAL=KzMvpemsTl zONUX2F#*CCXDmublYF1)8fcl;=MAT5c$E6#g$GW+q3v!53YK?# z3%=@gCctj2wz5y0E@2ZhRZGG))>OS+*uzGu_cU=A*abcE9?jeL#Zu{Ta7CE3@3?li z`XfOKLAMBcPT?tq3dYR+Z;I?A_YhOv8%?DOQS^$T%fQZ8InbAZH<+ce4v!l)U~|}s zVp7aq`r=beu648jDz7Sq!=j{1kXhEf$z=II^Z*^36suC+WdKbj1&cz=COX-FmOGYo zjY{=}sl|#AtG89dp?9v*Q=!=SWXNRp-Z7ayOWYZGC@P0iV__Og!k%-=!gGyU0H*cY zPI9(F^7;+7!~P=Kp0Ojw~a%JRt z%T20!`;eYJbHtJ(9+h}fwp9IZ~A)Zk4z%4Q7e^7HObg7)Sd1cl11visHqjS4z9YgcuSrph_SrNWYGi zS9lGkE*I7%rqKzufpVUkWd>r2(Xn(YZ$@&_Mx_j(A5yW!9OYS@?Tg7}{R_~ZdJB#wAeUf;XwqDmU>YCM$Dh-fCDx;C#Mpegb1?e9C@b8sT+ zEbxXjxnri~25?4ijwmWovIm~a4o0%^WcK8#EK0`beT`R}-)NrN%-KWr?L+FQ=hT5j zlp~EQX`Hgnv|n$}LOnwsUDmyGX4j3FN_OW+NF|}{!T#(}G@E!KJAOKgk~8yC-Q_30 z^W^07*R41ERB1P=uHmYYG;;*GX|jE4&kV2~)HfD1bNZY-I5Tq7q>{cI*{_oQ*@3}q z>_oN-U*hvpb582GC3VaUy+86E`vUf{c;VpULhZV_A@}ql75;@gg7KTcH2Bvq9U-i$~v(_ zWV`EqYZCg9&QY6=wBmXcAeL6>-{oCug#aGjwMf>3e!X^G0g83^dg800bp4X zUM1mF22;U&>0%T;&I&N^oe%X`_4M%Om&4oD`1+Q9?t=Ljp9A(~a_J|`CP^9K9LTbk#gH%Rd1rqi4aPHwG8XqOtV>F&Xy>e6{!_0du}I zeImwYRvXXad8Q?D+@*NZ$JY+8+4Kg&&k?wVcd)i(b)IYF81Cukr7fk<@=+{s3u-J%DIj1`SP(&l4TKAR&XH?-DIKMC=eDxBINTPE zOO`GAW{WdAKbHN_?1!;`mi;2z?@e5?x!lDpA&Z$=%pAsK*_LeYdy9Ze)}+rp=RNQD z`j4EVT&C#R@vAkw`7+bk`rB_Asd6srZRV7#5QI~7Uy%o zEY4LtO=86ljMP^xCwFZQHU6Nz}1%hAMFxfI+lINjyJ(+4#6pqZE9hr0+W2+9fS5w06K zHJ4ZA*Cn*BK?qjE4SN_K+NXd!_61x~uE>0Rg3Hs&_(YTT?7mqGKzVDxA2exZRY^|p z267WZP)!J;C~A$+?OexNKy`+UGe-Etxih!uEMTCx?t@%Z0jG-kjAsn+X0Q$BOMD=f zdzk?DYO7$#Q_y?eG&ssPQC3#I6F-{cs2rUdpGfn#lHq+Q;jIMC1lTM% z8)4z%J2dhUWjc{jQW+)4^4?w~1qZ^T%kZw)t!7i1G(Sv2VgU+IiG50RQsytMQoJPg zWEFlitv8ql@r1@W!{wvA7B@Oof?n+HKHRjBWToNTXiFpJTL@_4T5(KKqcV>pU7~5u zm9)yK7n*B2t!DVLRI~0J85-K#%e!HyvX2eGhn2%teNwyNVPy^L$Q7>nn&E`zJU(O0 z05!F-S){*hkXNHO?4QnV>IQd`z?5%+PHf`&120J#_tVFmr^XIkugnmA5J@y=Qa++2 zF6WoPOQT%4aD@ie9XqCQoLDp=!~VuP%W+dvC=4eXHSYwYUd_}y zxI?ohQYuEp9t^HB?VH@^1_f}Jw&1zOzXN+c40nX)qUlVp@{F@GD^t2?W?13KemQjIV?P?aKN*_t7@F=Gg78n5 OP&C6>Xwg8Zd-xa1Y$D(Q diff --git a/controlador.py b/controlador.py index f25d05e..a4bb3cb 100644 --- a/controlador.py +++ b/controlador.py @@ -1,17 +1,37 @@ from modelo import CorreoModelo from tkinter import messagebox +import threading class CorreoControlador: def __init__(self): self.modelo = CorreoModelo() def descargar_correos(self): + self.vista.actualizar_footer("📥 Descargando correos...") + hilo = threading.Thread(target=self._descargar_correos) + hilo.start() + + def _descargar_correos(self): resultado = self.modelo.descargar_correos() if resultado is True: messagebox.showinfo("Éxito", "Correos descargados correctamente") else: messagebox.showerror("Error", f"Error al descargar correos: {resultado}") self.vista.actualizar_lista() + self.vista.actualizar_footer("Gestor de Correos - 2025") 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): + self.vista.actualizar_footer("📨 Enviando correo...") + resultado, mensaje_respuesta = CorreoModelo.enviar_correo(destinatario, asunto, mensaje) + if resultado: + messagebox.showinfo("Éxito", mensaje_respuesta) + else: + messagebox.showerror("Error", mensaje_respuesta) + self.vista.actualizar_footer("Gestor de Correos - 2025") diff --git a/modelo.py b/modelo.py index 55e1524..61c598d 100644 --- a/modelo.py +++ b/modelo.py @@ -2,16 +2,25 @@ import poplib import email import pymongo from email.utils import parsedate_to_datetime +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from datetime import datetime +import re class CorreoModelo: - POP3_SERVER = "192.168.120.103" - POP3_PORT = 110 - EMAIL_USER = "kevin@psp.ieslamar.org" + POP3_SERVER = "s1.ieslamar.org" #192.168.120.103 + POP3_PORT = 110 #110 + + SMTP_SERVER = "s1.ieslamar.org" #s1.ieslamar.org + SMTP_PORT = 25 #25 + + EMAIL_USER = "kevin@fp.ieslamar.org" EMAIL_PASS = "1234" MONGO_CLIENT = "mongodb://localhost:27017/" - DB_NAME = "correo_db" - COLLECTION_NAME = "correos" + DB_NAME = "correo_db" + COLLECTION_NAME = "correoc" #*s def __init__(self): self.client = pymongo.MongoClient(self.MONGO_CLIENT) @@ -73,9 +82,6 @@ class CorreoModelo: return list(self.collection.find()) def hay_mensajes_nuevos(self): - """ - Verifica si hay correos nuevos en el servidor POP3 que no estén en la base de datos. - """ try: mail = poplib.POP3(self.POP3_SERVER, self.POP3_PORT) mail.user(self.EMAIL_USER) @@ -100,11 +106,45 @@ class CorreoModelo: if not self.correo_existe(remitente, asunto, fecha): mail.quit() - return True # Hay al menos un mensaje nuevo + return True mail.quit() - return False # No hay mensajes nuevos + return False except Exception as e: - return False # En caso de error, asumimos que no hay nuevos mensajes + return False + @staticmethod + def enviar_correo(destinatario, asunto, mensaje): + try: + if not CorreoModelo.validar_correo(destinatario): + return False, "Dirección de correo inválida" + + fecha_envio = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + msg = MIMEMultipart() + msg["From"] = CorreoModelo.EMAIL_USER + msg["To"] = destinatario + msg["Subject"] = asunto + msg["Date"] = fecha_envio + + mensaje_completo = f""" + {mensaje} + """ + msg.attach(MIMEText(mensaje_completo, "plain")) + + server = smtplib.SMTP(CorreoModelo.SMTP_SERVER, CorreoModelo.SMTP_PORT) + server.ehlo() + server.login(CorreoModelo.EMAIL_USER, CorreoModelo.EMAIL_PASS) + server.sendmail(CorreoModelo.EMAIL_USER, destinatario, msg.as_string()) + server.quit() + + return True, f"Correo enviado a {destinatario} el {fecha_envio}" + + 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-.]+$" + return re.match(patron, destinatario) is not None diff --git a/vista.py b/vista.py index 713effb..b7a834c 100644 --- a/vista.py +++ b/vista.py @@ -11,6 +11,7 @@ class CorreoVista: self.root.title("📬 Gestor de Correos") self.root.geometry("900x600") self.root.configure(bg="#f4f4f4") + self.archivos_adjuntos = [] # Frame del menú izquierdo self.menu_frame = tk.Frame(self.root, bg="#333333", width=200) @@ -22,6 +23,9 @@ class CorreoVista: self.btn_f2 = tk.Button(self.menu_frame, text="⚙️ Configuración", font=("Arial", 12), bg="#555555", fg="white", relief=tk.FLAT, command=self.mostrar_frame_f2) self.btn_f2.pack(fill=tk.X, pady=5, padx=5) + + self.btn_f3 = tk.Button(self.menu_frame, text="📤 Enviar Correo", font=("Arial", 12), bg="#555555", fg="white", relief=tk.FLAT, command=self.mostrar_frame_f3) + self.btn_f3.pack(fill=tk.X, pady=5, padx=5) # Frame principal derecho self.main_frame = tk.Frame(self.root, bg="#f4f4f4") @@ -37,9 +41,11 @@ class CorreoVista: # Frames individuales para cada sección self.frame_f1 = tk.Frame(self.main_frame, bg="#f4f4f4") self.frame_f2 = tk.Frame(self.main_frame, bg="#f4f4f4") + self.frame_f3 = tk.Frame(self.main_frame, bg="#f4f4f4") self.crear_frame_f1() self.crear_frame_f2() + self.crear_frame_f3() # Mostrar el primer frame por defecto self.frame_f1.pack(fill=tk.BOTH, expand=True) @@ -49,11 +55,18 @@ class CorreoVista: def mostrar_frame_f1(self): self.frame_f2.pack_forget() + self.frame_f3.pack_forget() self.frame_f1.pack(fill=tk.BOTH, expand=True) def mostrar_frame_f2(self): self.frame_f1.pack_forget() + self.frame_f3.pack_forget() self.frame_f2.pack(fill=tk.BOTH, expand=True) + + def mostrar_frame_f3(self): + self.frame_f1.pack_forget() + self.frame_f2.pack_forget() + self.frame_f3.pack(fill=tk.BOTH, expand=True) def crear_frame_f1(self): frame_top = tk.Frame(self.frame_f1, bg="#f4f4f4") @@ -82,9 +95,54 @@ class CorreoVista: self.detalle_text.pack(fill=tk.BOTH, expand=True) def crear_frame_f2(self): - label_config = tk.Label(self.frame_f2, text="⚙️ Configuración", font=("Arial", 16), bg="#f4f4f4") + label_config = tk.Label(self.frame_f2, text="⚙️ Configuración de Usuario", font=("Arial", 16), bg="#f4f4f4") label_config.pack(pady=20) + tk.Label(self.frame_f2, text="Usuario (correo):", font=("Arial", 12), bg="#f4f4f4").pack() + self.entry_usuario = tk.Entry(self.frame_f2, font=("Arial", 12), width=50) + self.entry_usuario.pack(pady=5) + + tk.Label(self.frame_f2, text="Contraseña:", font=("Arial", 12), bg="#f4f4f4").pack() + self.entry_contraseña = tk.Entry(self.frame_f2, font=("Arial", 12), width=50, show="*") + self.entry_contraseña.pack(pady=5) + + 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) + + frame_inputs = tk.Frame(self.frame_f3, bg="#f4f4f4") + frame_inputs.pack(pady=10, padx=10, fill=tk.X) + + tk.Label(frame_inputs, text="Para:", font=("Arial", 12), bg="#f4f4f4").grid(row=0, column=0, sticky="w", padx=5) + self.entry_destinatario = tk.Entry(frame_inputs, font=("Arial", 12), width=50) + self.entry_destinatario.grid(row=0, column=1, pady=5) + + tk.Label(frame_inputs, text="Asunto:", font=("Arial", 12), bg="#f4f4f4").grid(row=1, column=0, sticky="w", padx=5) + self.entry_asunto = tk.Entry(frame_inputs, font=("Arial", 12), width=50) + self.entry_asunto.grid(row=1, column=1, pady=5) + + tk.Label(self.frame_f3, text="Mensaje:", font=("Arial", 12), bg="#f4f4f4").pack(anchor="w", padx=10) + 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) + + btn_enviar = ttk.Button(self.frame_f3, text="📨 Enviar", command=self.enviar_correo) + btn_enviar.pack(pady=10) + + def enviar_correo(self): + destinatario = self.entry_destinatario.get().strip() + asunto = self.entry_asunto.get().strip() + mensaje = self.text_mensaje.get("1.0", tk.END).strip() + + if not destinatario or not asunto or not mensaje: + messagebox.showwarning("⚠️ Advertencia", "Todos los campos son obligatorios.") + return + + self.controlador.enviar_correo(destinatario, asunto, mensaje) + def mostrar_correo(self): seleccionado = self.tree.selection() if not seleccionado: @@ -118,9 +176,24 @@ class CorreoVista: # Cambiar color de la barra en el hilo principal de Tkinter self.root.after(0, self.cambiar_color_barra, "#FF0000" if hay_nuevos else "#1E90FF") - time.sleep(5) # Esperar 5 segundos antes de verificar nuevamente + time.sleep(2) # Esperar 5 segundos antes de verificar nuevamente def cambiar_color_barra(self, color): """ Cambia el color de la barra inferior. """ self.footer_frame.configure(bg=color) self.footer_label.configure(bg=color) + + def actualizar_footer(self, mensaje): + """ Cambia el texto de la barra inferior """ + self.footer_label.config(text=mensaje) + + def cambiar_credenciales(self): + nuevo_usuario = self.entry_usuario.get().strip() + nueva_contraseña = self.entry_contraseña.get().strip() + + if not nuevo_usuario or not nueva_contraseña: + messagebox.showwarning("⚠️ Advertencia", "Usuario y contraseña no pueden estar vacíos.") + return + + self.controlador.cambiar_credenciales(nuevo_usuario, nueva_contraseña) + messagebox.showinfo("✅ Éxito", "Credenciales actualizadas correctamente.") \ No newline at end of file