En el bucket de datos de AlpinaShop hay 8.000 opiniones de clientes escritas en texto libre, ya desidentificadas con Sensitive Data Protection en 04-07. Cinco años de gente contando qué le pareció una mochila, si la chaqueta abriga, si la talla venía justa, si el envío llegó roto.

Nadie las ha leído. Ni una.

No por dejadez: leer 8.000 opiniones a treinta segundos cada una son casi setenta horas de trabajo, y al terminar tendrías una impresión general que no podrías cruzar con nada. Las opiniones existen, se muestran en la ficha de producto, generan la puntuación media en estrellas, y ahí acaba su vida útil. Toda la información sobre por qué un producto gusta o no gusta está encerrada en un texto que ningún proceso toca.

La lección anterior terminó construyendo un modelo desde cero, con contenedores, GPU y tres semanas de A/B test. Esta empieza con el principio contrario, que es el que ahorra más dinero en la práctica: no entrenes lo que ya está entrenado.

Contenido

  1. APIs preentrenadas frente a modelos propios
  2. Qué ofrece exactamente la API de Natural Language
  3. score y magnitude: donde todo el mundo se equivoca
  4. Análisis de entidades y sentimiento de entidades
  5. Clasificación de contenido y análisis sintáctico
  6. Primera llamada desde Python
  7. Procesar las 8.000 opiniones: lotes, cuotas y errores
  8. Coste real y cómo estimarlo antes de empezar
  9. De BigQuery a opiniones_sentimiento y vuelta
  10. La pregunta de negocio: ¿el mal sentimiento predice devoluciones?
  11. Idiomas: el español, el catalán y lo que hay que comprobar
  12. Los límites reales: ironía, negación y jerga de montaña
  13. Validar con una muestra etiquetada a mano
  14. Comparación honesta: API, Gemini o clasificador propio
  15. La familia completa: Translation, Speech-to-Text, Document AI
  16. Privacidad: qué se envía, qué se guarda y qué dice el RGPD

  1. APIs preentrenadas frente a modelos propios

Una API preentrenada es un modelo que alguien ya entrenó, con muchos más datos y recursos de los que tú vas a tener, expuesto como servicio. Tú envías texto y recibes una respuesta. No hay datos de entrenamiento, no hay etiquetado, no hay entrenamiento, no hay despliegue, no hay monitorización de deriva.

Criterio API preentrenada Modelo propio (AutoML o custom)
Datos necesarios Ninguno Cientos o miles de ejemplos etiquetados
Tiempo hasta producción Una tarde Semanas
Coste inicial 0 Etiquetado + entrenamiento
Coste por uso Por unidad procesada Endpoint o lotes
Calidad en tareas genéricas Alta Similar, con mucho más esfuerzo
Calidad en jerga propia Media o baja Alta si hay datos
Mantenimiento Ninguno Reentrenamiento y vigilancia
Control Ninguno Total

La regla práctica se puede formular en una pregunta: ¿es mi problema esencialmente distinto del de cualquier otra empresa? Detectar si un texto en español es positivo o negativo no lo es: es exactamente el mismo problema para AlpinaShop, para una pizzería y para un banco. Saber si una opinión se refiere al ajuste del arnés o a la rigidez de la suela sí es específico.

Y el orden de trabajo que se deriva es claro: empieza siempre por la API preentrenada. Si resuelve el problema, has terminado en una tarde. Si no lo resuelve, ya sabes exactamente en qué falla, y ese conocimiento es justo lo que necesitas para decidir si entrenar algo propio merece la pena.

  1. Qué ofrece exactamente la API de Natural Language

La Cloud Natural Language API expone cinco funciones. Conviene conocerlas todas porque tres de ellas se ignoran sistemáticamente y son muy útiles.

Función Qué recibe Qué devuelve Uso en AlpinaShop
Análisis de sentimiento Un texto score y magnitude del texto y de cada frase Cómo de positiva es cada opinión
Análisis de entidades Un texto Personas, lugares, productos, cantidades, con relevancia Qué se menciona en las opiniones
Sentimiento de entidades Un texto Cada entidad con su propio sentimiento "La mochila bien, el envío fatal"
Clasificación de contenido Un texto (mínimo unas 20 palabras) Categorías de una taxonomía general Clasificar textos largos por tema
Análisis sintáctico Un texto Tokens, lemas, categorías gramaticales, dependencias Normalizar y extraer patrones

La tercera —sentimiento de entidades— es la joya escondida y la que más valor tiene para una tienda. Una opinión real casi nunca es "buena" o "mala" a secas: es "la chaqueta es fantástica pero tardó tres semanas en llegar". El sentimiento global de ese texto sale cercano a cero, lo que es a la vez cierto e inútil. El sentimiento por entidad separa que el producto gusta y que la logística no.

  1. score y magnitude: donde todo el mundo se equivoca

Este es el apartado más importante de la lección, y el que más malentendidos genera en proyectos reales.

El análisis de sentimiento devuelve dos números, no uno:

  • score: entre −1,0 y +1,0. Indica la orientación emocional global: negativa, neutra o positiva.
  • magnitude: de 0,0 a infinito. Indica la cantidad total de carga emocional del texto, y no está normalizada por longitud: un texto largo acumula más magnitud.

El error universal es mirar solo el score. Y el problema es que un score cercano a 0 tiene dos significados completamente opuestos, que solo se distinguen mirando la magnitude.

score magnitude Interpretación Ejemplo
+0,8 0,9 Claramente positivo "Mochila perfecta, muy cómoda."
−0,7 0,8 Claramente negativo "Se descosió a la semana. Fatal."
0,0 0,1 Neutro de verdad "Mochila de 40 litros, color azul."
0,0 3,4 Mixto, muy emocional "El producto es magnífico, pero la atención al cliente ha sido lamentable y el envío tardó tres semanas."

Las dos últimas filas son la clave. Ambas tienen score 0. La primera es una descripción sin emoción. La segunda es una opinión intensa con emociones fuertes en las dos direcciones que se anulan al promediarse.

Y son casos que exigen acciones opuestas. La opinión neutra no requiere nada. La opinión mixta contiene un problema serio de atención al cliente y de logística que hay que atender hoy. Un análisis que solo mire el score clasificará ambas como "neutras", ignorará la segunda, y el equipo concluirá que "la mayoría de las opiniones son neutras, aquí no hay nada".

La forma correcta de clasificar usa las dos dimensiones:

CREATE OR REPLACE VIEW `alpinashop-datos.alpinashop_analitica.v_opiniones_clasificadas` AS
SELECT
  opinion_id, sku, fecha, puntuacion, score, magnitude,
  CASE
    WHEN score >=  0.25                     THEN 'positiva'
    WHEN score <= -0.25                     THEN 'negativa'
    WHEN magnitude >= 1.5                   THEN 'mixta'      -- emocion alta, score bajo
    ELSE                                         'neutra'
  END AS clasificacion
FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento`;

Los umbrales 0,25 y 1,5 no son sagrados. Son un punto de partida razonable que hay que calibrar contra una muestra etiquetada a mano —apartado 13—, exactamente igual que el umbral de decisión de 05-02: es un ajuste de negocio, no una constante universal.

La magnitude no es comparable entre textos de longitudes muy distintas. Una opinión de 300 palabras tendrá más magnitud que una de 20 aunque sea menos intensa. Si hace falta comparar, conviene normalizar dividiendo por el número de frases, o al menos segmentar el análisis por rangos de longitud.

Y una precisión que evita conclusiones absurdas: el sentimiento no mide veracidad ni gravedad. Un cliente muy educado que escribe "lamentablemente el mosquetón cedió durante una vía" puede dar un score moderado, y es un incidente de seguridad. El sentimiento ordena y prioriza; no sustituye a leer lo importante.

  1. Análisis de entidades y sentimiento de entidades

El análisis de entidades identifica lo que se menciona en el texto y le asigna un tipo (PERSON, LOCATION, ORGANIZATION, CONSUMER_GOOD, EVENT, NUMBER, PRICE, OTHER) y una relevancia (salience) entre 0 y 1 que indica su importancia dentro del texto.

El sentimiento de entidades combina ambas cosas. Para la opinión:

"La chaqueta es impermeable de verdad y muy ligera, pero la cremallera se atasca y el envío tardó tres semanas."

Un resultado plausible:

Entidad Tipo Relevancia score magnitude
chaqueta CONSUMER_GOOD 0,52 +0,8 1,2
cremallera CONSUMER_GOOD 0,24 −0,6 0,7
envío OTHER 0,18 −0,7 0,9

Esto es accionable y el sentimiento global no lo era. El sentimiento global de esa opinión rondaría 0 con magnitud alta —"mixta"—, correcto pero mudo. El desglose por entidad dice tres cosas concretas: el producto cumple su promesa principal, hay un defecto de componente que el proveedor debe corregir, y hay un problema logístico.

Agregando 8.000 opiniones por entidad se obtiene lo que ninguna puntuación en estrellas da: qué falla exactamente y con qué frecuencia.

-- Que se menciona con peor sentimiento en las opiniones de mochilas
SELECT
  entidad,
  COUNT(*)                                  AS menciones,
  ROUND(AVG(score), 3)                      AS score_medio,
  ROUND(AVG(salience), 3)                   AS relevancia_media,
  COUNTIF(score < -0.3)                     AS menciones_negativas
FROM `alpinashop-datos.alpinashop_analitica.opiniones_entidades` e
JOIN `alpinashop-datos.alpinashop_analitica.productos` p USING (sku)
WHERE p.categoria = 'mochila'
GROUP BY entidad
HAVING menciones >= 20
ORDER BY score_medio ASC
LIMIT 20;

El HAVING menciones >= 20 importa: una entidad mencionada tres veces con sentimiento pésimo es una anécdota, no un patrón. Es la misma disciplina del soporte en el recomendador heurístico de 05-01.

  1. Clasificación de contenido y análisis sintáctico

Clasificación de contenido asigna el texto a categorías de una taxonomía general y jerárquica (/Sports/Outdoors/Climbing & Mountaineering). Requiere textos con cierta longitud —del orden de veinte palabras o más— y su taxonomía es general, no la de tu tienda. Para AlpinaShop tiene poco valor en las opiniones, que son cortas, pero sí lo tiene para clasificar automáticamente artículos del blog o descripciones largas.

Análisis sintáctico descompone el texto en tokens con su lema, categoría gramatical y relación de dependencia. Rara vez se usa tal cual, pero tiene dos aplicaciones prácticas muy concretas:

  • Lematización: agrupar "pesa", "pesaba", "pesado" bajo el lema "pesar" antes de contar frecuencias. Sin esto, un recuento de palabras se dispersa en variantes.
  • Extracción de patrones adjetivo-sustantivo: "correa incómoda", "tejido resistente", "costura floja". Un recuento de esos pares sobre 8.000 opiniones produce un resumen de defectos muy denso, y sin ningún modelo entrenado.

  1. Primera llamada desde Python

Preparación previa: habilitar la API con gcloud services enable language.googleapis.com --project=alpinashop-datos y crear la cuenta de servicio sa-nlp-opiniones. Invocar la API no requiere un rol específico; los permisos que hay que conceder son los de lectura y escritura en BigQuery (roles/bigquery.dataEditor).

Y la llamada:

from google.cloud import language_v2

cliente = language_v2.LanguageServiceClient()

texto = ("La chaqueta es impermeable de verdad y muy ligera, "
         "pero la cremallera se atasca y el envio tardo tres semanas.")

documento = language_v2.Document(
    content=texto,
    type_=language_v2.Document.Type.PLAIN_TEXT,
    language_code="es",          # forzar el idioma evita detecciones erroneas
)

r = cliente.analyze_sentiment(
    request={"document": documento,
             "encoding_type": language_v2.EncodingType.UTF8})

print(f"Global -> score {r.document_sentiment.score:+.2f} "
      f"magnitude {r.document_sentiment.magnitude:.2f}")

for frase in r.sentences:                    # desglose por frase, sin coste extra
    print(f"  [{frase.sentiment.score:+.2f}] {frase.text.content}")

e = cliente.analyze_entities(                # entidades: segunda facturacion
    request={"document": documento,
             "encoding_type": language_v2.EncodingType.UTF8})

Tres detalles que importan más de lo que parecen:

language_code="es" explícito. Si no se indica, la API detecta el idioma. En textos cortos la detección falla con frecuencia, y confundir español con portugués o con catalán degrada el resultado sin avisar de nada. Si conoces el idioma, decláralo.

encoding_type=UTF8. Determina cómo se cuentan las posiciones de los caracteres en la respuesta. Con acentos y ñ —o sea, siempre en español— usar el valor incorrecto desplaza los desplazamientos que devuelve la API y rompe cualquier lógica que extraiga fragmentos.

El análisis por frase (r.sentences) es gratuito: viene en la misma respuesta y en la misma llamada. Guardarlo permite localizar exactamente qué frase de una opinión larga es la negativa, sin volver a llamar.

  1. Procesar las 8.000 opiniones: lotes, cuotas y errores

La API procesa un documento por llamada. Ocho mil opiniones son ocho mil llamadas, y ahí es donde un script ingenuo se estrella.

Los tres problemas que hay que resolver: cuotas (hay un límite de peticiones por minuto que, si se supera, devuelve error 429), errores transitorios (503, cortes de red) y coste (cada llamada cuenta).

import time, random
from concurrent.futures import ThreadPoolExecutor
from google.cloud import language_v2
from google.api_core import exceptions

cliente = language_v2.LanguageServiceClient()

def analizar(opinion, max_intentos=5):
    """Analiza una opinion con reintentos y espera exponencial."""
    doc = language_v2.Document(
        content=opinion["texto"][:20000],   # recortar textos anomalos
        type_=language_v2.Document.Type.PLAIN_TEXT,
        language_code=opinion.get("idioma", "es"),
    )
    for intento in range(max_intentos):
        try:
            r = cliente.analyze_sentiment(
                request={"document": doc,
                         "encoding_type": language_v2.EncodingType.UTF8})
            return {
                "opinion_id": opinion["opinion_id"],
                "sku":        opinion["sku"],
                "score":      round(r.document_sentiment.score, 4),
                "magnitude":  round(r.document_sentiment.magnitude, 4),
                "n_frases":   len(r.sentences),
                "estado":     "OK",
            }
        except exceptions.ResourceExhausted:            # 429: cuota
            espera = (2 ** intento) + random.random()
            time.sleep(espera)
        except exceptions.ServiceUnavailable:           # 503: transitorio
            time.sleep(1 + intento)
        except exceptions.InvalidArgument as err:       # texto no procesable
            return {"opinion_id": opinion["opinion_id"], "sku": opinion["sku"],
                    "score": None, "magnitude": None, "n_frases": 0,
                    "estado": f"ERROR: {err.message[:120]}"}
    return {"opinion_id": opinion["opinion_id"], "sku": opinion["sku"],
            "score": None, "magnitude": None, "n_frases": 0,
            "estado": "ERROR: agotados los reintentos"}


with ThreadPoolExecutor(max_workers=8) as pool:
    resultados = list(pool.map(analizar, opiniones))

Las decisiones de este código, una a una:

Espera exponencial con ruido (2 ** intento + random()). Si ocho hilos reciben un 429 a la vez y todos reintentan al segundo exacto, vuelven a chocar. El componente aleatorio los desincroniza. Es el mismo patrón de reintentos de Pub/Sub en 04-04.

Distinguir tipos de error. ResourceExhausted es cuota y se reintenta esperando más. ServiceUnavailable es transitorio y se reintenta rápido. InvalidArgument es un texto que la API no puede procesar —vacío, en un idioma no soportado, demasiado largo— y reintentarlo es tiempo y dinero perdidos: falla siempre igual.

Registrar el fallo en lugar de abortar. El resultado siempre trae fila, con estado. Que un proceso de 8.000 documentos muera en el 7.400 porque uno venía vacío es inaceptable. Al final se cuenta cuántos fallaron y por qué.

max_workers=8, no 100. Con demasiada concurrencia se agota la cuota constantemente, todo entra en reintentos y el proceso acaba siendo más lento además de más caro. Ocho hilos es un punto de partida conservador que conviene subir midiendo.

Recortar el texto. La API tiene un límite de tamaño por documento. Un texto anómalo de 500 KB —que los hay: alguien pega algo por error— falla y consume cuota. Cortar a 20.000 caracteres es una defensa barata.

  1. Coste real y cómo estimarlo antes de empezar

La facturación de la API de Natural Language se hace por unidades de 1.000 caracteres, redondeando hacia arriba por documento y por cada función solicitada.

Las dos consecuencias prácticas:

  • Una opinión de 150 caracteres factura 1 unidad, igual que una de 990. El redondeo por documento penaliza los textos cortos.
  • Pedir sentimiento y entidades sobre el mismo texto son dos facturaciones, no una.

Estimación antes de gastar nada, calculada en SQL sobre los datos reales:

SELECT
  COUNT(*)                                             AS documentos,
  ROUND(AVG(LENGTH(texto)), 0)                         AS long_media,
  MAX(LENGTH(texto))                                   AS long_maxima,
  SUM(CEIL(LENGTH(texto) / 1000))                      AS unidades_sentimiento,
  SUM(CEIL(LENGTH(texto) / 1000)) * 2                  AS unidades_con_entidades
FROM `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica`;

Un resultado típico para AlpinaShop: 8.000 opiniones, longitud media de unos 240 caracteres, casi todas por debajo de 1.000. Eso son unas 8.000 unidades para sentimiento, o 16.000 si además se piden entidades.

Con precios del orden de uno o dos euros por cada 1.000 unidades —un orden de magnitud, el precio vigente hay que consultarlo siempre en la documentación oficial, y suele haber un tramo gratuito mensual—, el análisis completo del histórico de AlpinaShop cuesta del orden de decenas de euros, una sola vez.

Pon ese número al lado del coste de entrenar un clasificador propio: etiquetar 2.000 opiniones a mano, entrenar, desplegar, mantener. La comparación se responde sola, y es exactamente el argumento del apartado 1.

Y el flujo incremental, que es lo que hace sostenible el gasto: el histórico se procesa una vez; a partir de ahí solo se analizan las opiniones nuevas, unas pocas decenas al día. El coste recurrente es prácticamente cero.

-- Solo lo que aun no se ha analizado
SELECT o.opinion_id, o.sku, o.texto
FROM `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` o
LEFT JOIN `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` s
  USING (opinion_id)
WHERE s.opinion_id IS NULL;

  1. De BigQuery a opiniones_sentimiento y vuelta

La arquitectura del proceso, con las piezas que ya conoces del módulo 4:

flowchart LR
    A[v_opiniones_analitica<br/>BigQuery] --> B[Proceso Python<br/>lotes + reintentos]
    B --> C[Natural Language API]
    C --> B
    B --> D[opiniones_sentimiento<br/>BigQuery]
    D --> E[Cruce con pedidos<br/>y devoluciones]
    E --> F[Looker Studio]
    G[Cloud Scheduler] --> H[Workflows] --> B

La tabla de resultados, con particionado y clustering como manda 04-01:

CREATE TABLE IF NOT EXISTS `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` (
  opinion_id      STRING  NOT NULL,
  sku             STRING  NOT NULL,
  fecha           DATE,
  score           FLOAT64,
  magnitude       FLOAT64,
  n_frases        INT64,
  idioma          STRING,
  estado          STRING,
  modelo_version  STRING,          -- trazabilidad: que analizo esto
  procesado_en    TIMESTAMP
)
PARTITION BY fecha
CLUSTER BY sku;

Las dos columnas que casi nadie incluye y siempre acaban haciendo falta son modelo_version y procesado_en. Las APIs preentrenadas se actualizan por su cuenta: el modelo que analiza tus textos hoy no es necesariamente el de hace un año. Si dentro de seis meses los score medios se mueven, la primera pregunta será "¿cambió el modelo o cambiaron los clientes?", y sin esas dos columnas es imposible responder.

La escritura se hace con insert_rows_json en lotes de unas 500 filas, añadiendo a cada resultado modelo_version y procesado_en antes de insertar, y comprobando siempre la lista de errores que devuelve la llamada (que no lanza excepción: si no la miras, las filas rechazadas se pierden en silencio).

La ejecución periódica se engancha al orquestador que ya eligió AlpinaShop en 04-06: Cloud Scheduler dispara un Workflow que ejecuta el proceso incremental cada noche.

  1. La pregunta de negocio: ¿el mal sentimiento predice devoluciones?

Aquí es donde el análisis deja de ser un ejercicio y empieza a valer dinero.

WITH sentimiento_sku AS (
  SELECT
    sku,
    COUNT(*)                                       AS n_opiniones,
    ROUND(AVG(score), 3)                           AS score_medio,
    ROUND(AVG(magnitude), 3)                       AS magnitude_media,
    COUNTIF(score < -0.25)                         AS opiniones_negativas,
    ROUND(100 * COUNTIF(score < -0.25) / COUNT(*), 1) AS pct_negativas
  FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento`
  WHERE estado = 'OK'
  GROUP BY sku
  HAVING n_opiniones >= 15
),
devoluciones_sku AS (
  SELECT
    sku,
    COUNT(*)                                       AS unidades_vendidas,
    COUNTIF(devuelto)                              AS unidades_devueltas,
    ROUND(100 * COUNTIF(devuelto) / COUNT(*), 2)   AS tasa_devolucion
  FROM `alpinashop-datos.alpinashop_analitica.lineas_pedido`
  WHERE fecha_pedido >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 YEAR)
  GROUP BY sku
  HAVING unidades_vendidas >= 50
)
SELECT
  s.sku, p.nombre, p.categoria,
  s.n_opiniones, s.score_medio, s.pct_negativas,
  d.unidades_vendidas, d.tasa_devolucion,
  ROUND(d.unidades_devueltas * p.precio_medio, 0) AS coste_devoluciones_eur
