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ónGoogleTokenService
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.).