Gestión de reseñas en Google

A continuación se presenta un ejemplo de código a nivel de "proof of concept" que muestra cómo podría implementarse la integración con Google Business Profile (antes Google My Business) . Este ejemplo asume:

  • Ya existe un servicio genérico para gestión de tokens (IExternalTokenService) y una implementación GoogleTokenService que obtiene y refresca tokens de Google.
  • Dispones del HttpClient inyectado.
  • Estás trabajando dentro de tu capa de aplicación/infraestructura (por ejemplo, un AppService) siguiendo el patrón DDD y AspNetBoilerplate.
  • Configuraste previamente las credenciales OAuth 2.0 en Google Cloud, obtuviste el refresh token inicial y lo almacenaste en el repositorio de ExternalAccessToken.
  • Este código es orientativo y deberá adaptarse a tu entorno real (entidades, repositorios, manejo de errores, logging, etc.).

Obtención de Cuentas y Ubicaciones

Este ejemplo muestra cómo listar cuentas y ubicaciones asociadas:

public class GoogleMyBusinessAppService
{
    private readonly IExternalTokenService _tokenService;
    private readonly HttpClient _httpClient;

    public GoogleMyBusinessAppService(IExternalTokenService tokenService, HttpClient httpClient)
    {
        _tokenService = tokenService;
        _httpClient = httpClient;
    }

    public async Task<List<AccountDto>> GetAccountsAsync(string accountIdentifier)
    {
        // Obtener un access token válido para Google
        var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);

        // Añadir header de autorización
        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

        // Endpoint para listar cuentas: https://developers.google.com/my-business/reference/rest/v4/accounts/list
        var response = await _httpClient.GetAsync("https://mybusiness.googleapis.com/v4/accounts");
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();

        var accountsData = Newtonsoft.Json.JsonConvert.DeserializeObject<AccountsListResponse>(json);
        return accountsData.Accounts.Select(a => new AccountDto { Name = a.Name, AccountName = a.AccountName }).ToList();
    }

    public async Task<List<LocationDto>> GetLocationsAsync(string accountIdentifier, string accountName)
    {
        var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);

        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

        // https://developers.google.com/my-business/reference/rest/v4/accounts.locations/list
        var url = $"https://mybusiness.googleapis.com/v4/{accountName}/locations";
        var response = await _httpClient.GetAsync(url);
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();
        
        var locationsData = Newtonsoft.Json.JsonConvert.DeserializeObject<LocationsListResponse>(json);
        return locationsData.Locations.Select(l => new LocationDto { Name = l.Name, StoreCode = l.StoreCode, LocationName = l.LocationName }).ToList();
    }
    
    // DTOs internos para parsear la respuesta
    private class AccountsListResponse
    {
        [Newtonsoft.Json.JsonProperty("accounts")]
        public List<AccountItem> Accounts { get; set; }
    }

    private class AccountItem
    {
        public string Name { get; set; } // e.g. "accounts/1234567890"
        [Newtonsoft.Json.JsonProperty("accountName")]
        public string AccountName { get; set; } // The descriptive name of the account
    }

    private class LocationsListResponse
    {
        [Newtonsoft.Json.JsonProperty("locations")]
        public List<LocationItem> Locations { get; set; }
    }

    private class LocationItem
    {
        public string Name { get; set; } // e.g. "accounts/1234567890/locations/9876543210"
        [Newtonsoft.Json.JsonProperty("storeCode")]
        public string StoreCode { get; set; }
        [Newtonsoft.Json.JsonProperty("locationName")]
        public string LocationName { get; set; }
    }

    public class AccountDto
    {
        public string Name { get; set; }
        public string AccountName { get; set; }
    }

    public class LocationDto
    {
        public string Name { get; set; }
        public string StoreCode { get; set; }
        public string LocationName { get; set; }
    }
}

Lectura de Reseñas y Respuesta Automática

A continuación, un método para obtener reseñas de una ubicación y responderlas. Por ejemplo, supongamos que queremos responder automáticamente a todas las reseñas sin respuesta.

public class GoogleMyBusinessReviewsAppService
{
    private readonly IExternalTokenService _tokenService;
    private readonly HttpClient _httpClient;

    public GoogleMyBusinessReviewsAppService(IExternalTokenService tokenService, HttpClient httpClient)
    {
        _tokenService = tokenService;
        _httpClient = httpClient;
    }

    public async Task<List<ReviewDto>> GetReviewsAsync(string accountIdentifier, string locationName)
    {
        var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

        // Endpoint: https://developers.google.com/my-business/reference/rest/v4/accounts.locations.reviews/list
        var url = $"https://mybusiness.googleapis.com/v4/{locationName}/reviews";
        var response = await _httpClient.GetAsync(url);
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();

        var reviewsData = Newtonsoft.Json.JsonConvert.DeserializeObject<ReviewsListResponse>(json);
        if (reviewsData?.Reviews == null) return new List<ReviewDto>();

        return reviewsData.Reviews.Select(r => new ReviewDto {
            Name = r.Name,
            Comment = r.Comment,
            StarRating = r.StarRating,
            ReviewId = r.Name.Split('/').Last(),
            ReviewerDisplayName = r.Reviewer.DisplayName,
            ReviewTimestamp = r.CreateTime
        }).ToList();
    }