FROM sentimiento_sku s
JOIN devoluciones_sku d USING (sku)
JOIN `alpinashop-datos.alpinashop_analitica.productos` p USING (sku)
ORDER BY d.tasa_devolucion DESC
LIMIT 30;

Y la correlación global, que es la que contesta la pregunta:

SELECT
  COUNT(*)                                    AS skus_analizados,
  ROUND(CORR(score_medio, tasa_devolucion), 3) AS correlacion
FROM sentimiento_sku JOIN devoluciones_sku USING (sku);

Cómo leer el resultado, sin engañarse.

Si la correlación sale en torno a −0,4, significa que a peor sentimiento, mayor tasa de devolución, con una relación moderada. Eso ya es útil: el sentimiento sirve como señal de alerta temprana. Un producto nuevo con quince opiniones de sentimiento malo probablemente va a generar devoluciones antes de que las devoluciones aparezcan en los datos.

Si sale cerca de 0, la conclusión no es que el análisis no valga: es que las devoluciones de AlpinaShop se explican por otra cosa —lo más probable en ropa técnica, la talla—, y eso es también un hallazgo valioso que cambia dónde hay que actuar.

Y las tres cautelas obligatorias:

  1. Correlación no es causalidad. Puede haber una variable de fondo —la categoría— que explique ambas: si la ropa se devuelve más y además genera opiniones más críticas, la correlación aparece sin que una cosa cause la otra. La comprobación es repetir el análisis dentro de cada categoría.
  2. Sesgo de selección en quien opina. Solo escribe una parte de los clientes, y suele estar sobrerrepresentada la gente muy contenta y la muy enfadada. El sentimiento medio de las opiniones no es el sentimiento medio de los clientes.
  3. El filtro n_opiniones >= 15 deja fuera la mayoría del catálogo. Con 2.400 SKU y 8.000 opiniones, la media es de tres opiniones por producto: el análisis por SKU solo es fiable para los más vendidos. Para el resto, agregar por categoría o por familia es la única lectura honesta.

