4dim / Notas

A qué hora le escriben a un negocio

Nadie ha publicado la curva por hora en Colombia. Tenemos las bandejas delante y todavía no podemos publicarla: esto es por qué.

La pregunta, y por qué esta nota no la contesta

«¿A qué hora le escriben a un negocio?» Nos la hacen en cada reunión de Connection, y la contestábamos con una intuición.

Esta nota no trae la curva. Trae por qué no la tenemos: lo medido, lo que circula sin fuente, y lo que podemos medir y todavía no publicar.

Lo que sí está medido, y no es la hora

La mejor encuesta de tiempo del país es la ENUT del DANE: cerca de 33.500 hogares, boletín preliminar del 28 de octubre de 2025. Mide cuántas horas al día dedica cada quien a cada actividad. No a qué hora del día: le extrajimos el texto al PDF y buscamos «franja», «hora del día» y «por hora». Ninguna aparece, el 13-09-2026.

DataReportal da la conectividad a octubre de 2025 —41,7 millones de usuarios de internet, el 77,8 % de la población— pero nada por hora, ni penetración de WhatsApp: el «92,1 %» que se le atribuye no está ahí.

FuenteQué mideResolución temporalComprobado
DANE, ENUT 2024-2025tiempo diario por actividaddía, no hora13-09-2026
DataReportal, Digital 2026usuarios de internet y de redesninguna (foto anual)13-09-2026
Meta + BCG, Brasilmensajería empresarialno publicada (tras formulario)13-09-2026
Meta + BCG, Méxicomensajería empresarialno publicada (tras formulario)13-09-2026

Ninguna fila dice «hora». Los informes de Meta y BCG abren, pero sus cifras están tras un formulario: no las hemos leído.

La cifra que circula, y dónde se corta el rastro

El 27 de agosto de 2026, Administradores (Brasil) publicó que el 42,2 % de los mensajes llega fuera del horario comercial, a partir de un estudio de AgeuBot. Fuimos a buscarlo. No está.

EslabónQué diceMetodologíaOriginal
Administradores, 27-08-202657,8 % en horario, 42,2 % fuera; 19 h = +24 % sobre las 8 h; fin de semana = 20 %nono enlaza
AgeuBot, autor declarado1,3 M de mensajes, 62 mil clientesnono está en su sitio
Blog del proveedor, estadísticascita Meta, Statista, Opinion Box, Sebrae y datos internosningunano
Blog, fuera de horariocita «Meta Business Report 2025», Opinion Box, RD Station, HBR, Gartner, Zendeskningunano

Comprobado el 13 de septiembre de 2026. Mide la base de clientes de un vendedor de chatbots, no un mercado, y ni eso se puede comprobar. Ni es la única cifra por franja: en la misma página hay un «38 % de las compras entre las 19 h y las 23 h» de RD Station, igual de suelto.

Tenemos la base de datos delante y aun así no podemos publicar la curva. Decir exactamente por qué es la nota.

Curvas por hora sí hay, de otro continente y otro canal

Aledavood y otros publicaron en PLOS ONE, en 2015, el patrón horario de 74.124 llamadas del Reino Unido; Pintér y Felde lo mismo en Budapest en 2022. La versión de conferencia del primero trae el matiz que más nos importa, y se cita como está escrito: los patrones «pueden diferir» entre llamadas y textos. Potencial, no indicativo — y aun así, la razón de que nuestra consulta separe por canal.

El único conjunto colombiano de alta resolución es de Bogotá y mide desplazamiento; el estudio más cercano al tema, 200 empresas de México y España, consultó solo en horario de atención. Buscamos en DANE, DataReportal, PLOS ONE, SciELO, arXiv y Meta/BCG: existen los métodos, no la medición. Una afirmación negativa vale lo que valga la búsqueda, y por eso va dicho dónde buscamos.

Nuestro dato: la consulta, publicada antes de correrla

Esto es lo que habría que ejecutar sobre las bandejas de Connection, publicado antes de correrlo para que nadie tenga que creernos. created_at es timestamptz: sin huso guardado, la hora local no se recupera y esta nota no existiría.

WITH entrantes AS (
  SELECT
    EXTRACT(hour FROM m.created_at AT TIME ZONE 'America/Bogota')::int AS hora,
    CASE WHEN EXTRACT(isodow FROM m.created_at AT TIME ZONE 'America/Bogota') <= 5
         THEN 'lun-vie' ELSE 'sab-dom' END AS dia,
    cn.kind AS canal, cv.tenant_id AS negocio, cv.id AS hilo
  FROM messages      m
  JOIN conversations cv ON cv.id = m.conversation_id
  JOIN tenants       t  ON t.id  = cv.tenant_id
  JOIN connections   cn ON cn.id = cv.connection_id
  WHERE m.author        = 'cliente'     -- entrante
    AND m.external_id  IS NOT NULL      -- pasó por Meta de verdad
    AND cn.external_id IS NOT NULL      -- bandeja con número, no escaparate
    AND t.is_demo       = false         -- cinturón, no tirantes (ap. 07)
    AND m.created_at   >= timestamptz '2026-08-15 00:00:00-05'
    AND m.created_at    < date_trunc('month', now() AT TIME ZONE 'America/Bogota')
                            AT TIME ZONE 'America/Bogota'
)
SELECT canal, dia, hora,
       count(DISTINCT hilo)    AS hilos,
       count(DISTINCT negocio) AS negocios,
       round(100.0 * count(*) / sum(count(*)) OVER (PARTITION BY canal, dia), 2) AS pct
  FROM entrantes
 GROUP BY canal, dia, hora
 ORDER BY canal, dia, hora;

