Cómo integrar Azure Text Analytics en una aplicación de análisis de feedback
Daniel J. Saldaña
- 24 de marzo de 2024
- Puntuación de feedback

En la era digital, comprender las percepciones y emociones de los usuarios es esencial para mejorar productos y servicios. Microsoft Azure Text Analytics ofrece una solución robusta para analizar el sentimiento y las frases clave de los textos. En este post, te guiaré a través de una prueba de concepto para integrar Azure Text Analytics en una aplicación de análisis de feedback.
Paso 1: Configurar el entorno de desarrollo
Antes de comenzar, asegúrate de tener las siguientes herramientas y servicios configurados:
- Node.js y npm instalados en tu máquina local.
- Una cuenta de Azure con acceso al servicio Text Analytics.
- Un entorno de base de datos PostgreSQL.
Paso 2: Preparar el frontend
Nuestra aplicación de feedback se construirá usando React. Crearemos un componente para enviar feedback y visualizar el análisis de sentimientos.
Crea un nuevo proyecto React: Utiliza
create-react-app
para configurar tu entorno de frontend.Integra el componente AnalyzeFeedback: Este componente es responsable de enviar el título del feedback y mostrar los resultados del análisis.
Archivo de ejemplo a integrar:
'use client'
import { useEffect, useState } from 'react';import styles from './AnalyzeFeedback.module.scss';import ClassName from '@/types/ClassName';
interface AnalyzeFeedbackProps extends ClassName { title: string;}
const AnalyzeFeedback = ({ className, title }: AnalyzeFeedbackProps) => { const [score, setScore] = useState<number | null>(null); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { setIsLoading(true); fetch('https://api.danieljsaldana.dev/api/getfeedbackscores', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title }), }) .then(response => response.json()) .then(data => { if (data.compositeScore !== undefined) { setScore(data.compositeScore); } else { setScore(null); } }) .catch(() => setScore(null)) .finally(() => setIsLoading(false)); }, [title]);
const getBackgroundColor = (score: number | null) => { if (score === null) { return '#d3d3d3'; } else if (score > 80) { return '#0ac769'; } else if (score >= 50 && score <= 79) { return '#ffad00'; } else { return '#ea001e'; } };
const backgroundColor = getBackgroundColor(score); const circumference = 301.59289474462014; const offset = score !== null ? ((100 - score) / 100) * circumference : 0;
return ( <div className={`rounded-full h-[24px] w-[22px] ${className ?? ''}`}> <svg width="24" height="24" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> <g shapeRendering="geometricPrecision"> <circle cx="64" cy="64" fill={backgroundColor} r="64" /> <circle cx="64" cy="64" fill="none" r="48" stroke="rgba(0,0,0,.1)" strokeWidth="10" /> {!isLoading && score !== null && ( <circle cx="64" cy="64" fill="none" r="48" stroke="white" strokeDasharray={`${circumference} ${circumference}`} strokeDashoffset={offset} strokeLinecap="round" strokeLinejoin="round" strokeWidth="10" className="score_progress__quTNG" /> )} {(isLoading || score === null) && ( <svg className={`${styles.withIconIcon__MHUeb} ${styles.fadeIn}`} data-testid="geist-icon" fill="none" height="70" shapeRendering="geometricPrecision" stroke="#999" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="70" x="29" y="29"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> )} {!isLoading && score !== null && ( <text fill="white" fontSize="42" fontWeight="500" textAnchor="middle" x="64" y="79" > {score} </text> )} </g> </svg> </div> );};
export default AnalyzeFeedback;
Estiliza tu componente: Usa los estilos predefinidos para hacer que tu componente sea visualmente atractivo y claro para los usuarios.
Archivo de ejemplo a integrar:
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; }}
.fadeIn { animation: fadeIn ease-in 1s; animation-fill-mode: forwards;}
Paso 3: Configurar el backend
El backend de nuestra aplicación se construirá con Node.js y se comunicará con los servicios de Azure para analizar el feedback.
Establece la API de análisis de feedback: Crea una API que reciba el feedback, analice el sentimiento y las frases clave usando Azure Text Analytics, e inserte los resultados en la base de datos.
Archivos de ejemplo a integrar:
analyzeFeedback.js
import { TextAnalyticsClient, AzureKeyCredential } from '@azure/ai-text-analytics';import { enableCors } from '@/src/middleware/enableCors';import { methodValidator } from '@/src/utils/methodValidator';import { insertFeedback } from '@/src/core/insertFeedback';import dotenv from 'dotenv';
dotenv.config();
const textAnalyticsClient = new TextAnalyticsClient( process.env.AZURE_TEXT_ANALYTICS_ENDPOINT, new AzureKeyCredential(process.env.AZURE_TEXT_ANALYTICS_KEY));
async function analyzeFeedback(req, res) { try { await methodValidator(req, res, 'POST'); if (res.headersSent) return;
const { title, text, user } = req.body; if (!title || !text || !user) { return res.status(400).json({ error: 'Faltan datos requeridos para el análisis' }); }
const sentimentResult = await textAnalyticsClient.analyzeSentiment([text]); const sentiment = sentimentResult[0];
const keyPhrasesResult = await textAnalyticsClient.extractKeyPhrases([text]); const keyPhrases = keyPhrasesResult[0];
await insertFeedback( title, sentiment.sentiment, sentiment.confidenceScores.positive, sentiment.confidenceScores.neutral, sentiment.confidenceScores.negative, user );
res.status(200).json({ title, user, sentiment: sentiment.sentiment, confidenceScores: sentiment.confidenceScores, keyPhrases: keyPhrases.keyPhrases, }); } catch (error) { console.error('Error al analizar el feedback:', error); res.status(500).json({ error: `Error al analizar el feedback: ${error.message}` }); }}
export default enableCors(analyzeFeedback);
Desarrolla la API para obtener puntajes de feedback: Esta API recupera los promedios de los puntajes de feedback basados en el título proporcionado.
Archivos de ejemplo a integrar:
import { enableCors } from '@/src/middleware/enableCors';import { methodValidator } from '@/src/utils/methodValidator';import { sanitizeTitleForFilename } from '@/src/utils/sanitizeTitleForFilename';import { getAverageFeedbackScores } from '@/src/core/getAverageFeedbackScores';import dotenv from 'dotenv';
dotenv.config();
export async function getFeedbackScores(req, res) { await methodValidator(req, res, 'POST'); if (res.headersSent) return;
const { title } = req.body; if (!title) { return res.status(400).json({ error: 'Falta el título para el análisis' }); }
const sanitizedTitle = sanitizeTitleForFilename(title); console.log('Título sanitizado:', sanitizedTitle);
try { const averages = await getAverageFeedbackScores(sanitizedTitle);
if ( !averages || (averages.average_positive === 0 && averages.average_neutral === 0 && averages.average_negative === 0) ) { return res .status(200) .json({ error: 'No se encontraron datos para el título proporcionado', compositeScore: null }); }
const avgPositive = parseFloat(averages.average_positive); const avgNeutral = parseFloat(averages.average_neutral); const avgNegative = parseFloat(averages.average_negative);
console.log('Promedios convertidos:', avgPositive, avgNeutral, avgNegative);
let compositeScore = avgPositive + 0.5 * avgNeutral - avgNegative; compositeScore = Math.max(0, Math.min(1, compositeScore)) * 100;
compositeScore = Math.round(compositeScore);
console.log('Puntuación compuesta:', compositeScore); res.status(200).json({ compositeScore: compositeScore }); } catch (error) { console.error('Error al obtener los promedios del feedback:', error); res.status(500).json({ error: `Error al obtener los promedios del feedback: ${error.message}` }); }}
export default enableCors(getFeedbackScores);
Implementa funciones de base de datos: Usa estas funciones para interactuar con tu base de datos PostgreSQL y manejar la inserción y recuperación de datos de feedback.
Archivos de ejemplo a integrar:
import { sql } from '@vercel/postgres';
export async function getAverageFeedbackScores(title) { try { const result = await sql` SELECT AVG(positive) AS average_positive, AVG(neutral) AS average_neutral, AVG(negative) AS average_negative FROM post_feedback WHERE title = ${title} GROUP BY title; `;
if (result.rows && result.rows.length > 0) { return result.rows[0]; } else { return { average_positive: 0, average_neutral: 0, average_negative: 0 }; } } catch (error) { console.error(`Error al obtener los promedios de feedback para el título: ${title}`, error); throw error; }}
y
import { sql } from '@vercel/postgres';
export async function insertFeedback(title, sentiment, positive, neutral, negative, user) { console.log(`Insertando feedback para el título: ${title}`);
try { await sql` INSERT INTO post_feedback (title, sentiment, positive, neutral, negative, "user", last_updated) VALUES (${title}, ${sentiment}, ${positive}, ${neutral}, ${negative}, ${user}, CURRENT_TIMESTAMP) ON CONFLICT (title, "user") -- Actualiza esto para que coincida con la nueva restricción única DO UPDATE SET sentiment = EXCLUDED.sentiment, positive = EXCLUDED.positive, neutral = EXCLUDED.neutral, negative = EXCLUDED.negative, last_updated = CURRENT_TIMESTAMP; `; console.log(`Feedback insertado con éxito para el título: ${title}`); } catch (error) { console.error(`Error al insertar feedback para el título: ${title}`, error); throw new Error(`Error al insertar feedback: ${error.message}`); }}
Paso 4: Configuración de la base de datos
Antes de que puedas comenzar a almacenar y recuperar datos de feedback, necesitas configurar tu base de datos PostgreSQL. Usa la siguiente query SQL para crear la tabla necesaria para almacenar los datos de feedback:
CREATE TABLE post_feedback ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, sentiment VARCHAR(50) NOT NULL, positive NUMERIC NOT NULL, neutral NUMERIC NOT NULL, negative NUMERIC NOT NULL, "user" VARCHAR(255) NOT NULL, last_updated TIMESTAMP NOT NULL, UNIQUE(title, "user"));
Esta tabla almacenará cada pieza de feedback junto con el análisis de sentimientos y la marca de tiempo de la última actualización. Asegúrate de que tu base de datos esté en funcionamiento y accesible desde tu backend antes de continuar.
Paso 5: Prueba de concepto
Para probar la integración completa de tu sistema, utiliza Postman para simular solicitudes de cliente a tu backend. A continuación, te proporciono una guía para configurar las solicitudes en Postman:
Configuración en Postman:
POST para analizar feedback:
- URL:
http://localhost:3000/analyzeFeedback
(ajusta el puerto según tu configuración). - Método: POST
- Body: raw, JSON{"title": "Ejemplo de Título","text": "Ejemplo de feedback del usuario","user": "usuario_ejemplo"}
- URL:
POST para obtener puntajes de feedback:
- URL:
http://localhost:3000/getFeedbackScores
- Método: POST
- Body: raw, JSON{"title": "Ejemplo de Título"}
- URL:
Asegúrate de tener tu servidor backend en ejecución antes de enviar las solicitudes desde Postman. Estas solicitudes simularán la interacción entre el frontend y el backend, permitiéndote evaluar la funcionalidad completa de tu aplicación de análisis de feedback.
Después de configurar y ejecutar estas pruebas, deberías poder ver cómo tu aplicación procesa y responde al feedback, cómo se almacena en la base de datos y cómo se recuperan y calculan los promedios de los puntajes de sentimiento. Esto completará tu prueba de concepto, demostrando la funcionalidad y la integración entre todas las partes de tu aplicación.
Y con esto, has completado la integración de Azure Text Analytics en tu aplicación de análisis de feedback. ¡Felicidades por llegar hasta aquí! Si tienes alguna pregunta o comentario, no dudes en dejarlo a continuación.