La salida práctica es una lista de trabajo, igual que en 05-02: los productos con peor sentimiento y mayor coste de devoluciones, ordenados, para que alguien mire las opiniones concretas y decida si hay que hablar con el proveedor, corregir la ficha o cambiar la tabla de tallas.

  1. Idiomas: el español, el catalán y lo que hay que comprobar

La API soporta un conjunto amplio de idiomas, pero no todas las funciones soportan los mismos idiomas ni con la misma calidad. Es una fuente habitual de sorpresas.

Aspecto Situación práctica
Español Soporte completo y calidad alta en todas las funciones
Catalán Cobertura menor que la del español; hay que verificar función por función en la documentación oficial
Detección automática Poco fiable en textos cortos, que es justo el caso de las opiniones
Textos mezclados Una opinión que alterna castellano y catalán da resultados inconsistentes

AlpinaShop es una pyme catalana con clientes en toda España, así que este punto no es teórico: parte de las opiniones estarán en catalán, y algunas mezclarán ambos idiomas en la misma frase.

La estrategia sensata, en tres pasos:

  1. Guardar el idioma declarado por el cliente en el formulario de opinión, si existe. Es la información más fiable y es gratis.
  2. Si no existe, detectarlo una vez y almacenarlo, en lugar de dejar que la API lo adivine en cada llamada.
  3. Comprobar la cobertura real antes de procesar en masa: coger 50 opiniones en catalán, analizarlas, y comparar con la valoración humana. Si el resultado no es fiable, hay dos salidas razonables: traducir al español con Cloud Translation antes de analizar —añade coste y algo de ruido, pero funciona— o usar Gemini (05-06), que maneja el catalán con soltura.

Y antes de nada, un GROUP BY idioma sobre v_opiniones_analitica para saber cuánto hay de cada cosa: evita construir una solución compleja para el 2 % de los casos.

  1. Los límites reales: ironía, negación y jerga de montaña

Una lección honesta tiene que decir dónde falla la herramienta.

Ironía y sarcasmo. "Genial, la tienda de campaña aguantó perfectamente… tres horas." El modelo ve "genial" y "perfectamente" y probablemente devuelve un score positivo. La ironía requiere entender la contradicción entre la forma y el contenido, y los modelos de sentimiento clásicos no la capturan de forma fiable.

Negaciones complejas. "No diría que sea una mala mochila, aunque tampoco es que me haya encantado." Doble negación más matización. El resultado será algo cercano a cero, que no es incorrecto pero pierde todo el matiz.

Jerga del dominio. Este es el más traicionero, porque falla silenciosamente y con textos perfectamente normales.

Frase Lectura genérica Lectura de montaña
"La mochila pesa" Negativo Depende: en una de expedición es esperable; en una de trail, un defecto grave
"El saco es justo para 0 grados" Ambiguo Advertencia: no cumple lo que promete
"Las botas son duras" Negativo Positivo en botas de crampón: la rigidez es el requisito
"El tejido cruje" Neutro Negativo: indica membrana rígida y ruidosa
"Técnica" Neutro Positivo: producto especializado