    public async Task ReplyToReviewAsync(string accountIdentifier, string reviewName, string replyComment)
    {
        var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

        // Endpoint para responder una reseña:
        // PUT https://mybusiness.googleapis.com/v4/{reviewName}/reply
        // reviewName looks like: accounts/{accountId}/locations/{locationId}/reviews/{reviewId}
        var url = $"https://mybusiness.googleapis.com/v4/{reviewName}/reply";

        var content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(new { comment = replyComment }), 
            System.Text.Encoding.UTF8, 
            "application/json");

        var response = await _httpClient.PutAsync(url, content);
        response.EnsureSuccessStatusCode();
    }

    // DTOs internos
    private class ReviewsListResponse
    {
        [Newtonsoft.Json.JsonProperty("reviews")]
        public List<ReviewItem> Reviews { get; set; }
    }

    private class ReviewItem
    {
        public string Name { get; set; } // "accounts/123/locations/456/reviews/789"
        public string Comment { get; set; }
        public string StarRating { get; set; } // "FIVE", "FOUR", etc.
        [Newtonsoft.Json.JsonProperty("createTime")]
        public DateTime CreateTime { get; set; }
        public Reviewer Reviewer { get; set; }
    }

    private class Reviewer
    {
        [Newtonsoft.Json.JsonProperty("displayName")]
        public string DisplayName { get; set; }
    }

    public class ReviewDto
    {
        public string Name { get; set; }
        public string Comment { get; set; }
        public string StarRating { get; set; }
        public string ReviewId { get; set; }
        public string ReviewerDisplayName { get; set; }
        public DateTime ReviewTimestamp { get; set; }
    }
}

Respuesta Automática basándose en Análisis de Sentimiento

Podrías integrar Azure Cognitive Services u otro análisis de sentimiento antes de responder:

public class AutoResponderService
{
    private readonly GoogleMyBusinessReviewsAppService _reviewsAppService;
    private readonly IAiContentAnalyzerService _aiContentAnalyzer; // Un servicio que analiza el texto con Azure AI
    private readonly string _accountIdentifier;
    private readonly string _locationName;

    public AutoResponderService(GoogleMyBusinessReviewsAppService reviewsAppService, IAiContentAnalyzerService aiContentAnalyzer, string accountIdentifier, string locationName)
    {
        _reviewsAppService = reviewsAppService;
        _aiContentAnalyzer = aiContentAnalyzer;
        _accountIdentifier = accountIdentifier;
        _locationName = locationName;
    }

    public async Task RespondToNewReviewsAsync()
    {
        var reviews = await _reviewsAppService.GetReviewsAsync(_accountIdentifier, _locationName);
        foreach (var review in reviews)
        {
            // Analizar sentimiento
            var sentimentResult = await _aiContentAnalyzer.AnalyzeSentimentAsync(review.Comment);

            string replyMessage;
            if (sentimentResult.Sentiment == "positive")
            {
                replyMessage = "¡Muchas gracias por tu reseña positiva! Esperamos servirte de nuevo pronto.";
            }
            else if (sentimentResult.Sentiment == "negative")
            {
                replyMessage = "Lamentamos mucho que tu experiencia no haya sido la mejor. Por favor, contáctanos por mensaje privado para solucionarlo.";
            }
            else
            {
                replyMessage = "Gracias por tomarte el tiempo de dejarnos tu opinión. Trabajamos continuamente para mejorar.";
            }

            // Responder a la reseña
            await _reviewsAppService.ReplyToReviewAsync(_accountIdentifier, review.Name, replyMessage);
        }
    }
}

Recibir Notificaciones en Tiempo Real (Pub/Sub)

  • Configura un topic en Google Pub/Sub para recibir notificaciones de nuevas reseñas.
  • Crea un suscriptor HTTP (un endpoint en tu aplicación) que reciba mensajes POST.
  • El mensaje contendrá información para identificar la reseña o el evento.
  • En el handler de ese endpoint (por ejemplo, un controlador en AspNetBoilerplate), cuando recibes el mensaje, llamas a RespondToNewReviewsAsync() o una lógica más específica que obtenga la reseña correspondiente y la responda.

Ejemplo esquemático de un controlador:

[Route("api/pubsub")]
public class PubSubController : Controller
{
    private readonly AutoResponderService _autoResponderService;

    public PubSubController(AutoResponderService autoResponderService)
    {
        _autoResponderService = autoResponderService;
    }

    [HttpPost("messages")]
    public async Task<IActionResult> ReceiveMessage([FromBody] PubSubMessage message)
    {
        // message contiene información codificada. Por simplicidad, asumamos que indica una nueva reseña.
        
        await _autoResponderService.RespondToNewReviewsAsync();

        // Responder 200 OK para que Pub/Sub sepa que el mensaje fue procesado.
        return Ok();
    }
}

// DTO de Pub/Sub (ejemplo simplificado)
public class PubSubMessage
{
    [Newtonsoft.Json.JsonProperty("message")]
    public PubSubInnerMessage Message { get; set; }
}

public class PubSubInnerMessage
{
    [Newtonsoft.Json.JsonProperty("data")]
    public string Data { get; set; } // Base64 string con info del evento.
}

En la vida real, deberás decodificar la información del mensaje, identificar la reseña exacta que cambió y procesarla. La lógica presentada es simplificada.


En Conclusión:
Este código muestra un posible camino para:

  • Obtener tokens con IExternalTokenService.
  • Llamar a las APIs de Google Business Profile para obtener cuentas, ubicaciones, reseñas.
  • Responder automáticamente a reseñas.
  • Integrar un endpoint Pub/Sub para recibir notificaciones de cambios.

Deberás ajustar y expandir este ejemplo según las necesidades específicas de tu proyecto (manejo de errores, logging, pruebas, seguridad adicional, etc.).