package services import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // EnviarCorreoMicrosoft manda un correo de texto plano vía Microsoft Graph // (POST /me/sendMail) en nombre de la cuenta conectada. func EnviarCorreoMicrosoft(conexion *models.UmindConexion, destinatario, asunto, cuerpo string) error { accessToken, err := DescifrarSecretoUmind(conexion.AccessTokenEnc) if err != nil { return fmt.Errorf("no se pudo descifrar el access token: %w", err) } payload := map[string]interface{}{ "message": map[string]interface{}{ "subject": asunto, "body": map[string]string{"contentType": "Text", "content": cuerpo}, "toRecipients": []map[string]interface{}{ {"emailAddress": map[string]string{"address": destinatario}}, }, }, } body, _ := json.Marshal(payload) req, err := http.NewRequest(http.MethodPost, "https://graph.microsoft.com/v1.0/me/sendMail", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json") resp, err := umindOAuthHTTPClient.Do(req) if err != nil { return fmt.Errorf("no se pudo contactar Microsoft Graph: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) return fmt.Errorf("Microsoft Graph respondió %d: %s", resp.StatusCode, string(detalle)) } return nil } // LeerBandejaMicrosoft busca mensajes en la bandeja vía Microsoft Graph // ($search sobre asunto/cuerpo/remitente) y devuelve un resumen liviano. func LeerBandejaMicrosoft(conexion *models.UmindConexion, consulta string, limite int) ([]CorreoResumen, error) { if limite <= 0 || limite > 10 { limite = 10 } accessToken, err := DescifrarSecretoUmind(conexion.AccessTokenEnc) if err != nil { return nil, fmt.Errorf("no se pudo descifrar el access token: %w", err) } q := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/messages?$search=%s&$top=%d&$select=from,subject,receivedDateTime,bodyPreview", url.QueryEscape(`"`+consulta+`"`), limite) req, err := http.NewRequest(http.MethodGet, q, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+accessToken) // $search requiere este header ("eventual consistency") en Microsoft Graph. req.Header.Set("ConsistencyLevel", "eventual") resp, err := umindOAuthHTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("no se pudo contactar Microsoft Graph: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) return nil, fmt.Errorf("Microsoft Graph respondió %d: %s", resp.StatusCode, string(detalle)) } var out struct { Value []struct { From struct { EmailAddress struct { Name string `json:"name"` Address string `json:"address"` } `json:"emailAddress"` } `json:"from"` Subject string `json:"subject"` ReceivedDateTime string `json:"receivedDateTime"` BodyPreview string `json:"bodyPreview"` } `json:"value"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, err } resultados := make([]CorreoResumen, 0, len(out.Value)) for _, m := range out.Value { resultados = append(resultados, CorreoResumen{ De: m.From.EmailAddress.Address, Asunto: m.Subject, Fecha: m.ReceivedDateTime, Extracto: m.BodyPreview, }) } return resultados, nil }