La cuarta y la quinta filas son el problema en su forma pura. La misma palabra tiene signo opuesto según el producto. Un modelo genérico no sabe de crampones, y "duras" le suena mal siempre.

Esto no invalida la herramienta: la acota. El análisis de sentimiento genérico sirve para priorizar y agregar —qué categorías van peor, qué productos concentran quejas, cómo evoluciona el sentimiento mes a mes—. No sirve para decidir automáticamente sobre un producto concreto sin que nadie lea nada.

  1. Validar con una muestra etiquetada a mano

Todo lo anterior lleva a una sola conclusión operativa: hay que medir cuánto acierta la API sobre tus textos. No sobre textos en general: sobre los tuyos.

El protocolo, que cuesta unas tres horas y vale por toda la lección:

  1. Muestrear 200 opiniones al azar, estratificando por categoría de producto y por puntuación en estrellas, para que la muestra no sea toda de mochilas ni toda de cinco estrellas.
  2. Etiquetarlas a mano en cuatro clases: positiva, negativa, neutra, mixta. Idealmente dos personas de forma independiente, para medir cuánto coinciden entre ellas: si dos humanos solo coinciden en el 75 %, no puedes exigirle el 95 % a la máquina.
  3. Comparar con la clasificación de la API usando los umbrales del apartado 3.
  4. Ajustar los umbrales para maximizar la concordancia. Puede que en tus textos el corte correcto sea 0,15 y no 0,25.
  5. Repetir la validación cada seis meses, porque el modelo subyacente cambia sin avisar.
-- Matriz de concordancia entre el etiquetado humano y la API
SELECT
  m.etiqueta_humana,
  c.clasificacion                            AS etiqueta_api,
  COUNT(*)                                   AS casos
FROM `alpinashop-datos.alpinashop_analitica.muestra_etiquetada` m
JOIN `alpinashop-datos.alpinashop_analitica.v_opiniones_clasificadas` c
  USING (opinion_id)
GROUP BY 1, 2
ORDER BY 1, casos DESC;

Un resultado plausible sobre las opiniones de AlpinaShop: buena concordancia en positivas y negativas claras, y la mayoría de los desacuerdos concentrados en la frontera entre "neutra" y "mixta" —justo donde el apartado 3 avisaba— y en las opiniones con jerga técnica.

Este ejercicio produce dos cosas de valor permanente. Una es el número que puedes decir a dirección: "la clasificación automática coincide con el criterio humano en el 82 % de los casos". La otra es un conjunto de 200 opiniones etiquetadas que, si algún día decides entrenar un clasificador propio con AutoML (05-02), ya es tu punto de partida.

  1. Comparación honesta: API, Gemini o clasificador propio

Tres formas de resolver el mismo problema en 2026:

Criterio Natural Language API Gemini (05-06) AutoML Text (05-02)
Datos propios Ninguno Ninguno Cientos de ejemplos etiquetados
Puesta en marcha Horas Horas Días o semanas
Salida Fija: score, magnitude, entidades La que pidas en el prompt Tus etiquetas
Jerga del dominio No la entiende Se le puede explicar en el prompt La aprende de los datos
Ironía Mal Bastante mejor Depende de los ejemplos
Catalán Cobertura limitada Buena Depende de los datos
Coste por documento Bajo y muy predecible Mayor, por tokens Bajo tras entrenar
Latencia Baja Mayor Baja
Determinismo Alto: misma entrada, misma salida Variable Alto

Cuándo cada una, para AlpinaShop:

Natural Language API para el procesamiento masivo del histórico y el flujo diario. Es barata, rápida, predecible y da un número comparable en el tiempo. Es la elección para las 8.000 opiniones.

Gemini cuando la pregunta es más rica que "¿positivo o negativo?". Por ejemplo: "clasifica esta opinión en producto / talla / envío / atención al cliente, indica el sentimiento de cada aspecto y extrae el defecto concreto si lo hay, devolviendo JSON". Eso, que la API no puede hacer, Gemini lo hace con un prompt y sin entrenar nada. Y entiende la ironía y el catalán mucho mejor. A cambio, cuesta más por documento, es más lento y no es determinista: la misma opinión puede dar respuestas ligeramente distintas. Se ve en 05-06.

AutoML Text solo si se da una condición muy concreta: que exista una taxonomía propia y estable —"defecto de costura", "problema de talla", "retraso logístico", "error de descripción"— y que compense etiquetar mil ejemplos. Con las 200 del apartado 13 ya hay medio camino andado.

Y una combinación que suele ser la mejor de las tres: usar la API para procesar todo de forma barata, y reservar Gemini para el subconjunto interesante —las opiniones negativas y las mixtas, que serán unos cientos— pidiéndole un análisis detallado por aspectos. Bajo coste sobre el volumen, alta calidad donde importa.

  1. La familia completa: Translation, Speech-to-Text, Document AI

La API de Natural Language es una de varias APIs preentrenadas que comparten filosofía, facturación por uso y ausencia total de entrenamiento.

API Qué hace Uso posible en AlpinaShop
Cloud Translation Traduce entre idiomas Traducir fichas de producto a catalán e inglés; homogeneizar opiniones antes de analizarlas
Speech-to-Text Transcribe audio Transcribir las llamadas de atención al cliente y analizarlas igual que las opiniones
Text-to-Speech Genera voz Marginal aquí
Document AI Extrae datos de documentos Facturas y albaranes de proveedor (se detalla en 05-05)

Cloud Translation tiene dos ediciones: la básica, que traduce texto plano, y la avanzada, que permite glosarios y traducción por lotes. El glosario importa mucho en este dominio: sin él, "piolet" o "arnés" pueden traducirse de forma incorrecta o inconsistente entre fichas. Con glosario, la terminología queda fijada.

Speech-to-Text abre una posibilidad interesante: AlpinaShop tiene un teléfono de atención al cliente cuyas conversaciones no dejan ningún dato analizable. Transcribirlas y pasarlas por el mismo análisis de sentimiento daría una segunda fuente sobre los mismos problemas. Con una advertencia legal de primer orden: grabar y transcribir llamadas exige informar previamente, tener base legal y una política de conservación clara, y la transcripción contiene datos personales en abundancia. Antes de tocar nada, pasa por compliance y aplica la desidentificación de 04-07.

  1. Privacidad: qué se envía, qué se guarda y qué dice el RGPD

Qué se envía. El texto completo del documento viaja a la API. Aunque la infraestructura esté dentro de Google Cloud, el contenido sale de tu proyecto y entra en un servicio gestionado.

