cultiva_analytics.malloy — Modelo semantico completo para la cartera de clientes de CULTIVA IA.
Define fuentes reutilizables sobre 4 tablas Parquet (clientes, proyectos, horas, pipeline), con dimensiones, medidas y vistas composables.
Las queries se encadenan con -> y los dashboards se construyen con nest anidado — todo en SQL semantico, sin duplicar logica.
1
Fuentes definidas→
2
Vistas reutilizables→
3
Queries composables→
4
Dashboard anidadoModelo: cultiva_analytics.malloy
Fuentes, dimensiones, medidas y vistas reutilizables para toda la analitica de la agencia
models/cultiva_analytics.malloy
malloy
// ============================================================
// CULTIVA IA — Modelo Semántico de Datos
// Conexión: DuckDB local con archivos Parquet del CRM (Huly)
// ============================================================
// --- Conexión DuckDB ---
connection: db is duckdb [
parquet_path: "./data/"
]
// ============================================================
// FUENTE 1: clientes
// ============================================================
source: clientes is db.table('clientes.parquet') extend {
dimension:
mes_alta is fecha_alta.month
anio_alta is fecha_alta.year
es_enterprise is plan = 'enterprise'
tier_label is pick
'Starter' when plan = 'starter'
'Growth' when plan = 'growth'
'Enterprise'
measure:
cliente_count is count()
nuevos_mes is count() { where: mes_alta = now.month }
view: por_plan is {
group_by: tier_label
aggregate: cliente_count
order_by: cliente_count desc
}
}
// ============================================================
// FUENTE 2: proyectos — ingresos por servicio y cliente
// ============================================================
source: proyectos is db.table('proyectos.parquet') extend {
join_one: clientes on cliente_id = clientes.id
dimension:
mes_inicio is fecha_inicio.month
anio_inicio is fecha_inicio.year
duracion_dias is days(fecha_inicio to fecha_cierre)
servicio_cat is pick
'Marketing IA' when servicio = 'marketing'
'Web & Diseño' when servicio = 'web'
'Automatización' when servicio = 'automatizacion'
'Contenido IA' when servicio = 'contenido'
'Datos & Analytics' when servicio = 'datos'
'Agentes IA'
measure:
proyecto_count is count()
ingreso_total is sum(importe_eur)
ingreso_medio is avg(importe_eur)
proyectos_activos is count() { where: estado = 'activo' }
tasa_cierre is count() { where: estado = 'cerrado' } / proyecto_count
view: revenue_por_servicio is {
group_by: servicio_cat
aggregate: ingreso_total, proyecto_count, ingreso_medio
order_by: ingreso_total desc
}
view: top_clientes is {
group_by: clientes.nombre, clientes.sector, clientes.plan
aggregate: ingreso_total, proyecto_count
order_by: ingreso_total desc
limit: 10
}
view: evolucion_mensual is {
group_by: mes_inicio
aggregate: ingreso_total, proyecto_count, proyectos_activos
order_by: mes_inicio
}
}
// ============================================================
// FUENTE 3: horas — eficiencia y carga por consultor
// ============================================================
source: horas is db.table('horas.parquet') extend {
join_one: proyectos on proyecto_id = proyectos.id
dimension:
semana is fecha.week
mes is fecha.month
tipo_tarea is pick
'Estrategia' when tarea = 'estrategia'
'Ejecucion' when tarea = 'ejecucion'
'Revision' when tarea = 'revision'
'Reporting'
measure:
horas_total is sum(horas)
horas_promedio is avg(horas)
ingreso_hora is proyectos.ingreso_total / horas_total
view: eficiencia_consultores is {
group_by: consultor
aggregate: horas_total, ingreso_hora
order_by: ingreso_hora desc
}
view: carga_semanal is {
group_by: consultor, semana
aggregate: horas_total
order_by: semana
}
}
Queries composables y Dashboard anidado
Encadenamiento con pipeline -> y agregaciones multidimensionales con nest
queries/revenue_analysis.malloy
malloy
import "models/cultiva_analytics.malloy"
// Pipeline: solo proyectos 2026, por servicio
run: proyectos
-> { where: anio_inicio = 2026 }
-> revenue_por_servicio // vista reutilizable
-> { where: ingreso_total > 5000 }
// Nested: por servicio → desglose mensual + top clientes
run: proyectos -> {
where: anio_inicio = 2026
group_by: servicio_cat
aggregate:
ingreso_total
proyecto_count
ingreso_medio
// nested level 1: tendencia mensual
nest: por_mes is {
group_by: mes_inicio
aggregate: ingreso_total
order_by: mes_inicio
}
// nested level 2: top 3 clientes por servicio
nest: top_3 is {
group_by: clientes.nombre
aggregate: ingreso_total
order_by: ingreso_total desc
limit: 3
}
}
queries/eficiencia_equipo.malloy
malloy
import "models/cultiva_analytics.malloy"
// Eficiencia: €/hora por consultor con nested tareas
run: horas -> {
where: mes = 2026.06
group_by: consultor
aggregate:
horas_total
ingreso_hora
// nested: distribución por tipo de tarea
nest: por_tarea is {
group_by: tipo_tarea
aggregate: horas_total
order_by: horas_total desc
}
order_by: ingreso_hora desc
}
// Cross-join: clientes × servicio matriz de ingresos
run: proyectos -> {
group_by: clientes.tier_label
aggregate: ingreso_total
nest: por_servicio is {
group_by: servicio_cat
aggregate:
ingreso_total
proyecto_count
order_by: ingreso_total desc
}
}
// Pipeline funnel comercial
run: pipeline
-> { group_by: fase; aggregate: leads, valor_total }
-> { where: leads > 0; order_by: valor_total desc }
Resultado simulado — Dashboard CULTIVA IA Q2 2026
Salida de la query run: proyectos -> dashboard_q2 renderizada como visualizacion
Ingresos Q2 2026
€142.800
+28% vs Q2 2025
Proyectos activos
34
18 cerrados en Q2
Ticket medio
€2.740
Ingreso por proyecto
Clientes activos
41
6 nuevos en junio
Revenue por servicio · revenue_por_servicio view
Ingresos totales Q2 2026 · malloy: group_by servicio_cat
Resultado nested · servicio → top 3 clientes
malloy: nest: top_3 is { group_by: clientes.nombre; limit: 3 }
Automatización
€42.300
top clientes
Grupo Menta SL€14.200
FinOps Analytics€11.800
Clínica Avanza€9.500
Agentes IA
€34.500
top clientes
TechRetail Madrid€16.000
Grupo Menta SL€10.300
Inmobiliaria Nova€8.200
Marketing IA
€26.400
top clientes
EcoFarm Organic€9.600
StudyFlow App€8.800
Despacho Morales€8.000
Top clientes por ingreso · top_clientes view
malloy: run: proyectos → top_clientes (limit 8)
| # | Cliente | Sector | Plan | Ingresos | Proyectos | Ticket medio |
|---|---|---|---|---|---|---|
| 1 | Grupo Menta SL | Retail | Enterprise | €24.500 | 7 | €3.500 |
| 2 | TechRetail Madrid | Tecnología | Enterprise | €22.800 | 6 | €3.800 |
| 3 | FinOps Analytics | Fintech | Growth | €18.200 | 5 | €3.640 |
| 4 | EcoFarm Organic | Agrotech | Growth | €14.600 | 4 | €3.650 |
| 5 | Inmobiliaria Nova | Real Estate | Growth | €12.400 | 4 | €3.100 |
| 6 | StudyFlow App | EdTech | Growth | €11.200 | 3 | €3.733 |
| 7 | Clínica Avanza | Salud | Starter | €9.500 | 3 | €3.167 |
| 8 | Despacho Morales | Legal | Starter | €8.000 | 2 | €4.000 |
Eficiencia consultores
€/hora · eficiencia_consultores view
query ejecutada
run: horas -> eficiencia_consultores
Pipeline Comercial — Funnel CRM
malloy: run: pipeline → pipeline_funnel · datos de leads del CRM Huly via Parquet export
Funnel por fase · pipeline_funnel view
Leads activos por fase con valor estimado
Prospecto
24
€88.000
Contactado
19
€72.400
Propuesta
13
€54.600
Negociacion
8
€38.200
Cierre
4
€22.000
pipeline malloy
run: pipeline -> { group_by: fase; aggregate: leads, valor_total } -> { where: leads > 0 }
Mix de servicios en pipeline
Valor estimado por servicio de interes
Metricas de conversion
Leads totales activos
68
Valor pipeline total
€275.200
Valor esperado (prob.)
€62.400
Tasa conversion historica
22.7%
cultiva_analytics.malloy
4 fuentes · 8 vistas reutilizables · DuckDB 1.1 · Parquet · VS Code Malloy Extension
malloy v0.0.238
duckdb 1.1.0
apache-2.0