4dim / Notas
Chatwoot o consola propia: lo que cuesta cada camino
Instalamos Chatwoot en el mismo servidor donde corre nuestra consola y lo dejamos correr. El modo enterprise autohospedado se revirtió solo, al minuto exacto que predice una fórmula pública.
Qué se comparó, y en qué condiciones
Instalamos Chatwoot v4.17.0 el 25 de agosto de 2026 en el VPS donde corre nuestra consola propia: AMD EPYC, 8 vCPU, 23,47 GiB.
Dos avisos. La instancia tiene cero conversaciones y cero mensajes: esto es un suelo, no un coste de operación. Y el código va enlazado al tag, nunca a develop, donde el mecanismo ya se mueve; comparadas las etiquetas el 13-09-2026, ningún fichero citado aquí cambia en la v4.17.1.
La licencia, leída literal
El LICENSE de la raíz es MIT Expat, con una exclusión escrita: no cubre nada bajo enterprise/, que se rige por su propio LICENSE. Ese pide dos cosas a la vez para producción: aceptar los Subscription Terms of Service y tener «a valid Chatwoot Enterprise License for the correct number of user seats». Y trae la excepción que todos citan:
«you may copy and modify the Software for development and testing purposes, without requiring a subscription»
Es cierta. Lo que sigue ocurre igualmente. Los dos ficheros, leídos el 13-09-2026.
Lo que le falta a la imagen -ce: 226 KB
La edición comunitaria no es otra compilación: el workflow publish_foss_docker.yml ejecuta rm -rf enterprise y publica con sufijo; su hermano publica v4.17.0 y latest sin borrar nada.
La API de Docker Hub daba el 13-09-2026, para amd64, 673.581.365 bytes en v4.17.0 y 673.355.002 en v4.17.0-ce: la carpeta propietaria son 226.363 bytes, el 0,03 %. Quien tira de la etiqueta por defecto se lleva ese código al disco sin saberlo.
Dieciocho banderas en un fichero, nueve en el otro
config/features.yml declara 69 banderas; dieciocho llevan premium: true y tres son internas, así que un autohospedado ve quince. El fichero que gobierna el apagado automático es otro, y solo lista nueve. No encontramos documentación que explique el criterio, y no lo inventamos.
| Bandera | Interna | La apaga el reconciliador |
|---|---|---|
disable_branding | — | sí |
audit_logs | — | sí |
custom_tools | — | — |
sla | — | sí |
help_center_embedding_search | sí | — |
captain_integration | — | sí |
custom_roles | — | sí |
captain_v1_action_classifier | sí | — |
channel_voice | — | — |
captain_integration_v2 | — | sí |
captain_document_auto_sync | — | sí |
advanced_search | — | — |
saml | — | — |
advanced_search_indexing | sí | — |
companies | — | — |
csat_review_notes | — | sí |
conversation_required_attributes | — | sí |
advanced_assignment | — | — |
Contado el 13-09-2026 sobre ambos ficheros del tag. En develop ya son 70 banderas y 19 premium.
El modo enterprise se revierte solo, y el minuto es calculable
Cuatro eslabones, todos en el código:
config/schedule.ymldispara TriggerDailyScheduledItemsJob a las 00:00 UTC.- Ese job no llama al hub de inmediato: calcula tu minuto del día con
MD5(installation_identifier).hex % 1440. - A esa hora, el override enterprise de CheckNewVersionsJob —solo en producción— sobrescribe cinco
InstallationConfigconlocked = true, entre ellos el plan. - Si el plan que vuelve es
community, ReconcilePlanConfigService devuelve diez configuraciones de marca a los valores de Chatwoot y recorre todas las cuentas condisable_features!.
Dos matices a favor de Chatwoot: avisa en Redis antes de reescribir, y usa existing_config&.update!, así que solo pisa filas ya existentes.
El MD5 de nuestro identificador, módulo 1440, da 164: las 02:44 UTC. El plan se reescribió a community el 27 de agosto de 2026 a las 02:44:03 UTC. La fórmula acierta el minuto.
| UTC | Qué ocurrió | Prueba |
|---|---|---|
| 26-08 01:03 | Plan y marca puestos a mano | updated_at |
| 26-08 02:44 | La ranura de ese día no revirtió nada | hueco sin explicar |
| 26-08 20:04 | INSTALLATION_IDENTIFIER bloqueado | updated_at |
| 27-08 02:44:03 | Plan reescrito a community | al segundo del minuto 164 |
| 13-09 15:14 | Las nueve funciones, apagadas | rails runner |
Ningún tercero puede auditar estas filas: son updated_at de nuestra base. La fórmula sí. La del 26 a las 02:44 está ahí porque no sabemos explicarla: el mecanismo predecía la reversión 1,7 h después y llegó 24 h más tarde. Lo publicable es el minuto; las ≈25,7 h son una observación, no una propiedad del sistema.
El ping que DISABLE_TELEMETRY no apaga
En lib/chatwoot_hub.rb: info.merge(instance_metrics) unless ENV['DISABLE_TELEMETRY']. La variable suprime las métricas, no la llamada.
| Campo | Qué es | Se va |
|---|---|---|
installation_identifier | el UUID de tu instalación | no |
installation_version | tu versión | no |
installation_host | tu FRONTEND_URL | no |
installation_env | tu entorno | no |
edition | ce o ee | no |
accounts_count | cuentas | sí |
users_count | usuarios | sí |
inboxes_count | bandejas | sí |
conversations_count | conversaciones | sí |
incoming_messages_count | mensajes entrantes | sí |
outgoing_messages_count | mensajes salientes | sí |
additional_information | hash vacío | sí |
«Se va» es lo que quita DISABLE_TELEMETRY. Código del tag, leído el 13-09-2026. Tres precisiones inéditas: fetch_count devuelve model.last&.id || 0, el ID más alto de la tabla, así que los *_count filtran cuánto has borrado; la variable se evalúa por PRESENCIA, así que DISABLE_TELEMETRY=false también apaga las métricas pese a que la documentación diga «set it to true»; y sí apaga entero el otro endpoint, emit_event.
La URL es una constante: el override enterprise deja cambiarla solo if Rails.env.development?, así que en producción nada redirige el ping. El 13-09-2026, hub.2.chatwoot.com resolvía a un balanceador de AWS en us-east-1. Infraestructura de terceros: puede cambiar.
La máquina: documentado contra medido
| Qué | Documentación | Medido, sin tráfico |
|---|---|---|
| RAM | 4 GB más 1 GB de swap | 909,2 MiB al arrancar; 980,3 tras 13 días |
| Disco | 5-10 GB para Postgres | 673,6 MB de descarga; 2,56 GB en disco |
Requisitos oficiales contra docker stats, 13-09-2026: reproducible en método, no auditable en cifras. Los 4 GB son para 10.000 conversaciones al día; los 909 MiB, un suelo con 0 conversaciones y 0 mensajes, no una estimación de operación. Más 621 MB de pgvector, 57,8 MB de redis y 77,4 MB de volúmenes con la base vacía.
Arranque en frío: 65,1 s contra 4,87 s
Chatwoot, cuatro contenedores, de docker compose up a HTTP 200 en /app/login: 65,1 segundos, con las imágenes locales y la base migrada. La consola propia, Next.js sobre PM2, del arranque a {"ok":true} en /api/health: 4,87 segundos. Mismo servidor y mismo día, 13-09-2026.
Es la indisponibilidad de cada despliegue. No son el mismo producto: el número mide disponibilidad, no funcionalidad. Y es una sola medición de cada lado, nuestra, sin artefacto público.
Superficie: lo que hay que mantener
| Qué | Chatwoot v4.17.0 | 4dim |
|---|---|---|
| Dependencias | 380 gems | 66 paquetes |
| Tablas | 98 en el esquema | 33 |
| Migraciones | 176 | 32 |
| Canales | 12 | 3 |
| Idiomas | 57 | 1 |
Recontado el 13-09-2026 sobre el repositorio del tag; el lado 4dim es nuestra máquina. Las 380 gems salen de 395 líneas de Gemfile.lock; de los 66 paquetes, 6 son directas. Unidades no equivalentes. Las tablas son create_table; en la base viva salen 102, con las de Rails.
El precio, con la fuente delante
| Plan | Por agente y mes | Añade |
|---|---|---|
| Community Edition | $0 | nada premium |
| Premium Support | $19, anual | Captain AI, voz, marca propia, roles |
| Enterprise Edition | $99, anual | lo anterior, más SSO/SAML y SLA |
| Hacker | $0 | 2 agentes, 500 conversaciones/mes |
| Startups | $19, anual | — |
| Business | $39, anual | — |
| Enterprise | $99, anual | — |
Los tres primeros, en tu servidor; los cuatro últimos, en la nube. Leído del HTML servido el 13-09-2026 en autohospedado y en nube. Créditos extra de Captain AI: $20 por 1.000. El coste del servidor no lo ponemos: no tenemos dato publicable.
Y una aclaración: el «20+ agents» de esas páginas no es un mínimo de asientos: califica dos extras de soporte.
Dónde gana Chatwoot
Doce canales contra nuestros tres. Cincuenta y siete idiomas contra uno. Apps móviles en las dos tiendas, actualizadas el 2 de septiembre de 2026 —la de iOS en la 4.9.3—. Y el proyecto: 373 contribuidores, 36.760 estrellas, quince versiones en 188 días —una cada 13,4— y el último push el 12 de septiembre de 2026 (API de GitHub, 13-09-2026).
La vía de compra está documentada: la Enterprise se activa desde el panel de Super Admin, atada al Installation Identifier y resincronizada con el servidor de licencias, que es el hub.
Nada de esto lo tiene una consola recién escrita. Chatwoot es mejor producto, y la frase no lleva pero.
Dónde gana la consola propia
En arranque y en superficie, medidos arriba. Y en una tercera cosa que no se mide en gigas: nadie de fuera decide qué se apaga esta noche en tu instalación.
No gana en funciones. Gana en que hace lo que nuestro producto necesita, y en que las piezas que hay que entender para arreglar un fallo caben en una cabeza.
Dos trampas de medición
Primera. Buscar sso con grep en el HTML de /app/login acierta siempre: crossorigin lleva las letras dentro. El shell del SPA son 9.429 bytes y no tiene botones; comprobarlo a ojo exige renderizar, no curl.
Segunda, y casi nos cuela un dato falso. /sla_policies, /audit_logs y /custom_roles devolvían 401 el 13-09-2026, y el código confirma que una función premium apagada da ese 401. Pero un token caducado también, y no hicimos la petición de control contra un endpoint no premium. El 401 es consistente con la tesis y no la prueba.
Lo que no sabemos
- Por qué la reversión tardó ≈25,7 h y no 1,7.
- Qué devuelve el hub en la respuesta: no capturamos el tráfico.
- Si concede
enterprisea quien paga: no tenemos suscripción, y describir no es medir. - Qué pasa si se bloquea el hub: aunque funcionara, el problema es legal, no técnico.
- El consumo con tráfico real: nada de aquí mide rendimiento bajo carga.
- Por qué la documentación de telemetría dice enviar la distribución de bandejas y las integraciones activas, que no aparecen en
instance_metrics. - Cuánta gente usa Chatwoot autohospedado: no existe ese censo, y las estrellas miden GitHub.
Y para toda la nota: nuestras mediciones salen de una instancia nuestra. Puedes reproducir el método; no los números.
Cómo comprobarlo en tu instalación
- Saca tu identificador de
InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER'). - Calcula tu minuto:
python3 -c "import hashlib;print(int(hashlib.md5(b'TU-ID').hexdigest(),16)%1440)". - Mira el
updated_atdeINSTALLATION_PRICING_PLAN: si coincide con tu minuto, ya lo sabes. - Y si pruebas los endpoints premium, pide también uno no premium con el mismo token: sin control, un 401 no prueba nada.
Esta nota sale de nuestro trabajo en AI-CRM Connection.