Dónde se procesa. Las APIs preentrenadas ofrecen endpoints regionales para determinadas funciones e idiomas. Si la residencia del dato en la UE es un requisito, hay que verificar en la documentación oficial qué endpoints regionales están disponibles para la función y el idioma concretos, y configurarlos explícitamente. No asumas que por tener el proyecto en europe-west1 el procesamiento ocurre ahí.

Qué se guarda. La política vigente de Google Cloud para las APIs de predicción es no usar los datos de los clientes para entrenar sus modelos, y hay compromisos contractuales al respecto. Es un punto que hay que verificar en los términos vigentes y documentar en el registro de tratamientos: no es algo que se pueda dar por sabido.

El riesgo específico del texto libre. Este es el punto crítico y merece decirse con claridad: un campo de texto libre es el sitio donde acaban los datos personales que nadie esperaba. Un cliente escribe su nombre, su teléfono para que le llamen, la dirección donde se dejó el paquete, o el nombre de otra persona. Ninguna validación de formulario lo evita.

AlpinaShop ya hizo lo correcto en 04-07: las opiniones se desidentifican con Sensitive Data Protection antes de quedar disponibles para análisis, sustituyendo cada hallazgo por su tipo ([EMAIL_ADDRESS], [PERSON_NAME]), lo que preserva la estructura y el sentimiento del texto. Y el análisis de esta lección se ejecuta sobre v_opiniones_analitica, la vista desidentificada, nunca sobre la tabla original. Ese es el control que hace que el resto sea defendible.

Con la advertencia de siempre: la desidentificación automática no es infalible. Un cliente que escriba "soy el que compró la mochila azul el martes en la tienda de Sabadell" no será detectado por ningún tipo predefinido y sigue siendo potencialmente reidentificable.

Recomendación expresa. Antes de procesar en masa texto de clientes, un profesional de compliance o el DPO debe determinar: la base legal del tratamiento analítico de las opiniones y si la finalidad está cubierta por la información dada al recogerlas; si el envío del texto a la API constituye una comunicación de datos que requiera información adicional; los requisitos de residencia del dato y qué endpoints regionales los cumplen; el plazo de conservación de los textos y de los resultados; y la suficiencia de la desidentificación aplicada. Esta lección describe controles técnicos y no constituye asesoramiento jurídico. Todos los datos son ficticios.

Sobre el AI Act europeo: analizar sentimiento de opiniones sobre productos para mejorar el catálogo se sitúa razonablemente en un nivel de riesgo bajo. Conviene señalar, sin embargo, que el reglamento contempla restricciones específicas para sistemas de reconocimiento de emociones aplicados a personas en contextos como el laboral o el educativo. Analizar el tono de un texto sobre una mochila no es eso, pero la frontera importa: si mañana alguien propusiera aplicar el mismo análisis a las comunicaciones del equipo de atención al cliente para evaluar su desempeño, el encuadre legal cambiaría por completo. Cualquier uso que apunte a personas y no a productos requiere revisión legal previa.

Errores Comunes y Consejos

Mirar solo el score. El error de esta lección. Un score de 0 con magnitude de 3,4 es una opinión intensa y mixta, no una opinión neutra, y son las que más información contienen.

No declarar el idioma. La detección automática falla en textos cortos y degrada el resultado sin avisar.

Comparar magnitude entre textos de longitudes muy distintas. No está normalizada: un texto largo acumula más magnitud aunque sea menos intenso.

Reintentar los errores InvalidArgument. Un texto que la API no puede procesar fallará siempre igual. Reintentarlo cuesta tiempo y cuota.

Lanzar 100 hilos. Agota la cuota, dispara los reintentos y acaba siendo más lento y más caro que ir con ocho.

Pedir todas las funciones "por si acaso". Cada función factura por separado. Pide sentimiento y entidades si vas a usar ambas; si no, solo una.

Analizar SKU con tres opiniones. Con 8.000 opiniones y 2.400 productos, la media es de tres por SKU. Para la mayoría del catálogo, la única lectura honesta es por categoría.

Olvidar modelo_version y procesado_en. Sin ellas es imposible distinguir un cambio en los clientes de un cambio en el modelo del proveedor.

Consejo: guarda siempre el sentimiento por frase. Viene gratis en la misma respuesta y permite localizar la frase problemática de una opinión larga sin volver a llamar.

Consejo: valida con 200 opiniones etiquetadas a mano antes de enseñar ningún número a dirección. Es la diferencia entre "el sentimiento medio es 0,31" y "la clasificación coincide con el criterio humano en el 82 % de los casos, y falla sobre todo en ironía y en jerga técnica".

Ejercicios

Ejercicio 1

Lucía presenta a dirección que "el 71 % de las opiniones de AlpinaShop son neutras" y concluye que los clientes son indiferentes. Diagnostica qué ha podido pasar, qué consulta harías para comprobarlo, y qué le propondrías presentar en su lugar.

Ejercicio 2

Diseña el proceso completo para analizar las opiniones nuevas cada noche: arquitectura, tabla de destino, control de coste y qué hacer cuando una opinión falla. Indica qué servicios de módulos anteriores reutilizas.

Ejercicio 3

El análisis muestra que el producto BOT-3391 (bota de montaña rígida) tiene un sentimiento medio de −0,18, de los peores del catálogo, pero su tasa de devolución es del 2,1 %, muy por debajo de la media del 6,4 %. Explica la contradicción aparente y propón cómo investigarla.

Soluciones

Solución 1

Diagnóstico: casi con seguridad, está clasificando solo por score e ignorando la magnitude.

El apartado 3 explica el mecanismo exacto. Las opiniones mixtas —"el producto genial, el envío horrible"— tienen los dos sentimientos anulándose y producen un score cercano a 0. Si el criterio es "neutra si el score está entre −0,25 y +0,25", todas esas opiniones intensas caen en el saco de neutras.

Una causa secundaria posible: si el idioma no se declaró y parte de las opiniones están en catalán, la detección puede haber fallado y devuelto valores poco discriminantes.

Consulta para comprobarlo:

SELECT
  CASE WHEN ABS(score) < 0.25 THEN 'score_neutro' ELSE 'score_polarizado' END AS grupo,
  CASE WHEN magnitude < 0.6 THEN 'baja'
       WHEN magnitude < 1.5 THEN 'media'
       ELSE                      'alta' END                                   AS emocion,
  COUNT(*)                                     AS opiniones,
  ROUND(AVG(puntuacion), 2)                    AS estrellas_medias,
  ROUND(AVG(LENGTH(texto)), 0)                 AS long_media
FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` s
JOIN `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` o USING (opinion_id)
WHERE estado = 'OK'
GROUP BY grupo, emocion
ORDER BY grupo, emocion;

Qué esperar. Si dentro de score_neutro hay un bloque grande con emoción alta, ahí está la respuesta: no son neutras, son mixtas. Y el dato que lo confirma de forma independiente es estrellas_medias: si ese grupo tiene una media de 2,8 estrellas y textos largos, es evidente que no son clientes indiferentes.

Qué presentar en su lugar:

  1. La clasificación en cuatro clases —positiva, negativa, neutra, mixta— con la vista del apartado 3. Es muy probable que las "neutras" reales bajen del 71 % a un 25-30 %.
  2. El desglose de las mixtas por entidad, que es la información accionable: "en las opiniones mixtas, el producto tiene sentimiento medio +0,6 y el envío −0,5". Eso no dice que los clientes sean indiferentes: dice que el producto gusta y la logística falla, que es una conclusión con acciones asociadas.
  3. La validación con 200 opiniones etiquetadas a mano, con la frase de honestidad correspondiente: "la clasificación automática coincide con el criterio humano en el X % de los casos".

La lección de fondo: un número mal interpretado lleva a la conclusión de negocio más equivocada posible —"a los clientes les da igual"— cuando los datos dicen justo lo contrario.

Solución 2

Arquitectura, reutilizando lo del módulo 4:

flowchart LR
    A[Cloud Scheduler<br/>03:30 diario] --> B[Workflows]
    B --> C[Consulta incremental<br/>en BigQuery]
    C --> D[Cloud Run Job<br/>proceso Python]
    D --> E[Natural Language API]
    E --> D
    D --> F[opiniones_sentimiento]
    D --> G[opiniones_fallidas]
    B --> H[Comprobacion de calidad]
    H -->|fallo| I[Alerta a gcp-datos@]

Servicios reutilizados: Cloud Scheduler y Workflows como orquestador (04-06, decisión ya tomada frente a Composer), BigQuery como origen y destino (04-01) con la vista desidentificada de 04-07, Cloud Run como ejecutor del proceso (detalle en 07-02), Cloud Monitoring para la alerta (06-04) y Secret Manager si hiciera falta alguna credencial (03-06).

Por qué Cloud Run Job y no una función: el proceso puede tardar varios minutos con reintentos, y un trabajo de Cloud Run admite ejecuciones largas sin las restricciones de tiempo de una función.

Selección incremental, la consulta del apartado 8, con un tope de seguridad:

SELECT o.opinion_id, o.sku, o.fecha, o.texto, o.idioma
FROM `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` o
LEFT JOIN `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` s
  USING (opinion_id)
WHERE s.opinion_id IS NULL
LIMIT 5000;   -- tope de seguridad: si algo se desmadra, no se dispara el coste

El LIMIT 5000 es deliberado. Si por un error de datos aparecieran 400.000 opiniones sin procesar, el tope evita una factura inesperada. Lo que sobre se procesará la noche siguiente, y la comprobación de calidad avisará de que la cola crece.

Tabla de destino: la del apartado 9, particionada por fecha y agrupada por sku, con modelo_version y procesado_en.

Qué hacer cuando una opinión falla:

Tipo de fallo Acción
429 cuota Reintento con espera exponencial y ruido, hasta 5 veces
503 transitorio Reintento rápido
InvalidArgument No reintentar. Fila con estado='ERROR: ...'
Texto vacío o < 10 caracteres Filtrar antes de llamar; no gasta cuota
Agotados los reintentos Fila con estado de error; el proceso siguiente no lo reintenta automáticamente

La clave: cada opinión leída produce una fila, con resultado o con error. Así opinion_id IS NULL sigue significando "no procesado" y no "falló y se reintentará eternamente", que es como se construye un bucle infinito de coste.

Control de coste:

  1. Filtrar textos triviales antes de llamar (LENGTH(texto) >= 10).
  2. LIMIT 5000 como tope duro.
  3. Pedir solo sentimiento en el flujo diario; el análisis de entidades se reserva para las opiniones negativas y mixtas, que son una fracción.
  4. Etiqueta de facturación centro-coste:analitica y alerta de presupuesto (01-04).

Comprobación de calidad en el Workflow, que bloquea si algo va mal:

SELECT
  COUNTIF(estado = 'OK')                                        AS ok,
  COUNTIF(estado != 'OK')                                       AS fallidas,
  SAFE_DIVIDE(COUNTIF(estado != 'OK'), COUNT(*))                AS tasa_fallo
FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento`
WHERE DATE(procesado_en) = CURRENT_DATE();

Si tasa_fallo > 0.05, el Workflow marca fallo y alerta a gcp-datos@. Es la misma disciplina de calidad que bloquea el pipeline nocturno en 04-07.

Solución 3

No hay contradicción. Hay dos explicaciones plausibles, y las dos son buenas noticias mal contadas.

Explicación 1 (la más probable): la jerga del dominio. Es exactamente el caso de la tabla del apartado 12. Una bota de montaña rígida genera opiniones como "son durísimas", "cuesta andar con ellas en llano", "muy rígidas", "pesan". El modelo genérico lee "duras", "cuesta", "pesan" y devuelve sentimiento negativo. Para una bota compatible con crampón, la rigidez es el requisito funcional, no un defecto. El cliente que escribe "son durísimas" está describiendo con satisfacción que el producto hace lo que promete.

Y las devoluciones lo confirman: quien compra este producto sabe lo que compra y se lo queda.

Explicación 2: la queja es sobre el proceso de adaptación, no sobre el producto. Las botas rígidas requieren un periodo de rodaje incómodo. Las opiniones pueden ser negativas sobre esa experiencia —"los primeros días fueron un suplicio"— y positivas sobre el resultado final. El sentimiento global capta la parte negativa.

Cómo investigarlo, en tres pasos:

Paso 1 — Leer. Sacar las 20 opiniones más negativas del SKU y leerlas. Cuesta quince minutos y muchas veces resuelve el caso. Ningún análisis automático sustituye a esto.