No hay conteos absolutos en la salida, y es a propósito: la regla del apartado siguiente. Hace falta una segunda, sobre la misma ventana, que cuente lo que la primera tira en silencio — connection_id es ON DELETE SET NULL:

SELECT
  count(*)                                         AS entrantes_en_ventana,
  count(*) FILTER (WHERE m.external_id  IS NULL)   AS sin_id_de_meta,
  count(*) FILTER (WHERE cv.connection_id IS NULL) AS sin_bandeja,
  count(DISTINCT cv.tenant_id) AS negocios, count(DISTINCT cn.id) AS bandejas
  FROM messages         m
  JOIN conversations    cv ON cv.id = m.conversation_id
  LEFT JOIN connections cn ON cn.id = cv.connection_id
 WHERE m.author = 'cliente'
   AND m.created_at >= timestamptz '2026-08-15 00:00:00-05'
   AND m.created_at  < date_trunc('month', now() AT TIME ZONE 'America/Bogota')
                         AT TIME ZONE 'America/Bogota';

Es la que dice cuántas bandejas reales hay. Verificado contra el repositorio del 13 de septiembre de 2026: revalidar el día de correrlo.

Qué no sale de la base, y cómo se impone

La anonimización no es promesa: la salida no tiene una columna de texto libre. Se impone con un rol de solo lectura por columna, no con disciplina:

CREATE ROLE notas_horas NOLOGIN;
GRANT USAGE ON SCHEMA public TO notas_horas;
GRANT SELECT (id, conversation_id, author, external_id, created_at) ON messages      TO notas_horas;
GRANT SELECT (id, tenant_id, connection_id)                         ON conversations TO notas_horas;
GRANT SELECT (id, tenant_id, kind, external_id)                     ON connections   TO notas_horas;
GRANT SELECT (id, is_demo)                                          ON tenants       TO notas_horas;

Que SELECT * falle bajo privilegios parciales no lo dice la documentación de PostgreSQL: hay que ejecutarlo y publicar el permission denied. Queda una tensión que no conviene esconder: lo único que diría si quien escribe está en otro huso es el indicativo de su número, justo la columna que el rol prohíbe. Proteger el teléfono y saber el país del contacto son incompatibles: elegimos proteger.

La regla: porcentajes, nunca conteos absolutos, porque un conteo por hora deja reconstruir el volumen de un negocio. Se suprime toda celda con menos de 5 negocios o 20 hilos distintos, y de dos en dos — una sola celda tapada se deduce por resta del 100 %. Con un negocio conectado, ese umbral suprime la tabla entera.

Marco: la Ley 1581 de 2012 distingue al Responsable, que decide sobre la base de datos, del Encargado, que la trata por cuenta de aquel. Cada negocio es responsable de sus clientes; nosotros, encargados. Que un agregado sin celdas pequeñas deje de ser dato personal es lectura nuestra, no de la Superintendencia: el permiso lo da el contrato.

Tres trampas de nuestro propio esquema

Guardamos la hora equivocada. created_at es timestamptz DEFAULT now() y el INSERT del webhook no pasa fecha: Meta manda la hora a la que el cliente le dio a enviar y la tiramos — no aparece ni una vez en las 413 líneas del webhook. La curva de hoy sería de cuándo nos llegaron, no de cuándo los escribieron. Cota del sesgo: el webhook espera hasta 8 segundos por el adjunto antes de insertar, lo que como mucho mueve de casilla al 0,222 % de los mensajes con archivo — cálculo nuestro, 8 sobre 3.600, cota superior con llegada uniforme supuesta. Añadir sent_at lo arregla; hasta entonces la hora de envío es irrecuperable.

No sabemos en qué huso vive nadie. tenants no tiene columna de huso: ni «timezone» ni «America/Bogota» aparecen en las migraciones. Y la curva es de los clientes, de los que no sabemos ni eso ni el país.

La razón que nos habíamos contado era falsa. Creíamos que is_demo hacía falta porque el demo fabrica entrantes. No los fabrica: su escalera lleva AND m.author <> 'cliente'. Lo humano se separa de lo fabricado por el id de Meta, vacío cuando llama el simulador.

Cuándo publicaremos la curva

El primer canal de WhatsApp real de 4dimntion se conectó el 15 de agosto de 2026: son 29 días. Cuántas bandejas hay hoy no lo decimos de memoria — desde el 16 de agosto un formulario las da de alta sin pasar por nadie. Publicar la curva ahora sería publicar la agenda de un negocio, el nuestro, y dejar que se cite como la de Medellín. Las tres condiciones:

  1. Que sent_at exista y lleve 90 días llenándose.
  2. Que haya cinco bandejas de negocios distintos, para que ninguna celda sea el retrato de uno solo.
  3. Que el contrato diga expresamente que podemos publicar agregados anonimizados.

Importa más allá de nosotros: la Ley 2191 de 2022 obliga a toda empresa a tener política de desconexión laboral — el derecho a no recibir contacto de trabajo «por fuera de la jornada ordinaria»— y quien no sabe a qué hora le escriben no la escribe con datos.

Al pie: America/Bogota es UTC−5 sin horario de verano desde 1993, según la base de IANA. Si se reinstaurara, las curvas de años distintos dejarían de compararse.

Esta nota sale de nuestro trabajo en Agentes conversacionales.

← todas las notas