Files
plantillas-proyectos/backend/core/email.py
Kevin_Ramirez 880999b03a
Some checks failed
Build Producción & Push a Harbor / test (push) Failing after 2s
Build Producción & Push a Harbor / build (push) Has been skipped
feat: plantilla base workspace SaaS
Convierte el repositorio de Anexo76 en una plantilla limpia y reutilizable
para nuevos proyectos del ecosistema Workspace de Aduanasoft.

Cambios principales:
- Elimina módulos específicos de Anexo76: a76, a24, sitar, public
- Agrega módulo example/ con patrón CRUD de referencia (models/dto/service/routes)
- Limpia migraciones Alembic: solo quedan las 6 de core (users, tenants, permissions)
- Reemplaza todas las rutas del dashboard con stubs genéricos
- Elimina lógica de negocio aduanera: shortcuts, CSV imports, permisos, catálogos
- Simplifica variables de entorno: una sola WORKSPACE_URL deriva Hub y Keycloak
- Agrega scripts/auth-mode.sh para alternar entre auth local y workspace
- Configura docker-compose con nombres genéricos (app-*)
- Corrige flujo SSO: elimina system-gate SCAF/SCAII que bloqueaba el login
- Modo DEV_LOCAL_AUTH para desarrollo sin Keycloak ni Hub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:55:17 -05:00

119 lines
4.3 KiB
Python

"""
Email service for sending reports via SMTP.
"""
import aiosmtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from typing import List
import logging
from datetime import datetime
from core.config import settings
logger = logging.getLogger(__name__)
class EmailService:
"""Service for sending emails with attachments."""
@staticmethod
async def send_report_email(
recipient_email: str,
subject: str,
body_text: str,
csv_content: str,
filename: str
) -> bool:
"""
Send a report email with CSV attachment.
Args:
recipient_email: Email address of recipient
subject: Email subject line
body_text: Plain text email body
csv_content: CSV file content as string
filename: Name for the CSV attachment
Returns:
bool: True if email sent successfully, False otherwise
"""
try:
# Create message
msg = MIMEMultipart()
msg['From'] = f"{settings.SMTP_FROM_NAME} <{settings.SMTP_USER}>"
msg['To'] = recipient_email
msg['Subject'] = subject
# Email body
html_body = f"""
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h2 style="color: #2563eb; border-bottom: 2px solid #2563eb; padding-bottom: 10px;">
Reporte de Facturas
</h2>
<p>{body_text}</p>
<p style="margin-top: 20px;">
El reporte se encuentra adjunto en formato CSV.
</p>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
<p style="font-size: 12px; color: #6b7280;">
Este es un correo generado automáticamente. Por favor no responder.
</p>
<p style="font-size: 12px; color: #6b7280;">
Generado el {datetime.now().strftime('%d/%m/%Y a las %H:%M')}
</p>
</div>
</body>
</html>
"""
msg.attach(MIMEText(html_body, 'html'))
# CSV attachment with UTF-8 BOM for Excel compatibility
attachment = MIMEBase('text', 'csv')
csv_bytes = b'\xef\xbb\xbf' + csv_content.encode('utf-8')
attachment.set_payload(csv_bytes)
encoders.encode_base64(attachment)
attachment.add_header(
'Content-Disposition',
f'attachment; filename="{filename}"'
)
msg.attach(attachment)
# Create SSL context that ignores certificate errors
import ssl
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# Send email
if settings.SMTP_PORT == 465:
# Port 465 uses implicit SSL
async with aiosmtplib.SMTP(
hostname=settings.SMTP_HOST,
port=settings.SMTP_PORT,
use_tls=True, # Implicit SSL
tls_context=context
) as smtp:
await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
await smtp.send_message(msg)
else:
# Port 587 uses STARTTLS
async with aiosmtplib.SMTP(
hostname=settings.SMTP_HOST,
port=settings.SMTP_PORT,
tls_context=context
) as smtp:
await smtp.starttls(tls_context=context)
await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
await smtp.send_message(msg)
logger.info(f"Email sent successfully to {recipient_email}")
return True
except Exception as e:
logger.error(f"Failed to send email to {recipient_email}: {str(e)}")
return False