SELECT o.texto, s.score, s.magnitude, o.puntuacion
FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` s
JOIN `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` o USING (opinion_id)
WHERE s.sku = 'BOT-3391' AND s.estado = 'OK'
ORDER BY s.score ASC LIMIT 20;

Paso 2 — Contrastar con las estrellas. La puntuación numérica es una etiqueta humana que ya existe y es gratuita:

SELECT
  ROUND(AVG(o.puntuacion), 2)              AS estrellas_medias,
  ROUND(AVG(s.score), 3)                   AS score_medio,
  ROUND(CORR(o.puntuacion, s.score), 3)    AS correlacion_sku,
  COUNT(*)                                 AS n
FROM `alpinashop-datos.alpinashop_analitica.opiniones_sentimiento` s
JOIN `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` o USING (opinion_id)
WHERE s.sku = 'BOT-3391' AND s.estado = 'OK';

Este es el paso decisivo. Si las estrellas medias son 4,3 y el score medio es −0,18, el veredicto es inequívoco: el problema es del análisis, no del producto. Los clientes están contentos y el modelo lo está leyendo al revés.

Paso 3 — Comparar dentro de la categoría. Repetir la consulta anterior agrupando por categoria y subcategoria con HAVING opiniones >= 30, ordenando por correlación ascendente. Si todas las botas rígidas del catálogo muestran el mismo desajuste entre estrellas y score, no es un caso aislado: es un sesgo sistemático del modelo en esa categoría. Las categorías con correlación baja o negativa entre estrellas y score son aquellas donde el análisis genérico no es de fiar.

Qué hacer con el hallazgo:

  1. Corto plazo: no usar el score absoluto para comparar entre categorías. Comparar cada producto contra la media de su categoría, no contra la media global. Un score de −0,18 en una categoría cuya media es −0,22 es en realidad de los mejores.
  2. Medio plazo: usar Gemini para las categorías problemáticas (05-06), explicándole el contexto en el prompt: "Estas son opiniones sobre botas de montaña rígidas. En este contexto, 'duras', 'rígidas' y 'pesadas' pueden ser descripciones positivas del rendimiento técnico. Clasifica el sentimiento real del cliente." Eso arregla el problema sin entrenar nada.
  3. Largo plazo: si el problema es extenso, entrenar un clasificador propio con AutoML Text (05-02) usando las estrellas como etiqueta débil y una muestra validada a mano. Ahí sí compensaría.

Y la lección general: el mejor detector de fallos del análisis de sentimiento es una etiqueta humana que ya tienes. Las estrellas estaban en la base de datos desde el principio. Cruzar la salida de un modelo con una señal humana independiente es la comprobación más barata y más eficaz que existe.

Conclusión

Has aplicado el principio que más dinero ahorra en machine learning aplicado: no entrenes lo que ya está entrenado. Ocho mil opiniones que llevaban cinco años sin que nadie las leyera están ahora analizadas, cruzadas con las ventas y disponibles en BigQuery, por unas decenas de euros y sin un solo minuto de entrenamiento.

Conoces las cinco funciones de la API de Natural Language —sentimiento, entidades, sentimiento de entidades, clasificación de contenido y sintaxis— y por qué la tercera es la más valiosa para una tienda: una opinión real casi nunca es buena o mala a secas, es "el producto genial, el envío horrible", y solo el desglose por entidad convierte eso en algo accionable.

Y sobre todo entiendes score y magnitude, que es donde se equivoca casi todo el mundo. Un score de 0 con magnitude de 0,1 es un texto descriptivo sin emoción; un score de 0 con magnitude de 3,4 es una opinión intensa con un problema serio dentro. Confundirlos lleva a la conclusión más equivocada posible —"a los clientes les da igual"— justo cuando los datos dicen lo contrario.

Has montado el procesamiento de las 8.000 opiniones con lo que hace falta para que un proceso de este tipo sobreviva a la realidad: espera exponencial con ruido para las cuotas, distinción entre errores reintentables y definitivos, una fila de resultado por cada documento leído aunque falle, concurrencia moderada y recorte defensivo de textos anómalos. Sabes estimar el coste antes de gastarlo con una consulta SQL, y por qué el redondeo por documento y por función es lo que de verdad determina la factura.

Los resultados viven en opiniones_sentimiento, particionada y agrupada, con modelo_version y procesado_en —las dos columnas que permiten distinguir, dentro de seis meses, si cambió el modelo o cambiaron los clientes—. Y el proceso incremental nocturno se engancha a Cloud Scheduler y Workflows, el orquestador que AlpinaShop ya eligió en 04-06, con tope de seguridad y comprobación de calidad que bloquea si la tasa de fallo se dispara.

Has respondido la pregunta de negocio cruzando sentimiento con devoluciones, y sabes leer el resultado con las tres cautelas: correlación no es causalidad, quien opina no es una muestra representativa, y con tres opiniones por SKU de media la única lectura honesta para la mayoría del catálogo es por categoría.

Y conoces los límites reales, dichos sin adornos: la ironía se le escapa, las negaciones complejas se diluyen, y la jerga de montaña le juega malas pasadas —"duras" es un defecto en unas zapatillas y una virtud en unas botas de crampón—. Por eso el protocolo de validar con 200 opiniones etiquetadas a mano no es opcional: es lo que convierte "el sentimiento medio es 0,31" en "coincide con el criterio humano en el 82 % de los casos, y falla en ironía y jerga técnica". Y tienes el truco más barato de todos: cruzar la salida del modelo con las estrellas que los clientes ya habían puesto.

Tienes la comparación honesta entre la API, Gemini y un clasificador propio, con la combinación que suele ganar —la API para el volumen, Gemini para el subconjunto interesante—, y sabes que Translation, Speech-to-Text y Document AI comparten la misma filosofía. Y tienes el marco de privacidad claro: el análisis corre sobre la vista desidentificada de 04-07, el texto libre es donde acaban los datos personales que nadie esperaba, la residencia del dato hay que verificarla por función e idioma, y cualquier uso que apunte a personas en lugar de a productos cambia el encuadre legal por completo.

Queda una pregunta del módulo 4 sin responder, y es la más voluminosa: 60 GB de imágenes en alpinashop-catalogo. AutoML las clasificó por categoría en 05-02, pero de cada foto se puede extraer muchísimo más de lo que hay en ella: qué objetos aparecen y dónde, qué texto lleva impreso, qué colores dominan, si es apta para publicarse. Y hay un problema nuevo: los clientes empiezan a subir sus propias fotos en las opiniones, y nadie las está moderando.

En 05-05, la API de visión, aplicas el mismo principio a las imágenes. Verás las funciones de Cloud Vision —etiquetas, objetos con recuadro, OCR, logos, puntos de referencia, colores dominantes, SafeSearch y detección web—, procesarás las 60 GB en masa con asyncBatchAnnotate controlando el coste por millar de imágenes, y las convertirás en cuatro cosas concretas: el texto alternativo de accesibilidad que hoy no existe, el filtro por color de la tienda, la moderación automática de las fotos de clientes y la detección de las fichas que no cumplen el estándar del catálogo. Con la arquitectura por eventos sobre el topic imagenes-subidas, y con la advertencia legal más seria del módulo, que llega cuando en una imagen aparece una cara.

Curso de Google Cloud Platform (GCP)

Módulo 1: Introducción a Google Cloud Platform

Módulo 2: Servicios principales de GCP

Módulo 3: Redes y seguridad

Módulo 4: Datos y análisis

Módulo 5: Aprendizaje automático e IA

Módulo 6: DevOps y monitoreo

Módulo 7: Temas avanzados de GCP

Módulo 8: Proyecto final

© Copyright 2026. Todos los derechos reservados