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.

BanderaInternaLa apaga el reconciliador
disable_branding
audit_logs
custom_tools
sla
help_center_embedding_search
captain_integration
custom_roles
captain_v1_action_classifier
channel_voice
captain_integration_v2
captain_document_auto_sync
advanced_search
saml
advanced_search_indexing
companies
csat_review_notes
conversation_required_attributes
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:

  1. config/schedule.yml dispara TriggerDailyScheduledItemsJob a las 00:00 UTC.
  2. Ese job no llama al hub de inmediato: calcula tu minuto del día con MD5(installation_identifier).hex % 1440.
  3. A esa hora, el override enterprise de CheckNewVersionsJob —solo en producción— sobrescribe cinco InstallationConfig con locked = true, entre ellos el plan.
  4. Si el plan que vuelve es community, ReconcilePlanConfigService devuelve diez configuraciones de marca a los valores de Chatwoot y recorre todas las cuentas con disable_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.

UTCQué ocurrióPrueba
26-08 01:03Plan y marca puestos a manoupdated_at
26-08 02:44La ranura de ese día no revirtió nadahueco sin explicar
26-08 20:04INSTALLATION_IDENTIFIER bloqueadoupdated_at
27-08 02:44:03Plan reescrito a communityal segundo del minuto 164
13-09 15:14Las nueve funciones, apagadasrails 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.

CampoQué esSe va
installation_identifierel UUID de tu instalaciónno
installation_versiontu versiónno
installation_hosttu FRONTEND_URLno
installation_envtu entornono
editionce o eeno
accounts_countcuentas
users_countusuarios
inboxes_countbandejas
conversations_countconversaciones
incoming_messages_countmensajes entrantes
outgoing_messages_countmensajes salientes
additional_informationhash vacío

«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ónMedido, sin tráfico
RAM4 GB más 1 GB de swap909,2 MiB al arrancar; 980,3 tras 13 días
Disco5-10 GB para Postgres673,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.04dim
Dependencias380 gems66 paquetes
Tablas98 en el esquema33
Migraciones17632
Canales123
Idiomas571

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

PlanPor agente y mesAñade
Community Edition$0nada premium
Premium Support$19, anualCaptain AI, voz, marca propia, roles
Enterprise Edition$99, anuallo anterior, más SSO/SAML y SLA
Hacker$02 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 enterprise a 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

  1. Saca tu identificador de InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER').
  2. Calcula tu minuto: python3 -c "import hashlib;print(int(hashlib.md5(b'TU-ID').hexdigest(),16)%1440)".
  3. Mira el updated_at de INSTALLATION_PRICING_PLAN: si coincide con tu minuto, ya lo sabes.
  4. 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.

← todas las notas