La plataforma de datos de AlpinaShop funciona. Los pedidos entran solos, Dataflow los limpia, Spark calcula la cesta media, Data Fusion trae el fichero del transportista y Workflows lo coordina todo cada noche con su comprobación de calidad. Técnicamente es un éxito.
Y sin embargo, tres cosas siguen sin funcionar.
Nadie sabe qué hay. En alpinashop_analitica hay dieciséis tablas, seis vistas, dos vistas materializadas y una llamada pedidos_evento que nadie recuerda por qué existe. Cuando alguien de marketing pregunta "¿dónde está el dato de conversión?", la respuesta es "pregúntale a Lucía". Y si Lucía se va de vacaciones, la respuesta es "no lo sé".
Nadie sabe qué significa. La columna ventas_eur aparece en cuatro tablas. ¿Incluye IVA? ¿Descuenta devoluciones? ¿Cuenta los pedidos cancelados? Marta cree que sí, Lucía cree que no, y el informe que va a dirección usa una de las dos definiciones sin decir cuál. Nadie lo ha escrito nunca.
Nadie sabe quién puede ver qué. En 04-01 se protegió email_cliente con una etiqueta de política. Pero desde entonces han pasado seis lecciones: Dataflow escribe en pedidos_streaming, la suscripción de Pub/Sub vuelca en pedidos_evento, el archivo de eventos va a Cloud Storage y Data Fusion trajo el CSV del transportista con el nombre del destinatario. ¿Hay emails de clientes en alguno de esos sitios sin protección? Nadie lo ha comprobado.
Y hay una cuarta cosa que falta, la que dirección lleva pidiendo desde el módulo 3: el cuadro de mando. Los datos están impecables y nadie los ve.
En esta lección cerramos el módulo resolviendo las cuatro. Verás qué es el gobierno del dato y por qué una pyme lo necesita tanto como una multinacional, organizarás y documentarás la plataforma con Dataplex, definirás reglas de calidad que se comprueban solas, descubrirás y desidentificarás los datos personales con Sensitive Data Protection, y construirás por fin en Looker Studio el cuadro de mando de AlpinaShop, evitando el error de permisos que convierte un informe compartido en una fuga de datos.
Contenido
- Qué es el gobierno del dato y por qué también en una pyme
- Dataplex: lagos, zonas y activos
- El catálogo universal y la búsqueda de datos
- Etiquetas y plantillas: documentar
alpinashop_analitica - Perfiles de datos: qué hay realmente en tus tablas
- Reglas de calidad y qué hacer cuando fallan
- Linaje automático de extremo a extremo
- Sensitive Data Protection: descubrir dónde están los datos personales
- Desidentificación: enmascarado y tokenización
- Looker Studio: conectar y modelar
- El cuadro de mando de dirección de AlpinaShop
- Coste y rendimiento: extracción, consulta viva y BI Engine
- Permisos: el error clásico de las credenciales del propietario
- Looker y LookML: cuándo justifica su precio
- Checklist de una plataforma de datos sana
- Qué es el gobierno del dato y por qué también en una pyme
El gobierno del dato es el conjunto de prácticas que garantizan que los datos de una organización sean encontrables, comprensibles, fiables, seguros y gestionados a lo largo de su vida. Cinco patas:
| Pata | Pregunta que responde | Herramienta en Google Cloud |
|---|---|---|
| Catálogo | ¿Qué datos tenemos y dónde están? | Dataplex Universal Catalog |
| Linaje | ¿De dónde viene este dato y qué depende de él? | Dataplex Lineage |
| Calidad | ¿Puedo fiarme de este número? | Dataplex Data Quality |
| Clasificación | ¿Qué datos son sensibles y quién los ve? | Sensitive Data Protection + etiquetas de política |
| Ciclo de vida | ¿Cuánto tiempo lo guardamos y cuándo se borra? | Políticas de retención, TTL, ciclo de vida de Cloud Storage |
La objeción habitual es que esto es cosa de bancos y multinacionales. Es exactamente al revés, y merece argumentarse:
El RGPD no distingue por tamaño. Una empresa de 40 personas que trata datos de clientes europeos tiene las mismas obligaciones que una de 40.000: saber qué datos personales tiene, dónde están, con qué base legal, cuánto tiempo los conserva, y poder atender un derecho de supresión. Si no se sabe dónde está el email de un cliente, no se puede borrar cuando lo pida.
El coste del desconocimiento es proporcionalmente mayor. En una empresa grande hay redundancia: si alguien se va, otro sabe. En AlpinaShop, si Lucía se va, el conocimiento de la plataforma se va con ella. La documentación no es burocracia, es continuidad.
Los errores llegan antes a dirección. Con tres personas y un cuadro de mando, un dato mal calculado no pasa por ningún filtro intermedio. Va directo a una decisión.
Y el momento correcto es ahora. Gobernar dieciséis tablas es un trabajo de tarde. Gobernar doscientas, después de tres años de crecimiento desordenado, es un proyecto de meses. La deuda de gobierno se acumula con intereses.
- Dataplex: lagos, zonas y activos
Dataplex es la capa de gestión unificada de datos de Google Cloud. Su idea central: los datos de una organización están repartidos entre BigQuery, Cloud Storage y otros sistemas, y hace falta una capa lógica por encima que los organice, los describa y les aplique políticas comunes sin moverlos.
Su jerarquía:
| Nivel | Qué es | En AlpinaShop |
|---|---|---|
| Lago (lake) | Dominio de datos; agrupa todo lo de un ámbito | alpinashop-lago-comercial |
| Zona (zone) | Subdivisión por grado de refinamiento | zona-cruda, zona-curada |
| Activo (asset) | Recurso concreto: un bucket o un dataset | El bucket del lago, el dataset analítico |
Las zonas tienen dos tipos con semántica distinta:
- Raw: datos en su formato original, sin validar. Cualquier formato.
- Curated: datos ya limpios y estructurados, con esquema. Solo formatos estructurados (Parquet, Avro, ORC) o tablas de BigQuery. Dataplex valida que lo que se registra aquí cumple.
flowchart TD
L["Lago: alpinashop-lago-comercial"]
Z1["Zona RAW: zona-cruda<br/>datos tal como llegan"]
Z2["Zona CURATED: zona-curada<br/>datos limpios y modelados"]
A1["Activo: gs://alpinashop-datalake<br/>eventos, exportaciones, cuarentena"]
A2["Activo: gs://alpinashop-catalogo<br/>imagenes y exportaciones"]
A3["Activo: BQ alpinashop_analitica<br/>pedidos, lineas, productos, visitas"]
A4["Activo: gs://alpinashop-datalake/resultados<br/>Parquet de Spark"]
L --> Z1 --> A1
Z1 --> A2
L --> Z2 --> A3
Z2 --> A4
Creación:
gcloud config set project alpinashop-datos
gcloud services enable dataplex.googleapis.com \
datacatalog.googleapis.com datalineage.googleapis.com
# 1) El lago: el dominio de datos comercial de AlpinaShop
gcloud dataplex lakes create alpinashop-lago-comercial \
--location=europe-west1 \
--display-name="Lago comercial de AlpinaShop" \
--description="Pedidos, catalogo, navegacion y logistica" \
--labels=entorno=produccion,equipo=datos,centro-coste=analitica
# 2) Zona cruda: lo que llega tal cual
gcloud dataplex zones create zona-cruda \
--location=europe-west1 --lake=alpinashop-lago-comercial \
--type=RAW \
--resource-location-type=SINGLE_REGION \
--display-name="Datos en crudo" \
--discovery-enabled \
--discovery-schedule="0 3 * * *"
# 3) Zona curada: lo que ya esta limpio y modelado
gcloud dataplex zones create zona-curada \
--location=europe-west1 --lake=alpinashop-lago-comercial \
--type=CURATED \
--resource-location-type=SINGLE_REGION \
--display-name="Datos curados" \
--discovery-enabled \
--discovery-schedule="0 4 * * *"
# 4) Activos
gcloud dataplex assets create activo-datalake \
--location=europe-west1 --lake=alpinashop-lago-comercial --zone=zona-cruda \
--resource-type=STORAGE_BUCKET \
--resource-name=projects/alpinashop-datos/buckets/alpinashop-datalake \
--discovery-enabled
gcloud dataplex assets create activo-analitica \
--location=europe-west1 --lake=alpinashop-lago-comercial --zone=zona-curada \
--resource-type=BIGQUERY_DATASET \
--resource-name=projects/alpinashop-datos/datasets/alpinashop_analitica \
--discovery-enabledLa opción --discovery-enabled es la que aporta valor inmediato: Dataplex rastrea automáticamente los activos, infiere esquemas de los ficheros del bucket, detecta particiones por la estructura de carpetas, y crea tablas externas de BigQuery para que esos ficheros sean consultables con SQL sin que nadie las declare.
Para AlpinaShop eso significa que los Parquet que el trabajo de Spark deja en gs://alpinashop-datalake/resultados/cesta/ aparecen como tabla consultable sin trabajo adicional. Es descubrimiento automático de datos, y es la razón práctica de registrar los buckets aunque el gobierno formal no interese.
- El catálogo universal y la búsqueda de datos
El Universal Catalog de Dataplex —heredero de Data Catalog— indexa automáticamente los metadatos de BigQuery, Cloud Storage, Pub/Sub, Spanner y Bigtable de toda la organización. Y sobre ese índice hay un buscador.
La sintaxis de búsqueda:
# Todo lo que mencione 'pedido' pedido # Solo tablas de BigQuery type=TABLE system=BIGQUERY pedido # Por columna: donde hay un campo que se llame email column:email # Por etiqueta de negocio tag:sensibilidad.nivel=alto # Por proyecto y dataset parent:alpinashop-datos.alpinashop_analitica # Combinado: tablas con columna de importe en el dataset analitico type=TABLE parent:alpinashop_analitica column:importe
Desde la línea de comandos:
# Buscar todas las columnas llamadas 'email' en la organizacion
gcloud data-catalog search "column:email" \
--include-project-ids=alpinashop-datos,alpinashop-prod \
--format="table(relativeResourceName, searchResultSubtype)"Esa consulta responde en segundos a una pregunta que sin catálogo requiere abrir tabla por tabla: ¿dónde hay emails? Es el primer paso del apartado 8.
La respuesta a la pregunta de marketing —"¿dónde está el dato de conversión?"— pasa de "pregúntale a Lucía" a buscar conversion en el catálogo y encontrar agg_embudo_dia con su descripción. Ese cambio es todo el valor del catálogo.
- Etiquetas y plantillas: documentar
alpinashop_analitica
alpinashop_analiticaEl catálogo indexa lo que existe, pero no sabe lo que significa. Para eso están las etiquetas (tags), que añaden metadatos de negocio a tablas y columnas, estructurados según una plantilla.
Primero la plantilla, que define qué campos se documentan y con qué valores permitidos:
cat > plantilla-gobierno.json <<'EOF'
{
"displayName": "Gobierno del dato AlpinaShop",
"fields": {
"propietario": {
"displayName": "Propietario del dato",
"type": {"primitiveType": "STRING"},
"isRequired": true,
"description": "Persona o equipo responsable de este conjunto de datos"
},
"dominio": {
"displayName": "Dominio de negocio",
"type": {"enumType": {"allowedValues": [
{"displayName": "ventas"},
{"displayName": "catalogo"},
{"displayName": "logistica"},
{"displayName": "marketing"},
{"displayName": "finanzas"}
]}},
"isRequired": true
},
"sensibilidad": {
"displayName": "Nivel de sensibilidad",
"type": {"enumType": {"allowedValues": [
{"displayName": "publico"},
{"displayName": "interno"},
{"displayName": "confidencial"},
{"displayName": "datos-personales"}
]}},
"isRequired": true
},
"frescura_esperada": {
"displayName": "Frescura esperada",
"type": {"enumType": {"allowedValues": [
{"displayName": "tiempo-real"},
{"displayName": "diaria"},
{"displayName": "semanal"},
{"displayName": "mensual"}
]}},
"isRequired": true
},
"retencion_meses": {
"displayName": "Retencion en meses",
"type": {"primitiveType": "DOUBLE"},
"isRequired": true
},
"apto_para_informes": {
"displayName": "Apto para informes de direccion",
"type": {"primitiveType": "BOOL"},
"isRequired": true,
"description": "Falso en tablas de staging, temporales o experimentales"
},
"definicion_negocio": {
"displayName": "Definicion de negocio",
"type": {"primitiveType": "STRING"},
"isRequired": false,
"description": "Que significa exactamente este dato, en lenguaje de negocio"
}
}
}
EOF
gcloud data-catalog tag-templates create gobierno_alpinashop \
--location=europe-west1 \
--display-name="Gobierno del dato AlpinaShop" \
--field-file=plantilla-gobierno.jsonY ahora se etiqueta cada tabla:
# Tabla de pedidos: dato personal, diaria, apta para informes
gcloud data-catalog tags create \
--entry-group=@bigquery \
--entry="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/pedidos" \
--tag-template=gobierno_alpinashop \
--tag-template-location=europe-west1 \
--tag-file=- <<'EOF'
{
"propietario": "[email protected]",
"dominio": "ventas",
"sensibilidad": "datos-personales",
"frescura_esperada": "diaria",
"retencion_meses": 84,
"apto_para_informes": true,
"definicion_negocio": "Cabecera de pedido confirmado. total_pedido INCLUYE IVA y gastos de envio, y NO descuenta devoluciones posteriores; para ventas netas usar agg_ventas_categoria_dia."
}
EOFEse campo definicion_negocio es la solución al segundo problema del inicio de la lección. Una frase escrita una vez elimina para siempre la discusión sobre si total_pedido lleva IVA. Y queda donde se busca, no en un documento que nadie abre.
La disciplina de etiquetado que AlpinaShop adopta:
| Tabla | Sensibilidad | Apto para informes | Nota |
|---|---|---|---|
pedidos |
datos-personales | Sí | Contiene email_cliente |
lineas_pedido |
interno | Sí | Sin datos personales |
productos |
confidencial | Sí | coste_compra es margen comercial |
visitas |
datos-personales | Sí | cliente_id es seudónimo |
pedidos_staging |
interno | No | Tabla de trabajo del DAG |
pedidos_evento |
datos-personales | No | Volcado crudo de Pub/Sub |
agg_* |
interno | Sí | Agregados, sin identificación |
La columna apto para informes es la más útil en el día a día: distingue lo que puede alimentar un cuadro de mando de lo que es fontanería. Sin ella, alguien acabará construyendo un informe de dirección sobre pedidos_staging, que se vacía cada noche.
- Perfiles de datos: qué hay realmente en tus tablas
Antes de definir reglas de calidad hay que saber cómo son los datos. El perfilado de Dataplex analiza una tabla y produce estadísticas por columna: valores nulos, distintos, mínimos, máximos, medias, percentiles y los valores más frecuentes.
gcloud dataplex datascans create data-profile perfil-pedidos \
--location=europe-west1 \
--data-source-resource="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/pedidos" \
--display-name="Perfil de la tabla de pedidos" \
--on-demand \
--data-profile-spec-sampling-percent=100
gcloud dataplex datascans run perfil-pedidos --location=europe-west1
gcloud dataplex datascans describe perfil-pedidos --location=europe-west1 --view=FULLUn perfil típico revela cosas que nadie sospechaba:
| Columna | Nulos | Distintos | Hallazgo |
|---|---|---|---|
pedido_id |
0 % | 100 % | Correcto: identificador único |
cliente_id |
12 % | 8.400 | Compras como invitado. ¿Se sabía? |
email_cliente |
0 % | 8.900 | Dato personal en todas las filas |
canal |
0 % | 5 | Hay 5 valores, no 3: aparecen WEB y Web |
estado |
0 % | 6 | Correcto |
total_pedido |
0 % | — | mín. -45,00; máx. 4.120,00 |
envio.pais |
3 % | 14 | Hay pedidos sin país |
Tres hallazgos accionables en una sola pasada: el 12 % de compras sin cliente identificado (que puede ser normal o puede ser un fallo del registro), la inconsistencia de mayúsculas en canal que rompe cualquier GROUP BY, y un total_pedido negativo, que probablemente sea un abono mal modelado y que está distorsionando todas las medias.
Ese último es el ejemplo perfecto de por qué el perfilado va antes que las reglas: la regla "total nunca negativo" no se inventa, se descubre.
- Reglas de calidad y qué hacer cuando fallan
Con el perfil en la mano se definen las reglas. Un escaneo de calidad ejecuta un conjunto de comprobaciones y da un resultado de aprobado o fallido con el detalle por regla.
# calidad-pedidos.yaml
rules:
# --- Completitud ---
- column: pedido_id
nonNullExpectation: {}
dimension: COMPLETENESS
threshold: 1.0 # el 100 % de las filas debe cumplir
name: pedido-id-obligatorio
description: "Todo pedido debe tener identificador"
- column: fecha_pedido
nonNullExpectation: {}
dimension: COMPLETENESS
threshold: 1.0
name: fecha-obligatoria
# --- Unicidad ---
- column: pedido_id
uniquenessExpectation: {}
dimension: UNIQUENESS
threshold: 1.0
name: pedido-id-unico
description: "Detecta duplicados por reprocesos mal hechos"
# --- Validez de rango ---
- column: total_pedido
rangeExpectation:
minValue: "0"
maxValue: "10000"
strictMinEnabled: false
dimension: VALIDITY
threshold: 0.999 # se tolera un 0,1 % de excepciones
name: total-en-rango
description: "Importes no negativos y por debajo de 10.000 EUR"
# --- Conjunto de valores permitidos ---
- column: estado
setExpectation:
values: ["confirmado", "enviado", "entregado", "devuelto", "cancelado"]
dimension: VALIDITY
threshold: 1.0
name: estado-valido
- column: canal
setExpectation:
values: ["web", "movil", "telefono"]
dimension: VALIDITY
threshold: 1.0
name: canal-normalizado
description: "Detecta 'WEB' y 'Web' sin normalizar"
# --- Formato con expresion regular ---
- column: email_cliente
regexExpectation:
regex: "^[^@\\s]+@[^@\\s]+\\.[a-zA-Z]{2,}$"
dimension: VALIDITY
threshold: 0.99
name: email-con-formato-valido
ignoreNull: true
# --- Regla SQL a nivel de fila ---
- sqlAssertion:
sqlStatement: |
SELECT pedido_id, subtotal, descuento, iva, total_pedido
FROM ${data()}
WHERE ABS(total_pedido - (subtotal - IFNULL(descuento,0)
+ IFNULL(iva,0))) > 0.01
dimension: ACCURACY
name: cuadre-de-importes
description: "El total debe cuadrar con sus componentes (tolerancia 1 centimo)"
# --- Regla SQL agregada: frescura ---
- sqlAssertion:
sqlStatement: |
SELECT 1 FROM (
SELECT MAX(fecha_pedido) AS ultima FROM ${data()}
) WHERE DATE_DIFF(CURRENT_DATE(), ultima, DAY) > 2
dimension: FRESHNESS
name: datos-frescos
description: "Debe haber pedidos de los ultimos 2 dias"gcloud dataplex datascans create data-quality calidad-pedidos \
--location=europe-west1 \
--data-source-resource="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/pedidos" \
--display-name="Calidad de la tabla de pedidos" \
--data-quality-spec-file=calidad-pedidos.yaml \
--schedule="0 6 * * *" \
--export-results-table="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/resultados_calidad"Las seis dimensiones de calidad estándar, con su interpretación en AlpinaShop:
| Dimensión | Pregunta | Ejemplo |
|---|---|---|
| Completitud | ¿Falta algo? | Pedidos sin sku |
| Unicidad | ¿Hay duplicados? | El mismo pedido_id dos veces tras un reproceso |
| Validez | ¿Los valores son posibles? | estado fuera de la lista, importe negativo |
| Exactitud | ¿Cuadran entre sí? | total ≠ subtotal - descuento + iva |
| Consistencia | ¿Coinciden entre sistemas? | Pedidos en BigQuery frente a Cloud SQL |
| Frescura | ¿Están actualizados? | Sin pedidos nuevos desde hace 3 días |
Qué hacer cuando una regla falla. Esta es la parte que más se descuida, y sin ella el escaneo es decorativo:
| Gravedad | Ejemplo | Acción |
|---|---|---|
| Crítica | Duplicados, frescura rota | Bloquear: el cuadro de mando no debe actualizarse con esos datos |
| Alta | Importes que no cuadran | Alerta inmediata al propietario, investigación en el día |
| Media | canal sin normalizar |
Ticket de corrección en el origen; el pipeline lo normaliza mientras tanto |
| Baja | Un 0,3 % de emails con formato raro | Informe semanal; puede ser aceptable |
La integración con el orquestador de 04-06 cierra el círculo:
from airflow.providers.google.cloud.operators.dataplex import (
DataplexRunDataQualityScanOperator,
)
calidad = DataplexRunDataQualityScanOperator(
task_id="escaneo_calidad_pedidos",
project_id="alpinashop-datos",
region="europe-west1",
data_scan_id="calidad-pedidos",
asynchronous=False,
fail_on_dq_failure=True, # si falla la calidad, FALLA EL DAG
)
consolidar_pedidos >> calidad >> lanzar_dataflowCon fail_on_dq_failure=True, un fallo de calidad detiene el proceso nocturno antes de propagar datos malos al cuadro de mando. Es la versión industrializada de la comprobación de calidad que escribimos a mano en 04-06.
- Linaje automático de extremo a extremo
En 04-05 vimos el linaje de Data Fusion, limitado a sus pipelines. Dataplex Lineage lo hace de forma transversal: captura automáticamente el linaje de BigQuery (cada consulta que escribe en una tabla), Dataflow, Data Fusion, Composer y Dataproc.
Y ya está: a partir de ahí, cada trabajo registra su linaje solo.
El grafo completo de AlpinaShop, que Dataplex construye sin que nadie lo dibuje:
flowchart LR
CS["Cloud SQL<br/>alpinashop-pedidos"]
GCS["gs://alpinashop-datalake<br/>exportaciones"]
PS["Pub/Sub<br/>pedidos-nuevos"]
ERP["MySQL ERP<br/>Sabadell"]
ST["BQ pedidos_staging"]
PE["BQ pedidos"]
LP["BQ lineas_pedido"]
CE["BQ costes_producto_erp"]
AG["BQ agg_ventas_categoria_dia"]
MV["MV mv_ventas_diarias_sku"]
LS["Looker Studio<br/>Cuadro de mando"]
CS -->|export| GCS -->|GCSToBigQuery| ST -->|MERGE| PE
PS -->|Dataflow| PE
PS -->|suscripcion BQ| PE
ERP -->|Datastream| CE
PE --> LP
LP --> AG
CE --> AG
AG --> MV --> LS
Las preguntas que este grafo responde en un clic:
- "El margen del informe sale raro. ¿De dónde viene?" →
agg_ventas_categoria_dia←lineas_pedido+costes_producto_erp← Datastream ← MySQL del ERP. En cuatro saltos se llega al origen. - "Vamos a cambiar el esquema del ERP. ¿Qué se rompe?" → Todo lo que cuelga de
costes_producto_erp, incluido el cuadro de mando de dirección. - "Un cliente ejerce su derecho de supresión. ¿Dónde está su dato?" → Se rastrea
pedidoshacia adelante y aparecen todas las tablas derivadas. - "¿Podemos apagar la exportación nocturna de Cloud SQL?" → El grafo muestra que
pedidos_stagingdepende de ella. No.
Esa última pregunta es la que más dinero ahorra en la práctica: saber qué se puede apagar. Toda plataforma de datos con dos años acumula procesos que ya no alimentan nada, y sin linaje nadie se atreve a tocarlos.
- Sensitive Data Protection: descubrir dónde están los datos personales
Aviso de RGPD. Este apartado y el siguiente tratan datos personales. Las herramientas que se describen son controles técnicos, no un dictamen jurídico. Determinar la base legal de un tratamiento, los plazos de conservación, la necesidad de una evaluación de impacto o si una seudonimización es suficiente corresponde a un profesional de compliance o al delegado de protección de datos, y debe hacerse antes de poner nada en producción. Todos los datos de este curso son ficticios.
Sensitive Data Protection —el servicio antes conocido como Cloud DLP— hace dos cosas: inspeccionar para descubrir dónde hay datos sensibles, y desidentificar para que dejen de serlo.
Empecemos por la inspección, que responde al tercer problema del inicio de la lección: ¿hay emails de clientes en sitios donde no debería haberlos?
Un trabajo de inspección sobre todo el dataset:
{
"inspectJob": {
"storageConfig": {
"bigQueryOptions": {
"tableReference": {
"projectId": "alpinashop-datos",
"datasetId": "alpinashop_analitica",
"tableId": "pedidos_evento"
},
"sampleMethod": "RANDOM_START",
"rowsLimitPercent": 10
}
},
"inspectConfig": {
"infoTypes": [
{"name": "EMAIL_ADDRESS"},
{"name": "PHONE_NUMBER"},
{"name": "STREET_ADDRESS"},
{"name": "PERSON_NAME"},
{"name": "IBAN_CODE"},
{"name": "CREDIT_CARD_NUMBER"},
{"name": "ES_NIF_NUMBER"},
{"name": "IP_ADDRESS"}
],
"minLikelihood": "LIKELY",
"includeQuote": false,
"limits": {"maxFindingsPerRequest": 1000}
},
"actions": [
{
"saveFindings": {
"outputConfig": {
"table": {
"projectId": "alpinashop-datos",
"datasetId": "alpinashop_analitica",
"tableId": "hallazgos_dlp"
}
}
}
},
{"publishSummaryToCscc": {}}
]
}
}curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://dlp.googleapis.com/v2/projects/alpinashop-datos/locations/europe-west1/dlpJobs" \
-d @inspeccion-pedidos-evento.jsonDos decisiones deliberadas en esa configuración:
includeQuote: false: los hallazgos no incluyen el valor encontrado. Si lo pusieras entrue, la tabla de hallazgos contendría los emails reales, es decir, habrías creado una copia sin proteger de los datos que intentas proteger. Es un error clásico y grave.rowsLimitPercent: 10: para descubrir si hay datos sensibles no hace falta escanear el 100 %. Un muestreo del 10 % encuentra el problema y cuesta la décima parte.
Y una consulta sobre los hallazgos:
-- Donde hay datos personales y de que tipo
SELECT
info_type.name AS tipo_dato,
location.container_name AS tabla,
location.content_locations[SAFE_OFFSET(0)]
.record_location.field_id.name AS columna,
likelihood,
COUNT(*) AS hallazgos
FROM `alpinashop-datos.alpinashop_analitica.hallazgos_dlp`
GROUP BY tipo_dato, tabla, columna, likelihood
ORDER BY hallazgos DESC;Resultado típico en AlpinaShop, y por qué duele:
| Tipo | Tabla | Columna | Comentario |
|---|---|---|---|
EMAIL_ADDRESS |
pedidos |
email_cliente |
Esperado: ya protegido con etiqueta de política en 04-01 |
EMAIL_ADDRESS |
pedidos_evento |
payload |
No esperado: la suscripción de Pub/Sub vuelca el JSON crudo |
PERSON_NAME |
envios |
incidencia |
No esperado: el transportista escribe nombres en texto libre |
PHONE_NUMBER |
opiniones |
texto |
No esperado: clientes que dejan su teléfono en la opinión |
IP_ADDRESS |
visitas |
dispositivo |
No esperado: una IP es dato personal según el RGPD |
Cuatro de los cinco hallazgos son sorpresas. Ninguno es fruto de mala fe: son consecuencias naturales de mover datos. La suscripción de BigQuery de 04-04 vuelca el mensaje entero. El transportista escribe lo que quiere en el campo de incidencia. Los clientes ponen su teléfono en una opinión. Y una IP, que parece técnica, es dato personal.
Esto es exactamente lo que ninguna organización sabe sin inspeccionar. Y es la razón por la que este apartado existe.
Para hacerlo continuo, Dataplex ofrece perfilado de datos sensibles a nivel de organización, que escanea automáticamente todo BigQuery y mantiene un inventario actualizado de dónde hay datos sensibles y con qué nivel de riesgo, sin lanzar trabajos a mano.
- Desidentificación: enmascarado y tokenización
Descubierto el problema, hay que actuar. Las técnicas, de menor a mayor utilidad conservada:
| Técnica | Qué hace | ¿Reversible? | Cuándo usarla |
|---|---|---|---|
| Supresión | Elimina el valor | No | El dato no se necesita para nada |
| Enmascarado | [email protected] → ***@correo.com |
No | Se necesita el dominio, no la persona |
| Sustitución | Reemplaza por una constante | No | Solo hace falta saber que había algo |
| Hash cripto | HMAC-SHA256 con clave en KMS | No | Contar clientes distintos sin saber quiénes |
| Tokenización determinista (FPE) | Token con el formato original | Sí, con la clave | Cruzar entre sistemas y poder revertir |
| Generalización | 34 años → 30-39; 08013 → 08 |
No | Análisis demográfico o geográfico |
| Desplazamiento de fechas | Desplaza todas las fechas de un sujeto | Sí | Series temporales sin fechas reales |
La distinción crítica es entre hash y tokenización determinista:
- El hash con clave produce el mismo resultado para el mismo valor, así que permite contar y agrupar clientes distintos, pero no se puede volver atrás. Es lo correcto para analítica pura.
- La tokenización determinista con preservación de formato produce un token que parece un email y se puede revertir con la clave. Es lo correcto cuando un sistema autorizado necesita recuperar el valor original.
Configuración de desidentificación para AlpinaShop:
{
"deidentifyTemplate": {
"displayName": "Desidentificacion analitica AlpinaShop",
"description": "Aplica a las tablas expuestas a analisis y a Looker Studio",
"deidentifyConfig": {
"recordTransformations": {
"fieldTransformations": [
{
"fields": [{"name": "email_cliente"}],
"primitiveTransformation": {
"cryptoHashConfig": {
"cryptoKey": {
"kmsWrapped": {
"wrappedKey": "CiQA...",
"cryptoKeyName": "projects/alpinashop-prod/locations/europe-west1/keyRings/alpinashop-keyring/cryptoKeys/clave-pedidos"
}
}
}
}
},
{
"fields": [{"name": "codigo_postal"}],
"primitiveTransformation": {
"characterMaskConfig": {
"maskingCharacter": "X",
"numberToMask": 3,
"reverseOrder": true
}
}
},
{
"fields": [{"name": "texto_opinion"}],
"infoTypeTransformations": {
"transformations": [
{
"infoTypes": [
{"name": "EMAIL_ADDRESS"},
{"name": "PHONE_NUMBER"},
{"name": "PERSON_NAME"}
],
"primitiveTransformation": {
"replaceWithInfoTypeConfig": {}
}
}
]
}
}
]
}
}
}
}Qué hace cada transformación:
email_cliente→ hash con clave envuelta en KMS. Reutilizaclave-pedidosdel keyringalpinashop-keyringcreado en 03-06. La clave nunca sale de KMS, así que ni siquiera quien tenga acceso a la tabla puede revertir el hash. Lucía sigue pudiendo contar clientes únicos conCOUNT(DISTINCT email_hash).codigo_postal→ enmascarado de los 3 últimos.08013se convierte en08XXX. Se conserva la provincia, que es lo que sirve para el análisis geográfico, y se pierde la calle, que es lo que identifica.texto_opinion→ sustitución por tipo dentro del texto libre. "Llamadme al 611223344, soy Ana" se convierte en "Llamadme al [PHONE_NUMBER], soy [PERSON_NAME]". La opinión sigue siendo analizable —incluido el análisis de sentimiento del módulo 5— sin datos personales dentro.
La última es la más valiosa y la que ninguna otra herramienta hace bien: desidentificar dentro de texto libre, donde el dato personal no está en una columna sino incrustado en una frase.
Y la vista que expone Lucía y el cuadro de mando:
-- Vista desidentificada: es la UNICA que se conecta a Looker Studio
CREATE OR REPLACE VIEW `alpinashop-datos.alpinashop_analitica.v_pedidos_analitica` AS
SELECT
pedido_id,
fecha_pedido,
momento_pedido,
cliente_id,
canal,
estado,
-- Provincia sí, direccion no
envio.pais AS pais,
SUBSTR(envio.codigo_postal, 1, 2) AS provincia_cp,
metodo_pago,
subtotal, descuento, iva, total_pedido
FROM `alpinashop-datos.alpinashop_analitica.pedidos`;
-- Sin email_cliente, sin ciudad, sin codigo postal completo.
-- Es una VISTA AUTORIZADA (04-01): quien la consulta NO necesita
-- permiso sobre la tabla base, y por tanto no puede saltarsela.El principio operativo: los datos personales existen en la tabla base, protegida y con acceso restringido a gcp-seguridad@; todo lo demás —analítica, cuadros de mando, exploración— consume vistas desidentificadas. La minimización no es una limpieza puntual, es una arquitectura.
- Looker Studio: conectar y modelar
Looker Studio (antes Data Studio) es la herramienta gratuita de visualización de Google. Se conecta a BigQuery y a decenas de orígenes, y permite construir informes interactivos sin código.
Hay tres formas de conectarlo a BigQuery, y elegir bien determina el coste y el rendimiento:
| Modo | Qué hace | Coste | Cuándo |
|---|---|---|---|
| Tabla/vista | Consulta viva contra la tabla | Cada interacción cuesta | Datos que cambian y se filtran mucho |
| Consulta personalizada | SQL propio como origen | Cada interacción ejecuta ese SQL | Modelado complejo; cuidado con el coste |
| Extracción | Copia los datos en la caché de Looker Studio | Casi cero | Volúmenes pequeños, refresco diario |
La regla de oro: nunca conectes Looker Studio directamente a una tabla de detalle grande. Conéctalo a una tabla agregada o a una vista materializada. Un cuadro de mando con seis gráficos que consulta lineas_pedido ejecuta seis consultas cada vez que alguien cambia un filtro, y con diez usuarios refrescando, la factura de BigQuery se dispara —exactamente el escenario del ejercicio 3 de 04-01.
Para AlpinaShop, el origen del cuadro de mando será una tabla preparada por el proceso nocturno:
-- Tabla base del cuadro de mando: pequena, agregada, lista para consumir
CREATE OR REPLACE TABLE `alpinashop-datos.alpinashop_analitica.bi_resumen_diario`
PARTITION BY dia
CLUSTER BY canal, pais AS
SELECT
p.fecha_pedido AS dia,
p.canal,
p.envio.pais AS pais,
pr.categoria,
COUNT(DISTINCT p.pedido_id) AS pedidos,
COUNT(DISTINCT p.cliente_id) AS clientes,
SUM(l.cantidad) AS unidades,
ROUND(SUM(l.importe_linea), 2) AS ventas_eur,
ROUND(SUM(l.cantidad * c.coste_medio_eur), 2) AS coste_eur,
COUNTIF(p.estado = 'devuelto') AS pedidos_devueltos
FROM `alpinashop-datos.alpinashop_analitica.pedidos` AS p
JOIN `alpinashop-datos.alpinashop_analitica.lineas_pedido` AS l
ON l.pedido_id = p.pedido_id AND l.fecha_pedido = p.fecha_pedido
JOIN `alpinashop-datos.alpinashop_analitica.productos` AS pr USING (sku)
LEFT JOIN `alpinashop-datos.alpinashop_analitica.costes_producto_erp` AS c USING (sku)
WHERE p.fecha_pedido >= DATE_SUB(CURRENT_DATE(), INTERVAL 800 DAY)
AND p.estado NOT IN ('cancelado')
GROUP BY dia, p.canal, pais, pr.categoria;Esa tabla tiene unos pocos miles de filas —dos años × 3 canales × 14 países × 6 categorías— frente a los millones de lineas_pedido. Consultarla es prácticamente gratis.
Los campos calculados de Looker Studio se definen sobre el origen y se comportan como columnas:
# Ticket medio
SUM(ventas_eur) / NULLIF(SUM(pedidos), 0)
# Margen bruto en euros
SUM(ventas_eur) - SUM(coste_eur)
# Margen porcentual
100 * (SUM(ventas_eur) - SUM(coste_eur)) / NULLIF(SUM(ventas_eur), 0)
# Tasa de devolucion
100 * SUM(pedidos_devueltos) / NULLIF(SUM(pedidos), 0)
# Agrupacion de canales para una vista simplificada
CASE
WHEN canal IN ('web','movil') THEN 'Digital'
ELSE 'Otros'
ENDConsejo de modelado: define los campos calculados en la vista de BigQuery, no en Looker Studio, siempre que puedas. Un campo definido en SQL está versionado en Git, es reutilizable por otros informes y por Spark, y no se pierde si alguien duplica el informe. Un campo definido en Looker Studio vive dentro de ese informe y nadie más lo ve.
- El cuadro de mando de dirección de AlpinaShop
Diseñemos el informe que dirección lleva pidiendo desde el módulo 3.
Estructura en tres páginas:
Pagina 1 — RESUMEN EJECUTIVO +----------------------------------------------------------+ | [Selector de fechas] [Canal] [Pais] [Categoria] | +----------------------------------------------------------+ | Ventas mes | Pedidos | Ticket medio | Margen % | | 42.180 EUR | 387 | 108,99 EUR | 38,2 % | | ^ +12,4 % | ^ +8,1 % | ^ +4,0 % | v -1,2 pp | +----------------------------------------------------------+ | Evolucion de ventas (linea, 13 meses, con ano anterior) | +----------------------------------------------------------+ | Top 10 productos (barras) | Ventas por categoria (aro) | +----------------------------------------------------------+ Pagina 2 — EMBUDO Y CONVERSION +----------------------------------------------------------+ | Embudo: sesiones > vieron > carrito > pago > compra | +----------------------------------------------------------+ | Tasa de conversion (linea) | Coste de adquisicion (linea)| +----------------------------------------------------------+ | Conversion por canal y dispositivo (tabla con barras) | +----------------------------------------------------------+ Pagina 3 — PRODUCTO Y LOGISTICA +----------------------------------------------------------+ | Margen por categoria (barras) | Productos sin ventas | +----------------------------------------------------------+ | Tasa de devolucion por categoria | Plazo medio de entrega | +----------------------------------------------------------+
Los indicadores clave, con su definición explícita —que es lo que evita la discusión de "este número no es el que yo tenía":
| Indicador | Fórmula | Definición que hay que escribir en el informe |
|---|---|---|
| Ventas del mes | SUM(ventas_eur) |
Importe de líneas de pedido, IVA incluido, sin pedidos cancelados |
| Evolución | Comparación con el periodo anterior | Mismo número de días, no mes natural completo |
| Ticket medio | ventas / pedidos |
Por pedido, no por línea |
| Top de productos | SUM(unidades) por SKU |
Por unidades vendidas, no por importe |
| Tasa de conversión | compras / sesiones |
Sesiones con actividad, no visitas totales |
| Coste de adquisición | inversión / clientes nuevos |
Requiere el dato de inversión en marketing |
| Margen | (ventas - coste) / ventas |
Coste del ERP; sin gastos generales ni logística |
El coste de adquisición merece una nota honesta: requiere un dato que AlpinaShop no tiene todavía en la plataforma —la inversión mensual en publicidad—. Hay dos opciones: cargarlo como una tabla manual mantenida por marketing, o integrarlo desde la API de la plataforma publicitaria. La primera es la sensata para empezar. Y hay que mostrarlo en el informe indicando de dónde sale, porque un indicador cuyo origen nadie conoce acaba desacreditando al resto.
Buenas prácticas de diseño, que son las que separan un informe que se usa de uno que se abre una vez:
- Lo más importante arriba a la izquierda. Es donde va la vista.
- Cuatro tarjetas de indicador, no doce. Un cuadro de mando con veinte cifras no se lee: se ignora.
- Comparación siempre. Un número sin contexto no informa. 42.180 € no dice nada;
+12,4 % frente al mes anteriorsí. - Un tipo de gráfico por pregunta: línea para evolución, barras para comparar categorías, tabla para el detalle. Nada de gráficos de tarta con doce porciones.
- Filtros arriba y comunes a la página, no repartidos por los gráficos.
- Colores con significado y accesibles: verde y rojo tienen sentido para bueno y malo, pero hay que asegurarse de que se distinguen también para daltónicos, añadiendo flechas o signos además del color.
- Fecha de última actualización visible. Un informe sin ella genera desconfianza y llamadas.
- Una nota metodológica al pie. Dónde se define cada indicador. Es la diferencia entre un informe y una discusión.
- Coste y rendimiento: extracción, consulta viva y BI Engine
Los tres mecanismos, en orden de coste creciente:
1. Extracción de datos. Looker Studio copia hasta un límite de datos en su propia caché y los sirve desde ahí. Refresco programable de hasta una vez al día. Coste de BigQuery prácticamente nulo, rendimiento excelente. Es la opción por defecto para un informe con datos agregados que no necesita ser de hoy mismo.
2. Consulta viva con caché. Looker Studio cachea los resultados durante un tiempo configurable (hasta 12 horas). Con la caché activada, diez personas mirando el mismo informe generan una consulta, no diez. Es lo que debe usarse en un cuadro de mando compartido.
3. BI Engine. Un servicio de aceleración en memoria de BigQuery. Se reserva una capacidad y las consultas que caben en ella se responden en milisegundos y sin coste de bytes escaneados.
# Reserva de BI Engine de 2 GB para el cuadro de mando
bq update --reservation --project_id=alpinashop-datos \
--location=europe-west1 \
--bi_reservation_size=2147483648Con la tabla bi_resumen_diario, que ocupa unos pocos megabytes, 2 GB de BI Engine caben de sobra: el cuadro de mando entero se sirve desde memoria, con latencia de milisegundos y sin facturar escaneo. El coste de la reserva es del orden de unos pocos euros al mes, y suele salir más barato que pagar las consultas que evita.
Comprobar que BI Engine se está usando:
SELECT
job_id,
bi_engine_statistics.bi_engine_mode AS modo,
ROUND(total_bytes_billed / POW(1024,2), 2) AS mb_facturados,
TIMESTAMP_DIFF(end_time, start_time, MILLISECOND) AS ms
FROM `region-europe-west1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
AND job_type = 'QUERY'
AND bi_engine_statistics IS NOT NULL
ORDER BY creation_time DESC
LIMIT 20;Si modo es FULL, la consulta se resolvió íntegramente en memoria. Si es DISABLED o PARTIAL, el campo bi_engine_statistics.acceleration_mode explica por qué —normalmente porque la consulta usa una función no soportada o la tabla no cabe.
La receta completa de AlpinaShop, resumida: tabla agregada pequeña + caché activada + BI Engine + informe conectado a la vista desidentificada. Coste mensual del cuadro de mando: unos pocos euros. Sin esa disciplina, los mismos gráficos costarían cientos.
- Permisos: el error clásico de las credenciales del propietario
Aquí está el error más peligroso de toda la lección, y ocurre constantemente porque la interfaz lo pone fácil.
Cuando se crea un origen de datos en Looker Studio hay que elegir con qué credenciales se ejecutan las consultas:
| Modo | Cómo funciona | Consecuencia |
|---|---|---|
| Credenciales del propietario | Todas las consultas usan los permisos de quien creó el origen | Quien ve el informe accede a los datos con los permisos del propietario, aunque él no tenga ninguno |
| Credenciales del espectador | Cada consulta usa los permisos de quien mira | Cada persona ve lo que sus permisos de BigQuery le permiten |
El escenario del desastre, paso a paso:
- Lucía crea el cuadro de mando conectado a BigQuery con credenciales del propietario. Es lo cómodo, y es lo que la interfaz sugiere.
- Lucía tiene, además de acceso a los agregados, permiso de lectura sobre
pedidoscompleto. - Lucía comparte el informe con "cualquier persona con el enlace" para que un proveedor externo vea un gráfico.
- Ese proveedor —o cualquiera a quien reenvíen el enlace— puede explorar los datos con los permisos de Lucía. Puede añadir campos, cambiar dimensiones y, si el origen es una tabla con datos personales, verlos.
- Nadie en BigQuery le ha dado ningún permiso a esa persona. Los permisos de BigQuery no lo protegen, porque las consultas no se hacen en su nombre.
Eso es una brecha de datos personales con obligación de notificación bajo el RGPD.
Las reglas que AlpinaShop adopta, sin excepción:
- El origen apunta siempre a una vista desidentificada y autorizada, nunca a una tabla con datos personales. Aunque el modo de credenciales sea el equivocado, no hay nada sensible detrás. Esta es la defensa que no depende de que nadie se acuerde.
- Credenciales del espectador en cualquier informe que salga del equipo de datos.
- Nunca "cualquier persona con el enlace" para informes con datos de negocio. Compartir siempre con personas o grupos concretos (
gcp-datos@,direccion@). - Revisión trimestral de con quién están compartidos los informes. Los permisos se acumulan solos.
- Si hace falta credenciales del propietario —porque los espectadores no tienen ni deben tener permisos de BigQuery—, entonces el origen tiene que ser una vista agregada sin ningún dato personal ni confidencial. Sin negociación.
Verificación desde BigQuery de quién está consultando desde Looker Studio:
SELECT
user_email,
COUNT(*) AS consultas,
ROUND(SUM(total_bytes_billed)/POW(1024,3), 2) AS gb,
MIN(creation_time) AS primera,
MAX(creation_time) AS ultima
FROM `region-europe-west1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type = 'QUERY'
AND (labels.requestor = 'looker_studio'
OR STRPOS(IFNULL(query, ''), 'Looker Studio') > 0)
GROUP BY user_email
ORDER BY consultas DESC;Si en esa lista aparece una sola dirección de correo —la de Lucía— cuando el informe lo miran quince personas, estás en modo credenciales del propietario. Es la comprobación de treinta segundos que conviene hacer hoy.
- Looker y LookML: cuándo justifica su precio
Looker (sin "Studio") es un producto distinto y mucho más caro. Su diferencia esencial es LookML, un lenguaje de modelado donde se define una sola vez qué significa cada métrica y cada dimensión, y todos los informes de la empresa consumen esas definiciones.
# modelo.lkml — la definicion vive en UN sitio, versionada en Git
view: pedidos {
sql_table_name: `alpinashop-datos.alpinashop_analitica.bi_resumen_diario` ;;
dimension_group: dia {
type: time
timeframes: [date, week, month, quarter, year]
sql: ${TABLE}.dia ;;
}
dimension: canal { type: string sql: ${TABLE}.canal ;; }
measure: ventas_eur {
type: sum
sql: ${TABLE}.ventas_eur ;;
value_format_name: eur
description: "Importe de lineas de pedido, IVA incluido, sin cancelados"
}
measure: pedidos { type: sum sql: ${TABLE}.pedidos ;; }
measure: ticket_medio {
type: number
sql: ${ventas_eur} / NULLIF(${pedidos}, 0) ;;
value_format_name: eur
description: "Ventas dividido entre numero de pedidos"
}
}| Criterio | Looker Studio | Looker |
|---|---|---|
| Coste | Gratuito (Pro tiene coste) | Miles de € al mes |
| Modelado centralizado | No: cada informe define lo suyo | Sí, LookML |
| Versionado en Git | No | Sí, nativo |
| Coherencia de métricas | Depende de la disciplina | Garantizada por diseño |
| Permisos por fila | Vía BigQuery | Nativo en el modelo |
| API y contenido embebido | Limitado | Completo |
| Curva de aprendizaje | Horas | Semanas |
Cuándo justifica su precio: cuando el coste de la incoherencia supera el coste de la licencia. Es decir, cuando hay decenas de analistas creando informes, cuando "ventas" significa cosas distintas en tres departamentos y las reuniones se dedican a discutir de quién es el número bueno, o cuando se necesita gobierno del modelo semántico con revisión por pull request.
Para AlpinaShop, Looker Studio es la respuesta obvia: tres personas, un cuadro de mando, presupuesto de pyme. La coherencia se garantiza con la disciplina del apartado 10 —definir las métricas en vistas de BigQuery versionadas en Git— que es el 80 % del valor de LookML por el 0 % del precio.
- Checklist de una plataforma de datos sana
Organización y descubrimiento
- [ ] Todos los datasets y buckets registrados en un lago de Dataplex con sus zonas.
- [ ] Descubrimiento automático activado en los activos de Cloud Storage.
- [ ] Toda tabla con
descriptiony con etiquetas de gobierno. - [ ] Distinción explícita entre tablas aptas para informes y tablas de fontanería.
- [ ] Cada tabla con un propietario nombrado.
Calidad
- [ ] Perfilado ejecutado sobre las tablas principales.
- [ ] Reglas de calidad definidas para completitud, unicidad, validez, exactitud y frescura.
- [ ] Escaneos programados diariamente.
- [ ] El orquestador bloquea el proceso si falla una regla crítica.
- [ ] Resultados históricos exportados a BigQuery para ver la tendencia.
Linaje
- [ ] API de linaje activada.
- [ ] Grafo revisado tras cada cambio de arquitectura.
- [ ] Procesos sin consumidores identificados y apagados.
Seguridad y privacidad
- [ ] Inspección de datos sensibles ejecutada sobre todo el dataset, no solo donde se sospecha.
- [ ] Columnas con datos personales etiquetadas con política de acceso.
- [ ] Vistas desidentificadas como única superficie de exposición analítica.
- [ ] Ninguna herramienta de BI conectada a una tabla con datos personales.
- [ ] Claves de hash y tokenización en KMS, nunca en el código.
- [ ] Revisión por un profesional de compliance o el DPO antes de producción.
Coste
- [ ] Tablas particionadas y clusterizadas;
require_partition_filteren las grandes. - [ ] Cuadros de mando conectados a agregados, nunca a tablas de detalle.
- [ ] Caché de informes activada; BI Engine si compensa.
- [ ] Presupuestos y alertas por etiqueta de centro de coste.
- [ ] Nada permanentemente encendido que no lo necesite: sin clústeres de Dataproc olvidados, sin instancias de Data Fusion encendidas, sin pipelines de streaming huérfanos.
- [ ] Auditoría periódica de consultas caras con
INFORMATION_SCHEMA.
Operación
- [ ] Todos los procesos orquestados, ninguno en un
cronde una VM. - [ ] Tareas idempotentes; reprocesos seguros.
- [ ] Alertas que llegan a una persona concreta, no a un correo genérico.
- [ ] Código —DDL, DAG, pipelines, flujos— versionado en Git.
- [ ] Documentación de qué hace cada proceso y a quién avisar.
Errores Comunes y Consejos
Documentar solo al principio. Un catálogo con etiquetas de hace un año es peor que ninguno, porque genera confianza injustificada. Documentar debe ser parte de crear la tabla, no un proyecto aparte.
Poner reglas de calidad sin perfilar antes. Se acaban definiendo umbrales inventados que fallan a diario, todo el mundo ignora las alertas, y el sistema deja de servir. Perfila, mira los datos reales, y luego define.
Reglas de calidad que no bloquean nada. Si un fallo de calidad no detiene el proceso ni avisa a nadie, es decoración.
Inspeccionar con includeQuote: true. Se crea una tabla de hallazgos que contiene los datos sensibles que se querían proteger. Es empeorar el problema.
Confundir hash con anonimización. Un hash sin sal ni clave de un email es reversible por fuerza bruta —el espacio de emails plausibles no es tan grande—. Sigue siendo dato personal a efectos del RGPD. La clave debe estar en KMS.
Conectar Looker Studio a una tabla de detalle. Cada filtro dispara consultas sobre millones de filas. Es el camino directo a una factura sorpresa.
Credenciales del propietario más enlace público. El error más grave de la lección. Cualquiera con el enlace consulta con los permisos del propietario.
No poner la fecha de actualización en el informe. Genera desconfianza, llamadas y decisiones basadas en datos de la semana pasada creyendo que son de hoy.
Definir las métricas en Looker Studio en vez de en BigQuery. Se quedan encerradas en ese informe, no se versionan, y al duplicarlo divergen.
Consejo: escribe la definición de negocio de cada métrica y publícala. La mitad de las discusiones sobre datos son discusiones sobre definiciones, no sobre números.
Consejo: pon un enlace al catálogo en el pie del cuadro de mando. Quien quiera saber qué significa un número, que pueda llegar solo.
Consejo: haz la inspección de datos sensibles hoy, no cuando haya un incidente. Cuesta una tarde y casi siempre encuentra algo que nadie sabía.
Ejercicios
Ejercicio 1: gobernar la tabla de opiniones
Para la tabla alpinashop_analitica.opiniones: (a) escribe el comando que la registra como activo en la zona curada del lago; (b) crea la etiqueta de gobierno indicando propietario, dominio, sensibilidad, frescura, retención, aptitud para informes y una definición de negocio precisa; (c) define un escaneo de calidad con al menos cinco reglas que cubran completitud, unicidad, validez de rango, conjunto de valores y una regla SQL propia; (d) explica qué acción tomarías ante el fallo de cada regla.
Ejercicio 2: inspección y desidentificación de opiniones
El campo texto de opiniones es texto libre escrito por clientes. Diseña: (a) el trabajo de inspección que descubra qué tipos de datos personales contiene, con las opciones correctas para no crear una copia de los datos sensibles; (b) la plantilla de desidentificación que permita conservar el texto analizable —para el análisis de sentimiento del módulo 5— eliminando los datos personales; (c) la vista que se expondría a Looker Studio; (d) la advertencia de RGPD que acompañaría la propuesta antes de llevarla a producción.
Ejercicio 3: rediseñar un cuadro de mando problemático
El cuadro de mando de dirección lleva dos meses funcionando y hay cuatro problemas. Uno: tarda 40 segundos en cargar y el coste de BigQuery del proyecto ha pasado de 12 € a 310 € al mes. Dos: INFORMATION_SCHEMA muestra que todas las consultas del informe las hace [email protected], aunque lo miran doce personas. Tres: el informe está compartido como "cualquier persona con el enlace" porque hubo que enseñárselo a un consultor externo. Cuatro: dirección dice que el número de ventas del informe no coincide con el de contabilidad.
Diagnostica cada problema, indica su gravedad relativa y propón la solución concreta con el orden de actuación.
Soluciones
Solución 1
(a) Registrar el activo. La tabla ya está dentro del dataset alpinashop_analitica, que se registró completo como activo activo-analitica en zona-curada. Por tanto no hay que registrar la tabla individualmente: Dataplex la descubre al rastrear el dataset. Si se quisiera un activo separado —por ejemplo, para un dataset distinto— sería:
gcloud dataplex assets create activo-opiniones \
--location=europe-west1 \
--lake=alpinashop-lago-comercial --zone=zona-curada \
--resource-type=BIGQUERY_DATASET \
--resource-name=projects/alpinashop-datos/datasets/alpinashop_opiniones \
--discovery-enabled
gcloud dataplex assets describe activo-opiniones \
--location=europe-west1 --lake=alpinashop-lago-comercial --zone=zona-curada(b) Etiqueta de gobierno.
gcloud data-catalog tags create \
--entry-group=@bigquery \
--entry="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/opiniones" \
--tag-template=gobierno_alpinashop \
--tag-template-location=europe-west1 \
--tag-file=- <<'EOF'
{
"propietario": "[email protected]",
"dominio": "catalogo",
"sensibilidad": "datos-personales",
"frescura_esperada": "diaria",
"retencion_meses": 36,
"apto_para_informes": false,
"definicion_negocio": "Opiniones de clientes sobre productos. puntuacion es de 1 a 5, entera. El campo texto es LIBRE y puede contener datos personales incrustados (nombres, telefonos): NO exponer directamente; usar la vista v_opiniones_analitica, ya desidentificada. Una opinion corresponde a un cliente y un SKU; un cliente puede opinar varias veces del mismo producto."
}
EOFsensibilidad: datos-personales y apto_para_informes: false son las dos decisiones clave: aunque el campo texto parezca inocuo, es texto libre y por tanto imprevisible.
(c) Escaneo de calidad.
# calidad-opiniones.yaml
rules:
- column: opinion_id
nonNullExpectation: {}
dimension: COMPLETENESS
threshold: 1.0
name: opinion-id-obligatorio
- column: opinion_id
uniquenessExpectation: {}
dimension: UNIQUENESS
threshold: 1.0
name: opinion-id-unico
- column: puntuacion
rangeExpectation:
minValue: "1"
maxValue: "5"
dimension: VALIDITY
threshold: 1.0
name: puntuacion-de-1-a-5
- column: pais
setExpectation:
values: ["ES","FR","PT","IT","DE","AD","BE","NL","AT","CH","GB","IE","LU","PL"]
dimension: VALIDITY
threshold: 0.99
name: pais-en-lista
- column: sku
nonNullExpectation: {}
dimension: COMPLETENESS
threshold: 1.0
name: sku-obligatorio
# Regla SQL: integridad referencial contra el catalogo
- sqlAssertion:
sqlStatement: |
SELECT o.opinion_id, o.sku
FROM ${data()} AS o
LEFT JOIN `alpinashop-datos.alpinashop_analitica.productos` AS p
ON p.sku = o.sku
WHERE p.sku IS NULL
dimension: CONSISTENCY
name: sku-existe-en-catalogo
# Regla SQL: frescura
- sqlAssertion:
sqlStatement: |
SELECT 1 FROM (SELECT MAX(fecha) AS ultima FROM ${data()})
WHERE DATE_DIFF(CURRENT_DATE(), ultima, DAY) > 7
dimension: FRESHNESS
name: opiniones-recientesgcloud dataplex datascans create data-quality calidad-opiniones \
--location=europe-west1 \
--data-source-resource="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/opiniones" \
--data-quality-spec-file=calidad-opiniones.yaml \
--schedule="0 6 * * *" \
--export-results-table="//bigquery.googleapis.com/projects/alpinashop-datos/datasets/alpinashop_analitica/tables/resultados_calidad"(d) Acción ante cada fallo:
| Regla | Gravedad | Acción |
|---|---|---|
opinion-id-obligatorio |
Crítica | Bloquear el proceso. Un identificador nulo rompe la idempotencia del MERGE |
opinion-id-unico |
Crítica | Bloquear e investigar: indica reproceso mal hecho, exactamente el fallo de 04-06 |
puntuacion-de-1-a-5 |
Alta | Alerta y cuarentena de esas filas. Una puntuación de 9 falsea todas las medias |
pais-en-lista |
Media | Ticket al equipo web. Puede ser un país nuevo legítimo: revisar y ampliar la lista |
sku-obligatorio |
Alta | Alerta: una opinión sin producto no es analizable |
sku-existe-en-catalogo |
Media | Investigar: producto descatalogado y borrado del maestro, o error de escritura |
opiniones-recientes |
Alta | Alerta al equipo de plataforma: probablemente el pipeline de ingesta está roto y nadie lo sabe |
Solución 2
(a) Inspección.
{
"inspectJob": {
"storageConfig": {
"bigQueryOptions": {
"tableReference": {
"projectId": "alpinashop-datos",
"datasetId": "alpinashop_analitica",
"tableId": "opiniones"
},
"identifyingFields": [{"name": "opinion_id"}],
"sampleMethod": "RANDOM_START",
"rowsLimitPercent": 20
}
},
"inspectConfig": {
"infoTypes": [
{"name": "EMAIL_ADDRESS"},
{"name": "PHONE_NUMBER"},
{"name": "PERSON_NAME"},
{"name": "STREET_ADDRESS"},
{"name": "ES_NIF_NUMBER"},
{"name": "IBAN_CODE"},
{"name": "CREDIT_CARD_NUMBER"},
{"name": "URL"}
],
"minLikelihood": "POSSIBLE",
"includeQuote": false,
"limits": {"maxFindingsPerRequest": 3000}
},
"actions": [
{"saveFindings": {"outputConfig": {"table": {
"projectId": "alpinashop-datos",
"datasetId": "alpinashop_analitica",
"tableId": "hallazgos_dlp_opiniones"
}}}}
]
}
}Las tres decisiones que se evalúan:
includeQuote: false: sin esto, la tablahallazgos_dlp_opinionescontendría los teléfonos y nombres reales encontrados. Se habría creado una segunda copia sin proteger de exactamente lo que se quiere proteger.minLikelihood: POSSIBLEen lugar deLIKELY: en texto libre escrito por personas, los datos aparecen en formatos irregulares ("llamadme al seis once..."). Un umbral bajo genera más falsos positivos, que es preferible a no detectar un dato real. En una columna estructurada se usaríaLIKELY.identifyingFields: permite saber qué opinión contiene el hallazgo sin guardar el valor, lo que hace el resultado accionable.
(b) Plantilla de desidentificación.
{
"deidentifyTemplate": {
"displayName": "Desidentificacion de opiniones",
"deidentifyConfig": {
"recordTransformations": {
"fieldTransformations": [
{
"fields": [{"name": "texto"}],
"infoTypeTransformations": {
"transformations": [
{
"infoTypes": [
{"name": "PERSON_NAME"}, {"name": "PHONE_NUMBER"},
{"name": "EMAIL_ADDRESS"}, {"name": "STREET_ADDRESS"},
{"name": "ES_NIF_NUMBER"}, {"name": "IBAN_CODE"},
{"name": "CREDIT_CARD_NUMBER"}
],
"primitiveTransformation": {
"replaceWithInfoTypeConfig": {}
}
}
]
}
}
]
}
}
}
}Por qué replaceWithInfoTypeConfig y no supresión. Sustituye cada hallazgo por su tipo entre corchetes:
- Original:
"Muy comoda. Si teneis dudas escribidme a [email protected] o al 611223344, soy Ana Ruiz" - Resultado:
"Muy comoda. Si teneis dudas escribidme a [EMAIL_ADDRESS] o al [PHONE_NUMBER], soy [PERSON_NAME]"
El texto sigue siendo analizable. La opinión conserva su estructura, su longitud aproximada y —lo importante para el módulo 5— su sentimiento: "Muy cómoda" sigue ahí. Con supresión pura se perdería la coherencia sintáctica y el análisis de sentimiento se degradaría.
(c) Vista expuesta.
CREATE OR REPLACE VIEW `alpinashop-datos.alpinashop_analitica.v_opiniones_analitica` AS
SELECT
opinion_id,
sku,
fecha,
puntuacion,
texto_desidentificado AS texto,
pais,
CASE WHEN puntuacion >= 4 THEN 'positiva'
WHEN puntuacion = 3 THEN 'neutra'
ELSE 'negativa' END AS valoracion,
LENGTH(texto_desidentificado) AS longitud_texto
FROM `alpinashop-datos.alpinashop_analitica.opiniones_desidentificadas`;El pipeline nocturno escribe en opiniones_desidentificadas aplicando la plantilla, y la tabla original opiniones queda con acceso restringido a gcp-seguridad@. Looker Studio, Lucía y los futuros modelos del módulo 5 consumen exclusivamente la vista.
(d) Advertencia de RGPD.
Las opiniones de clientes contienen datos personales, tanto en campos estructurados (
paiscombinado conskuyfechapuede ser reidentificable si el volumen es bajo) como incrustados en texto libre. Esta propuesta aplica dos medidas técnicas: desidentificación automática del texto mediante Sensitive Data Protection y restricción de acceso a la tabla original.Antes de pasar a producción es necesario que un profesional de compliance o el delegado de protección de datos determine: la base legal del tratamiento analítico de las opiniones; si la desidentificación aplicada constituye anonimización efectiva o mera seudonimización —lo que determina si el RGPD sigue aplicando al resultado—; el plazo de conservación adecuado; el riesgo de reidentificación por combinación de campos aparentemente no identificativos; y si el uso previsto en el módulo 5 (análisis de sentimiento) requiere información adicional al interesado. 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 de dato predefinido. Todos los datos de este curso son ficticios.
Solución 3
Gravedad relativa, de mayor a menor:
Problema 3 (enlace público) + Problema 2 (credenciales del propietario) = INCIDENTE DE SEGURIDAD.
No son dos problemas: son uno solo y grave. Que todas las consultas las haga Lucía significa modo credenciales del propietario. Combinado con "cualquier persona con el enlace", cualquiera que tenga o reciba ese enlace consulta BigQuery con los permisos de Lucía, que incluyen la tabla pedidos con email_cliente. Si el consultor externo reenvió el enlace, o si acabó en un correo o un chat, hay una posible brecha de datos personales con obligación de notificación. Se actúa primero, hoy, en minutos.
Problema 4 (el número no cuadra con contabilidad) = CRISIS DE CONFIANZA. Es el segundo en gravedad porque destruye el valor de todo lo construido. Un cuadro de mando en el que dirección no confía deja de usarse, y el trabajo de siete lecciones se pierde.
Problema 1 (lento y caro) = OPERATIVO. 310 € al mes duelen, pero no comprometen ni la seguridad ni la confianza. Se arregla en una tarde.
Actuación en tres fases:
Fase 1 — Hoy, en minutos: cortar la exposición.
1. Cambiar la compartición a "Personas concretas": gcp-datos@ y direccion@. Eliminar "cualquier persona con el enlace". 2. Cambiar el origen de datos a CREDENCIALES DEL ESPECTADOR. 3. Comprobar a qué apunta el origen. Si es una tabla con email_cliente, repuntarlo a la vista desidentificada v_pedidos_analitica.
-- Comprobar el alcance real: quien ha consultado y cuanto, en 60 dias
SELECT
user_email,
COUNT(*) AS consultas,
MIN(creation_time) AS primera,
MAX(creation_time) AS ultima,
COUNT(DISTINCT DATE(creation_time)) AS dias_distintos
FROM `region-europe-west1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 60 DAY)
AND job_type = 'QUERY'
AND STRPOS(IFNULL(query, ''), 'alpinashop_analitica') > 0
GROUP BY user_email
ORDER BY consultas DESC;Y en paralelo: avisar al responsable de seguridad y al DPO. La valoración de si esto constituye una brecha notificable bajo el RGPD no la toma el equipo técnico. Hay que documentar qué datos eran accesibles, durante cuánto tiempo y a quién se compartió el enlace.
Fase 2 — Esta semana: recuperar la confianza en el número.
La discrepancia con contabilidad tiene tres causas candidatas, y hay que determinar cuál es antes de tocar nada:
-- Comparar las tres definiciones posibles de "ventas del mes"
SELECT
'A) Bruto, IVA incluido, sin cancelados' AS definicion,
ROUND(SUM(total_pedido), 2) AS importe
FROM `alpinashop-datos.alpinashop_analitica.pedidos`
WHERE fecha_pedido BETWEEN DATE '2026-03-01' AND DATE '2026-03-31'
AND estado != 'cancelado'
UNION ALL
SELECT
'B) Bruto sin IVA, sin cancelados',
ROUND(SUM(subtotal - IFNULL(descuento,0)), 2)
FROM `alpinashop-datos.alpinashop_analitica.pedidos`
WHERE fecha_pedido BETWEEN DATE '2026-03-01' AND DATE '2026-03-31'
AND estado != 'cancelado'
UNION ALL
SELECT
'C) Neto sin IVA, sin cancelados NI devueltos',
ROUND(SUM(subtotal - IFNULL(descuento,0)), 2)
FROM `alpinashop-datos.alpinashop_analitica.pedidos`
WHERE fecha_pedido BETWEEN DATE '2026-03-01' AND DATE '2026-03-31'
AND estado NOT IN ('cancelado','devuelto');Casi con total seguridad, contabilidad usa la definición C —ventas netas sin IVA— y el informe muestra la A. No es un error de datos: es un error de definición no documentada, exactamente el segundo problema del inicio de esta lección.
La corrección:
- Acordar con contabilidad una definición canónica y escribirla.
- Implementarla en la tabla
bi_resumen_diario, exponiendo ambas magnitudes con nombres inequívocos:ventas_brutas_iva_inclyventas_netas_sin_iva. - Escribirla en la etiqueta
definicion_negociodel catálogo de Dataplex. - Añadir una nota metodológica visible al pie del cuadro de mando.
Fase 3 — Este mes: coste y rendimiento.
El diagnóstico primero:
SELECT
SUBSTR(REGEXP_REPLACE(query, r'\s+', ' '), 1, 200) AS consulta,
COUNT(*) AS ejecuciones,
ROUND(AVG(total_bytes_billed)/POW(1024,3), 2) AS gb_por_ejecucion,
ROUND(SUM(total_bytes_billed)/POW(1024,4), 2) AS tb_totales,
COUNTIF(cache_hit) AS desde_cache
FROM `region-europe-west1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), MONTH)
AND job_type = 'QUERY'
GROUP BY consulta
ORDER BY tb_totales DESC
LIMIT 10;El resultado esperado: el informe está conectado directamente a lineas_pedido o a pedidos, y cada uno de los ocho gráficos ejecuta su propia consulta cada vez que alguien mueve un filtro. Doce personas × varios filtros al día × ocho gráficos = miles de consultas sobre millones de filas.
Las cuatro correcciones, por orden de impacto:
- Repuntar el informe a
bi_resumen_diario, la tabla agregada de unos pocos miles de filas que produce el proceso nocturno. Sola, esta medida suele dividir el coste por cien y bajar la carga de 40 segundos a menos de 3. - Activar la caché del informe con 12 horas de vigencia. Doce personas mirando generan una consulta, no doce.
- Reservar 2 GB de BI Engine. La tabla agregada cabe entera en memoria: respuesta en milisegundos y cero bytes facturados.
- Poner una cuota diaria de bytes en el proyecto y una alerta de presupuesto sobre la etiqueta
centro-coste:analitica, para que un problema equivalente se detecte a los 20 € y no a los 310 €.
Resultado esperado: de 310 €/mes a menos de 10 €, y de 40 segundos a menos de 3.
La lección transversal del ejercicio: los cuatro problemas eran evitables con lo que ya sabíamos. El coste, con lo de 04-01. Los permisos, con lo de 03-04 y el apartado 13. La discrepancia de definiciones, con el etiquetado del apartado 4. Ninguno era un fallo técnico difícil: los cuatro eran fallos de gobierno.
Conclusión
El módulo 4 se cierra aquí, y se cierra con la plataforma completa.
Has entendido qué es el gobierno del dato —catálogo, linaje, calidad, clasificación y ciclo de vida— y por qué una pyme lo necesita igual o más que una multinacional: el RGPD no distingue por tamaño, el conocimiento no tiene redundancia cuando el equipo son tres personas, los errores llegan a dirección sin filtros intermedios, y la deuda de gobierno se acumula con intereses.
Has organizado la plataforma en Dataplex: el lago alpinashop-lago-comercial con su zona cruda y su zona curada, con los buckets y el dataset registrados como activos y con descubrimiento automático que convierte los Parquet de Spark en tablas consultables sin declarar nada. Has usado el catálogo universal para responder en segundos a "¿dónde está el dato de conversión?" y a "¿dónde hay columnas que se llamen email". Y has documentado con una plantilla de etiquetas que obliga a declarar propietario, dominio, sensibilidad, frescura, retención, aptitud para informes y —lo más valioso— la definición de negocio, esa frase escrita una vez que zanja para siempre si total_pedido incluye IVA.
Has perfilado las tablas antes de juzgarlas, descubriendo lo que nadie sospechaba: un 12 % de pedidos sin cliente, cinco valores en canal donde debía haber tres, y un total_pedido negativo que distorsionaba todas las medias. Y sobre ese conocimiento has definido reglas de calidad en las seis dimensiones, con umbrales, con exportación de resultados y —esto es lo que las hace útiles— con una acción asignada a cada fallo y con integración en el orquestador para que un fallo crítico detenga el proceso antes de propagar datos malos.
Has activado el linaje automático y tienes el grafo completo desde Cloud SQL y el ERP de Sabadell hasta el cuadro de mando, capaz de responder de dónde viene un número, qué se rompe si cambias un esquema, dónde está el dato de un cliente que ejerce su derecho de supresión, y qué proceso se puede apagar sin miedo.
Has inspeccionado con Sensitive Data Protection y has encontrado lo que siempre se encuentra: emails en el volcado crudo de Pub/Sub, nombres en el campo de incidencias del transportista, teléfonos dentro de opiniones de clientes, e IP en los datos de navegación. Cuatro sorpresas de cinco hallazgos, ninguna por mala fe. Y has desidentificado con criterio: hash con clave de KMS para poder contar clientes sin saber quiénes, enmascarado del código postal conservando la provincia, y sustitución por tipo dentro del texto libre para que las opiniones sigan siendo analizables sin datos personales dentro. Con la advertencia expresa, repetida donde tocaba, de que esto son controles técnicos y de que la decisión jurídica corresponde a un profesional de compliance o al DPO.
Y has construido por fin el cuadro de mando que dirección pedía: conectado a una tabla agregada pequeña y no a la tabla de detalle, con caché, con BI Engine, con campos calculados definidos en SQL versionado y no encerrados en el informe, con cuatro indicadores y no doce, con comparación siempre, con fecha de actualización visible y con nota metodológica al pie. Y con la lección de permisos que vale por sí sola toda la lección: credenciales del espectador, compartición con personas concretas, y el origen apuntando siempre a una vista desidentificada, porque es la única defensa que no depende de que nadie se acuerde. Sabes cuándo Looker con LookML justifica su precio —cuando el coste de la incoherencia supera el de la licencia— y por qué AlpinaShop no está en ese caso.
Mira lo que existe ahora y que no existía hace siete lecciones. Los pedidos entran por Pub/Sub sin acoplar la tienda a nada. Dataflow los transforma en lote y en streaming, con el tiempo del evento bien entendido. Dataproc calcula lo algorítmico en clústeres que viven cuatro minutos. Data Fusion trae lo que llega sucio de fuera. BigQuery lo guarda y lo responde en segundos, particionado, clusterizado y sin arruinar a nadie. Workflows lo orquesta cada noche con comprobaciones que bloquean si algo no cuadra. Dataplex lo cataloga, lo mide y lo rastrea. Y Looker Studio lo enseña. AlpinaShop ha pasado de no saber qué mochila se vende más en Cataluña a tener una plataforma de datos gobernada por la que Lucía no le pide nada a nadie.
Pero fíjate en lo que todo esto tiene en común: describe lo que ya pasó. Cuenta las ventas de ayer, el embudo del mes, el margen del trimestre, qué productos se compraron juntos. Es memoria, y la memoria es valiosísima. No es predicción, y no es automatización.
Y AlpinaShop tiene ahora mismo cuatro preguntas que la memoria no puede responder. Cuando un cliente mete unos crampones en el carrito, ¿qué debería sugerirle la web en ese instante, aprovechando la matriz de coocurrencia que calculó Spark? Cuando entran 60 GB de imágenes nuevas al catálogo, ¿tiene que etiquetarlas alguien a mano o puede la máquina decir "esto es una mochila azul de 40 litros"? Con 8.000 opiniones en texto libre ya desidentificadas, ¿alguien las va a leer todas para saber qué falla en los frontales? Y con 2.400 fichas de producto que describir, ¿quién las escribe?
En el módulo 5, Aprendizaje automático e IA, AlpinaShop pasa de describir a predecir y automatizar. Empezaremos por 05-01, Vertex AI, la plataforma que unifica todo el ciclo de vida del modelo —datos, entrenamiento, evaluación, despliegue y monitorización— y que conectará directamente con el dataset alpinashop_analitica que acabas de gobernar. Después llegarán AutoML para entrenar sin escribir código, TensorFlow para cuando haga falta control total, las API de lenguaje natural y de visión que responden a las preguntas de las opiniones y de las imágenes sin entrenar nada, la IA generativa con Gemini para las descripciones, y MLOps con Vertex AI Pipelines para que un modelo no sea un experimento en el portátil de alguien sino un sistema de producción con la misma disciplina de idempotencia, calidad, linaje y coste que has aplicado en todo este módulo.
Los datos ya están limpios, gobernados, documentados y consultables. Eso no era el objetivo: era el requisito. Ahora empieza lo interesante.
Curso de Google Cloud Platform (GCP)
Módulo 1: Introducción a Google Cloud Platform
- ¿Qué es Google Cloud Platform?
- Configuración de tu cuenta de GCP
- Descripción general de la consola de GCP
- Proyectos, jerarquía de recursos y facturación
- Regiones, zonas y modelo de responsabilidad compartida
- Cloud Shell y la CLI de gcloud
Módulo 2: Servicios principales de GCP
- Compute Engine: máquinas virtuales en Google Cloud
- Cloud Storage: almacenamiento de objetos
- Cloud SQL: bases de datos relacionales gestionadas
- App Engine: plataforma como servicio
- Google Kubernetes Engine (GKE)
- Bases de datos NoSQL: Firestore, Bigtable y Spanner
- Cómo elegir el servicio de cómputo adecuado
Módulo 3: Redes y seguridad
- Redes VPC
- Balanceo de carga en la nube
- Cloud CDN
- Gestión de identidad y acceso (IAM)
- Cloud Armor
- Secretos y cifrado: Secret Manager y Cloud KMS
- Cloud DNS, certificados TLS y publicación segura de servicios
Módulo 4: Datos y análisis
- BigQuery: el almacén de datos analítico
- Cloud Dataflow: procesamiento de datos por lotes y en streaming
- Cloud Dataproc: Spark y Hadoop gestionados
- Cloud Pub/Sub: mensajería asíncrona
- Cloud Data Fusion: integración de datos sin código
- Orquestación de pipelines con Cloud Composer y Workflows
- Gobierno del dato y cuadros de mando con Dataplex y Looker Studio
Módulo 5: Aprendizaje automático e IA
- Vertex AI: la plataforma de machine learning de GCP
- AutoML: modelos a medida sin escribir código
- TensorFlow en GCP: entrenamiento y servicio de modelos
- API de lenguaje natural
- API de visión
- IA generativa en Vertex AI: modelos Gemini y embeddings
- MLOps: del modelo al producto con Vertex AI Pipelines
Módulo 6: DevOps y monitoreo
- Cloud Build: integración continua en GCP
- Cloud Source Repositories y gestión del código fuente
- Cloud Functions: funciones sin servidor
- Cloud Monitoring (antes Stackdriver): métricas, paneles y alertas
- Cloud Deployment Manager e infraestructura como código nativa
- Cloud Logging y Cloud Trace: logs, trazas y diagnóstico
- Terraform en GCP: infraestructura como código en la práctica
Módulo 7: Temas avanzados de GCP
- Híbrido y multinube con Anthos
- Computación sin servidor con Cloud Run
- Redes avanzadas: VPC compartida, peering y conectividad híbrida
- Mejores prácticas de seguridad
- Gestión y optimización de costos
- Fiabilidad: SLO, alta disponibilidad y recuperación ante desastres
- Gobierno a escala: organización, políticas y auditoría
