Sistema de Notificaciones y Baja One-Click

This commit is contained in:
2026-03-12 13:52:33 -03:00
parent f1a9bb9099
commit 96fca4d9c7
21 changed files with 1384 additions and 79 deletions

View File

@@ -18,8 +18,28 @@ public class NotificationService : INotificationService
_frontendUrl = config["AppSettings:FrontendUrl"]?.Split(',')[0].Trim() ?? "http://localhost:5173";
}
private string GetEmailShell(string title, string content)
// ─── Shell del correo ──────────────────────────────────────────────────────
/// <summary>
/// Genera el HTML completo del correo con header, contenido y footer.
/// Si se provee unsubscribeUrl, agrega el enlace de baja en el footer.
/// </summary>
private string GetEmailShell(string title, string content, string? unsubscribeUrl = null)
{
// Footer de baja: solo se muestra si el correo tiene categoría de preferencia
var unsubscribeBlock = string.IsNullOrEmpty(unsubscribeUrl)
? string.Empty
: $@"
<div style='margin-top: 18px; padding-top: 18px; border-top: 1px solid #1f2937;'>
<p style='color: #4b5563; font-size: 11px; margin: 0; text-align: center;'>
¿No querés recibir más este tipo de correos?
<a href='{unsubscribeUrl}'
style='color: #6b7280; text-decoration: underline; font-size: 11px;'>
Darte de baja
</a>
</p>
</div>";
return $@"
<div style='background-color: #0a0c10; color: #e5e7eb; font-family: sans-serif; padding: 40px; line-height: 1.6;'>
<div style='max-width: 600px; margin: 0 auto; background-color: #12141a; border: 1px solid #1f2937; border-radius: 24px; overflow: hidden; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);'>
@@ -34,12 +54,20 @@ public class NotificationService : INotificationService
</div>
<div style='padding: 20px; border-top: 1px solid #1f2937; text-align: center; background-color: #0d0f14;'>
<p style='color: #4b5563; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; margin: 0;'>Motores Argentinos - La Plata, Buenos Aires, Argentina</p>
{unsubscribeBlock}
</div>
</div>
</div>";
}
public async Task SendChatNotificationEmailAsync(string toEmail, string fromUser, string message, int adId)
// ─── Implementaciones ──────────────────────────────────────────────────────
/// <summary>
/// Notificación de nuevo mensaje de chat. Categoría: mensajes.
/// </summary>
public async Task SendChatNotificationEmailAsync(
string toEmail, string fromUser, string message, int adId,
string? unsubscribeUrl = null)
{
string subject = "Tienes un nuevo mensaje - Motores Argentinos";
string content = $@"
@@ -53,10 +81,15 @@ public class NotificationService : INotificationService
<a href='{_frontendUrl}/mis-avisos' style='background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; text-transform: uppercase; font-size: 12px;'>VER MENSAJES</a>
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Nuevo Mensaje", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Nuevo Mensaje", content, unsubscribeUrl));
}
public async Task SendAdStatusChangedEmailAsync(string toEmail, string adTitle, string status, string? reason = null)
/// <summary>
/// Aviso de cambio de estado del aviso. Categoría: sistema.
/// </summary>
public async Task SendAdStatusChangedEmailAsync(
string toEmail, string adTitle, string status, string? reason = null,
string? unsubscribeUrl = null)
{
string subject = "Estado de tu aviso - Motores Argentinos";
string color = status.ToUpper() == "APROBADO" ? "#10b981" : "#ef4444";
@@ -70,9 +103,12 @@ public class NotificationService : INotificationService
{(string.IsNullOrEmpty(reason) ? "" : $"<p style='background: #1f2937; padding: 15px; border-radius: 8px; border-left: 4px solid #ef4444;'><strong>Motivo:</strong> {reason}</p>")}
<p style='margin-top: 20px;'>Gracias por confiar en nosotros.</p>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Cambio de Estado", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Cambio de Estado", content, unsubscribeUrl));
}
/// <summary>
/// Alerta de seguridad crítica. SIN enlace de baja (siempre se envía).
/// </summary>
public async Task SendSecurityAlertEmailAsync(string toEmail, string actionDescription)
{
string subject = "Alerta de Seguridad - Motores Argentinos";
@@ -85,10 +121,16 @@ public class NotificationService : INotificationService
<p>Si no fuiste tú, te recomendamos cambiar tu contraseña inmediatamente y contactar a nuestro equipo de soporte.</p>
<p style='margin-top: 20px;'>Atentamente,<br>Equipo de Seguridad.</p>";
// Sin unsubscribeUrl: los correos de seguridad no tienen baja
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Alerta de Seguridad", content));
}
public async Task SendExpirationWarningEmailAsync(string toEmail, string userName, string adTitle, DateTime expirationDate)
/// <summary>
/// Aviso de próximo vencimiento. Categoría: sistema.
/// </summary>
public async Task SendExpirationWarningEmailAsync(
string toEmail, string userName, string adTitle, DateTime expirationDate,
string? unsubscribeUrl = null)
{
string subject = "Tu aviso está por vencer - Motores Argentinos";
string content = $@"
@@ -99,10 +141,15 @@ public class NotificationService : INotificationService
<a href='{_frontendUrl}/mis-avisos' style='background-color: #f59e0b; color: #000; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; text-transform: uppercase; font-size: 12px;'>RENOVAR AVISO</a>
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Aviso por Vencer", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Aviso por Vencer", content, unsubscribeUrl));
}
public async Task SendAdExpiredEmailAsync(string toEmail, string userName, string adTitle)
/// <summary>
/// Aviso de vencimiento consumado. Categoría: sistema.
/// </summary>
public async Task SendAdExpiredEmailAsync(
string toEmail, string userName, string adTitle,
string? unsubscribeUrl = null)
{
string subject = "Tu aviso ha finalizado - Motores Argentinos";
string content = $@"
@@ -113,10 +160,15 @@ public class NotificationService : INotificationService
<a href='{_frontendUrl}/mis-avisos' style='background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; text-transform: uppercase; font-size: 12px;'>REPUBLICAR AHORA</a>
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Aviso Finalizado", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Aviso Finalizado", content, unsubscribeUrl));
}
public async Task SendWeeklyPerformanceEmailAsync(string toEmail, string userName, string adTitle, int views, int favorites)
/// <summary>
/// Resumen semanal de rendimiento del aviso. Categoría: rendimiento.
/// </summary>
public async Task SendWeeklyPerformanceEmailAsync(
string toEmail, string userName, string adTitle, int views, int favorites,
string? unsubscribeUrl = null)
{
string subject = "Resumen semanal de tu aviso - Motores Argentinos";
@@ -158,10 +210,15 @@ public class NotificationService : INotificationService
<![endif]-->
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Rendimiento Semanal", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Rendimiento Semanal", content, unsubscribeUrl));
}
public async Task SendPaymentReminderEmailAsync(string toEmail, string userName, string adTitle, string link)
/// <summary>
/// Recordatorio de carrito abandonado. Categoría: marketing.
/// </summary>
public async Task SendPaymentReminderEmailAsync(
string toEmail, string userName, string adTitle, string link,
string? unsubscribeUrl = null)
{
string subject = "Finaliza la publicación de tu aviso - Motores Argentinos";
string content = $@"
@@ -172,10 +229,14 @@ public class NotificationService : INotificationService
<a href='{link}' style='background-color: #10b981; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; text-transform: uppercase; font-size: 12px;'>FINALIZAR PUBLICACIÓN</a>
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Acción Requerida", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Acción Requerida", content, unsubscribeUrl));
}
public async Task SendPaymentReceiptEmailAsync(string toEmail, string userName, string adTitle, decimal amount, string operationCode)
/// <summary>
/// Recibo de pago transaccional. SIN enlace de baja (siempre se envía).
/// </summary>
public async Task SendPaymentReceiptEmailAsync(
string toEmail, string userName, string adTitle, decimal amount, string operationCode)
{
string subject = "Comprobante de Pago - Motores Argentinos";
string content = $@"
@@ -191,10 +252,16 @@ public class NotificationService : INotificationService
</div>
<p>Tu aviso ha pasado a la etapa de moderación y será activado a la brevedad.</p>";
// Sin unsubscribeUrl: los comprobantes de pago son transaccionales obligatorios
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Recibo de Pago", content));
}
public async Task SendUnreadMessagesReminderEmailAsync(string toEmail, string userName, int unreadCount)
/// <summary>
/// Recordatorio de mensajes sin leer. Categoría: mensajes.
/// </summary>
public async Task SendUnreadMessagesReminderEmailAsync(
string toEmail, string userName, int unreadCount,
string? unsubscribeUrl = null)
{
string subject = "Tienes mensajes sin leer - Motores Argentinos";
string content = $@"
@@ -205,6 +272,6 @@ public class NotificationService : INotificationService
<a href='{_frontendUrl}/mis-avisos' style='background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; text-transform: uppercase; font-size: 12px;'>IR A MIS MENSAJES</a>
</div>";
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Mensajes Pendientes", content));
await _emailService.SendEmailAsync(toEmail, subject, GetEmailShell("Mensajes Pendientes", content, unsubscribeUrl));
}
}