Files
hechakuaa_bot/DEPLOY.md
markosbenitez 6aa8b8fa62 docs: guía de deploy completa con Docker, Nginx, SSL, DNS y webhook
- Estructura en 4 partes: preparación, servidor, HTTPS/producción, operación
- Sección DNS detallada para Bluehost cPanel + mensaje exacto en inglés
  para delegar a un asistente técnico
- Configuración Nginx como reverse proxy con SSL terminado en el servidor
- Certbot + Let's Encrypt con renovación automática
- docker-compose en modo producción (bind 127.0.0.1:8080)
- Activación del modo webhook paso a paso
- Health check HTTP integrado en Nginx
- Tabla de rutas absolutas de todos los archivos
- Troubleshooting expandido para 8 escenarios de error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 23:55:36 -03:00

1255 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Guía de despliegue — Pedrito Hechakuaa en VPS Ubuntu
> **Destinatario:** equipo técnico de InQuality
> **Entorno objetivo:** VPS Ubuntu 22.04 en Hostinger — producción
> **Tiempo estimado:** 6090 minutos (primera vez)
> **Acceso requerido:** SSH root al VPS + datos de configuración provistos por Marcos
> **Subdominio objetivo:** `pedrito.inqualityhq.com`
---
## Índice
**PARTE A — Preparación (antes de tocar el servidor)**
1. [Datos que necesitás antes de empezar](#1-datos-que-necesitás-antes-de-empezar)
2. [Configurar DNS en Bluehost](#2-configurar-dns-en-bluehost)
**PARTE B — Servidor: base**
3. [Conectarse al VPS](#3-conectarse-al-vps)
4. [Actualizar el sistema y crear usuario](#4-actualizar-el-sistema-y-crear-usuario)
5. [Instalar Docker](#5-instalar-docker)
6. [Instalar Nginx](#6-instalar-nginx)
7. [Clonar el repositorio](#7-clonar-el-repositorio)
8. [Configurar el archivo .env](#8-configurar-el-archivo-env)
9. [Cifrar las credenciales del PJ](#9-cifrar-las-credenciales-del-pj)
**PARTE C — HTTPS y producción**
10. [Obtener certificado SSL con Certbot](#10-obtener-certificado-ssl-con-certbot)
11. [Configurar Nginx como reverse proxy](#11-configurar-nginx-como-reverse-proxy)
12. [Configurar el firewall](#12-configurar-el-firewall)
13. [Iniciar el bot con Docker](#13-iniciar-el-bot-con-docker)
14. [Activar el modo webhook](#14-activar-el-modo-webhook)
**PARTE D — Operación**
15. [Verificación final](#15-verificación-final)
16. [Monitoreo y logs](#16-monitoreo-y-logs)
17. [Health check HTTP](#17-health-check-http)
18. [Cómo actualizar el bot](#18-cómo-actualizar-el-bot)
19. [Troubleshooting](#19-troubleshooting)
20. [Referencia rápida de comandos](#20-referencia-rápida-de-comandos)
---
## 1. Datos que necesitás antes de empezar
Antes de conectarte al VPS, juntá estos datos. Sin alguno de ellos el deploy no puede completarse.
| # | Dato | Quién lo tiene | Dónde va |
|---|------|---------------|----------|
| 1 | `FERNET_KEY` (clave de cifrado) | Marcos la genera | `.env` |
| 2 | `TELEGRAM_BOT_TOKEN` | Marcos (de @BotFather) | `.env` |
| 3 | `TELEGRAM_CHAT_ID` | Marcos (de @userinfobot) | `.env` |
| 4 | `TELEGRAM_EXTRA_IDS` | Marcos (chat IDs de otros abogados, separados por coma) | `.env` |
| 5 | `INVITE_CODE` | Marcos lo decide | `.env` |
| 6 | Cédula del PJ | Marcos | Script de cifrado |
| 7 | Contraseña del PJ | Marcos | Script de cifrado |
| 8 | IP pública del VPS | Panel de Hostinger | DNS + SSH |
| 9 | Credenciales SSH del VPS | Panel de Hostinger | Terminal |
> **Tip:** Pedile a Marcos que te envíe los datos 1-5 por un canal seguro (Signal, Bitwarden Send, etc.).
> Nunca por email ni WhatsApp.
> ⚠️ **Seguridad crítica:** Nunca guardés la contraseña del PJ (dato 7) en ningún archivo, chat,
> nota, ni Post-it. El script del paso 9 la pide en pantalla, la descarta de memoria y guarda
> solo el resultado cifrado. Una vez que el script termina exitosamente, podés olvidarla.
---
## 2. Configurar DNS en Bluehost
El bot va a ser accesible en `pedrito.inqualityhq.com`. Para eso necesitamos apuntar ese
subdominio a la IP del VPS con un registro DNS tipo A.
**Necesitás:** La IP pública del VPS (Panel de Hostinger → tu VPS → ver IP).
---
### Opción A: Hacerlo vos mismo en Bluehost cPanel
1. Entrá a [bluehost.com](https://www.bluehost.com) con las credenciales de InQuality
2. En el panel, buscá **"cPanel"** y hacé click
3. Dentro de cPanel, en la sección **"Domains"**, hacé click en **"Zone Editor"**
(también puede aparecer como **"DNS Zone Editor"** o **"Advanced DNS Editor"**)
4. Buscá `inqualityhq.com` en la lista y hacé click en **"Manage"**
5. Hacé click en **"Add Record"**
6. Completá el formulario así:
| Campo | Valor |
|-------|-------|
| **Name** (o Host) | `pedrito` |
| **Type** | `A` |
| **TTL** | `300` (o el mínimo disponible) |
| **Value** (o Points To) | `<IP_DE_TU_VPS>` |
7. Hacé click en **"Save"** o **"Add Record"**
8. Para verificar que se propagó (puede tardar de 5 minutos a 2 horas):
```bash
# Desde tu computadora local
nslookup pedrito.inqualityhq.com
# Debe mostrar la IP del VPS en la respuesta
# O también:
dig pedrito.inqualityhq.com A +short
# Debe retornar solo la IP del VPS
```
> **Tip:** Si `nslookup` aún no muestra la IP, esperá 10-15 minutos y volvé a intentar.
> La propagación en Bluehost suele ser rápida, pero a veces tarda más.
---
### Opción B: Mensaje exacto para pedirle a un asistente de Bluehost (en inglés)
Si necesitás que un asistente del equipo (que tenga acceso al cPanel de Bluehost) lo haga por vos,
podés enviarle exactamente este mensaje:
---
> Hi,
>
> I need to add a DNS A record to the `inqualityhq.com` domain managed through Bluehost cPanel.
> Could you please add the following record?
>
> - **Domain:** inqualityhq.com
> - **Record type:** A
> - **Host/Name:** pedrito *(this will create pedrito.inqualityhq.com)*
> - **Value/Points to:** `<IP_DEL_VPS>` *(replace with the actual VPS IP)*
> - **TTL:** 300 seconds (minimum available)
>
> Once added, please confirm so I can verify propagation.
> If you need me to confirm with the account holder, let me know.
>
> Thank you!
---
Reemplazá `<IP_DEL_VPS>` con la IP real del VPS antes de enviar el mensaje.
---
## 3. Conectarse al VPS
Desde tu computadora local, abrí una terminal:
```bash
ssh root@<IP_DE_LA_VPS>
```
Si tenés clave SSH configurada en Hostinger (recomendado):
```bash
ssh -i ~/.ssh/id_rsa root@<IP_DE_LA_VPS>
```
Si te pide contraseña, es la del panel de Hostinger.
> **Tip para Windows:** Usá [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701) o
> [PuTTY](https://www.putty.org/). En Windows Terminal el comando SSH es el mismo.
> **Tip:** Si es la primera conexión, va a preguntar:
> ```
> Are you sure you want to continue connecting (yes/no/[fingerprint])?
> ```
> Escribí `yes` y Enter.
---
## 4. Actualizar el sistema y crear usuario
Todos estos comandos se ejecutan como **root**.
### 4.1 Actualizar paquetes
```bash
apt update && apt upgrade -y
```
Esto puede tardar 2-5 minutos. Esperá que termine.
### 4.2 Verificar la versión de Ubuntu
```bash
lsb_release -a
```
Salida esperada: `Ubuntu 22.04.x LTS`. Si es otra versión, avisale a Marcos antes de continuar.
### 4.3 Crear el usuario `pedrito`
El bot no debe correr como root. Le creamos un usuario propio:
```bash
# Crear el usuario (sin contraseña de login interactivo)
adduser pedrito --disabled-password --gecos ""
# Verificar que se creó
id pedrito
```
Salida esperada:
```
uid=1001(pedrito) gid=1001(pedrito) groups=1001(pedrito)
```
> **¿Por qué UID 1001 y no 1000?** En Ubuntu el usuario 1000 suele ser el primer usuario
> creado durante la instalación. En un VPS de Hostinger puede o no existir.
> Lo importante es que `pedrito` tenga su propio UID.
### 4.4 Permitir que `pedrito` use Docker (sin sudo)
```bash
# Esto lo hacemos después de instalar Docker (paso 5), pero lo dejamos acá como referencia
# usermod -aG docker pedrito ← lo corremos en el paso 5.3
```
### 4.5 Configurar acceso SSH para `pedrito` (opcional pero recomendado)
```bash
mkdir -p /home/pedrito/.ssh
cp /root/.ssh/authorized_keys /home/pedrito/.ssh/
chown -R pedrito:pedrito /home/pedrito/.ssh
chmod 700 /home/pedrito/.ssh
chmod 600 /home/pedrito/.ssh/authorized_keys
```
Para verificar: en una segunda terminal, intentá `ssh pedrito@<IP>`. Si entra sin contraseña, perfecto.
---
## 5. Instalar Docker
Docker es el motor de contenedores que va a correr el bot de forma aislada y reproducible.
### 5.1 Instalar dependencias previas
```bash
apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release
```
### 5.2 Agregar el repositorio oficial de Docker
```bash
# Crear directorio para las claves GPG
install -m 0755 -d /etc/apt/keyrings
# Descargar e instalar la clave GPG de Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Agregar el repositorio
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
# Actualizar lista de paquetes
apt update
```
### 5.3 Instalar Docker Engine y docker-compose
```bash
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Verificar que Docker se instaló correctamente
docker --version
# Salida esperada: Docker version 24.x.x o mayor
docker compose version
# Salida esperada: Docker Compose version v2.x.x
```
### 5.4 Agregar a `pedrito` al grupo docker
```bash
# Esto permite que pedrito corra docker sin sudo
usermod -aG docker pedrito
# Verificar
groups pedrito
# Debe incluir "docker" en la lista
```
### 5.5 Verificar que Docker funciona
```bash
docker run --rm hello-world
```
Si imprime `Hello from Docker!`, Docker está funcionando.
> **¿Qué es un contenedor?** Pensalo como una "caja" cerrada que incluye el código del bot
> y todas sus dependencias. Si algo falla dentro de la caja, no afecta al resto del servidor.
> `docker compose up` abre la caja, `docker compose down` la cierra.
---
## 6. Instalar Nginx
Nginx actúa como intermediario entre internet y el bot. Recibe las conexiones HTTPS (puerto 443)
y las reenvía internamente al bot (puerto 8080). Esto permite tener SSL sin que el bot lo maneje.
```bash
# Instalar Nginx
apt install -y nginx
# Verificar que arrancó
systemctl status nginx
```
Salida esperada:
```
● nginx.service - A high performance web server and reverse proxy server
Active: active (running) since ...
```
Si dice `inactive`, arrancalo manualmente:
```bash
systemctl start nginx
systemctl enable nginx # para que arranque en cada reboot
```
---
## 7. Clonar el repositorio
A partir de acá trabajamos como el usuario `pedrito`:
```bash
# Cambiar al usuario pedrito
su - pedrito
# Verificar que estamos en el home correcto
pwd
# Debe mostrar: /home/pedrito
# Clonar el repositorio
git clone https://github.com/InqGit/hechakuaa_bot.git
# Entrar al directorio
cd hechakuaa_bot
# Verificar que los archivos están presentes
ls -la
```
Deberías ver archivos como: `main.py`, `config.py`, `telegram_bot.py`, `Dockerfile`,
`docker-compose.yml`, `migrations/`, etc.
### Crear el directorio de datos
```bash
# Este directorio persiste entre reinicios del bot (DB, snapshots)
mkdir -p /home/pedrito/hechakuaa_bot/data
```
Salir de vuelta a root para el siguiente paso:
```bash
exit
# Ahora estás como root de nuevo
```
### Ajustar permisos del directorio data para Docker
El contenedor Docker corre con usuario UID 1000. El directorio `data/` necesita
ser escribible por ese UID:
```bash
chown -R 1000:1000 /home/pedrito/hechakuaa_bot/data
# Verificar
ls -la /home/pedrito/hechakuaa_bot/data
# Debe mostrar: drwxr-xr-x 2 1000 1000 ... data
```
> **¿Por qué 1000 y no pedrito?** Dentro del contenedor Docker existe un usuario con UID 1000.
> El servidor no sabe que ese usuario se llama "pedrito" — solo entiende el número UID.
> En el sistema anfitrión, UID 1000 puede corresponder a un usuario diferente, pero Docker
> solo necesita que el directorio sea escribible por ese número.
---
## 8. Configurar el archivo .env
El archivo `.env` contiene toda la configuración sensible del bot. Se crea directamente en el VPS
y **nunca** se sube al repositorio ni se copia entre máquinas.
```bash
# Como pedrito:
su - pedrito
cd /home/pedrito/hechakuaa_bot
# Crear .env a partir del template
cp .env.example .env
# Editar (nano es el editor más simple)
nano .env
```
### Guía de campos del .env
Completá los campos marcados con `← COMPLETAR`. Los demás se pueden dejar con sus valores por defecto:
```bash
# ── CIFRADO ──────────────────────────────────────────────────────────────────
FERNET_KEY= # ← COMPLETAR (Marcos te la da o la generás abajo)
# ── CREDENCIALES PJ (se completan en el paso 9) ───────────────────────────────
PJ_USUARIO_ENC= # ← se completa automáticamente con el script de cifrado
PJ_PASSWORD_ENC= # ← se completa automáticamente con el script de cifrado
# ── TELEGRAM ─────────────────────────────────────────────────────────────────
TELEGRAM_BOT_TOKEN= # ← COMPLETAR (de @BotFather)
TELEGRAM_CHAT_ID= # ← COMPLETAR (chat ID de Marcos)
TELEGRAM_EXTRA_IDS= # ← COMPLETAR si hay más abogados (ej: 111222333,444555666)
# ── INVITACIÓN ────────────────────────────────────────────────────────────────
INVITE_CODE= # ← COMPLETAR (código maestro ilimitado)
# ── WEBHOOK (completar después del paso 14) ───────────────────────────────────
WEBHOOK_URL= # ← dejar vacío por ahora, completar en el paso 14
WEBHOOK_SECRET= # ← dejar vacío por ahora, completar en el paso 14
# ── HEALTH CHECK ─────────────────────────────────────────────────────────────
HEALTH_PORT=8080 # dejar así
HEALTH_ALERT_HOURS=2 # alerta al admin si el poller no actualizó en 2 horas
# ── POLLING ──────────────────────────────────────────────────────────────────
POLL_INTERVAL_MINUTES=60 # revisar el PJ cada 60 minutos
POLL_HORA_INICIO=7 # desde las 7am (hora Paraguay)
POLL_HORA_FIN=18 # hasta las 6pm (hora Paraguay)
```
Guardar y salir de nano: `Ctrl+O` → `Enter` → `Ctrl+X`.
### Generar FERNET_KEY si Marcos no te la dio
```bash
# Generar una clave nueva (solo si no tenés una preexistente)
uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```
Salida esperada: una cadena de ~44 caracteres como `gAAAAABx3...`. Copiarla y pegarla en `.env`.
> ⚠️ **Guardá la FERNET_KEY en un gestor de contraseñas (Bitwarden, 1Password, etc.).**
> Si se pierde, las credenciales cifradas de todos los abogados quedan irrecuperables.
### Instalar uv (si no está disponible como pedrito)
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version
```
### Restringir permisos del .env
```bash
chmod 600 /home/pedrito/hechakuaa_bot/.env
# Verificar
ls -la /home/pedrito/hechakuaa_bot/.env
# Debe mostrar: -rw------- 1 pedrito pedrito
```
---
## 9. Cifrar las credenciales del PJ
El bot nunca guarda la cédula ni la contraseña del PJ en texto plano. Este script las cifra
con la `FERNET_KEY` y guarda el resultado cifrado en `.env`.
```bash
# Como pedrito, dentro del directorio del proyecto:
cd /home/pedrito/hechakuaa_bot
uv run python scripts/cifrar_credenciales.py
```
El script te va a pedir:
1. **Cédula de identidad** — el número con el que Marcos entra a `apps.csj.gov.py`
2. **Contraseña** — la contraseña de ese sistema (no se muestra mientras escribís)
Salida esperada:
```
✅ Credenciales cifradas y guardadas en .env
PJ_USUARIO_ENC y PJ_PASSWORD_ENC actualizadas.
```
Verificar que se guardaron:
```bash
grep "PJ_USUARIO_ENC" /home/pedrito/hechakuaa_bot/.env
# Debe mostrar un valor cifrado largo, NUNCA la cédula en texto plano
```
---
## 10. Obtener certificado SSL con Certbot
HTTPS es obligatorio para el modo webhook de Telegram y para cualquier servicio expuesto
en producción. Certbot obtiene el certificado gratis de Let's Encrypt.
> **Prerequisito:** El DNS del paso 2 ya debe estar propagado. Verificalo:
> ```bash
> nslookup pedrito.inqualityhq.com
> # Debe retornar la IP de este VPS
> ```
> Si no retorna la IP correcta, esperá y volvé después.
### 10.1 Instalar Certbot
```bash
# Como root:
exit # si estabas como pedrito, volver a root
apt install -y certbot python3-certbot-nginx
```
### 10.2 Obtener el certificado
```bash
certbot --nginx -d pedrito.inqualityhq.com
```
Certbot te va a preguntar:
1. Tu dirección de email (para avisos de vencimiento) — ponés la de Marcos o la del equipo
2. Aceptar los términos de servicio → `Y`
3. Si querés compartir el email con EFF → `N` (opcional)
Salida esperada al terminar:
```
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem
...
```
> **¿Por qué usamos --nginx?** Le decimos a Certbot que tenemos Nginx instalado y que puede
> configurar automáticamente el sitio para HTTPS. Pero vamos a sobreescribir esa configuración
> en el siguiente paso para personalizarla.
### 10.3 Verificar la renovación automática
Los certificados de Let's Encrypt duran 90 días y se renuevan automáticamente:
```bash
# Verificar que el timer de renovación automática existe
systemctl status certbot.timer
# Debe estar "active (waiting)"
# Simular una renovación para verificar que funciona
certbot renew --dry-run
# Debe decir: "Simulating renewal of an existing certificate..."
```
---
## 11. Configurar Nginx como reverse proxy
Nginx recibe las requests de internet y las redirige al bot corriendo en el contenedor Docker
(puerto 8080). También maneja el SSL automáticamente.
### 11.1 Crear el archivo de configuración
```bash
# Como root:
nano /etc/nginx/sites-available/pedrito
```
Pegar **exactamente** este contenido (reemplazá el nombre de dominio si usás uno diferente):
```nginx
# /etc/nginx/sites-available/pedrito
# Reverse proxy para Pedrito Hechakuaa
server {
listen 80;
server_name pedrito.inqualityhq.com;
# Redirigir todo el tráfico HTTP a HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name pedrito.inqualityhq.com;
# Certificados SSL de Let's Encrypt
ssl_certificate /etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Seguridad: no exponer versión de Nginx
server_tokens off;
# Tamaño máximo de payload (Telegram puede enviar archivos)
client_max_body_size 20M;
# ── Health check (acceso público para monitoreo) ──────────────────────────
location /health {
proxy_pass http://127.0.0.1:8080/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 10s;
}
location /health/history {
proxy_pass http://127.0.0.1:8080/health/history;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 10s;
}
# ── Webhook de Telegram ───────────────────────────────────────────────────
location /webhook {
proxy_pass http://127.0.0.1:8080/webhook;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Pasar el header de autenticación del webhook de Telegram
proxy_set_header X-Telegram-Bot-Api-Secret-Token $http_x_telegram_bot_api_secret_token;
proxy_read_timeout 30s;
}
# ── Denegar cualquier otra ruta ───────────────────────────────────────────
location / {
return 404;
}
}
```
Guardar: `Ctrl+O` → `Enter` → `Ctrl+X`.
### 11.2 Activar el sitio
```bash
# Crear el enlace simbólico que activa el sitio
ln -s /etc/nginx/sites-available/pedrito /etc/nginx/sites-enabled/pedrito
# Verificar que no hay errores de sintaxis en la configuración
nginx -t
```
Salida esperada:
```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
Si hay errores, revisá que el archivo se pegó correctamente (especialmente las comillas y los `;`).
### 11.3 Recargar Nginx
```bash
systemctl reload nginx
systemctl status nginx
# Debe estar active (running)
```
### 11.4 Verificar que Nginx responde
```bash
# Desde el propio servidor:
curl -sk https://pedrito.inqualityhq.com/health
# Debe retornar un error de conexión rechazada (porque el bot no está corriendo aún)
# o un JSON si ya está corriendo
# Verificar que HTTP redirige a HTTPS:
curl -I http://pedrito.inqualityhq.com/health
# Debe retornar: HTTP/1.1 301 Moved Permanently
```
---
## 12. Configurar el firewall
```bash
# Como root
ufw allow OpenSSH
ufw allow 'Nginx Full' # abre puertos 80 (HTTP) y 443 (HTTPS)
ufw --force enable
# Verificar estado
ufw status
```
Salida esperada:
```
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
```
> **Importante:** El puerto 8080 del bot **no** se abre al exterior. Solo Nginx (que corre en el
> mismo servidor) se comunica con el bot internamente. El bot no es accesible directamente desde
> internet, solo a través de Nginx.
---
## 13. Iniciar el bot con Docker
### 13.1 Actualizar docker-compose.yml para producción
Por seguridad, el bot solo debe escuchar en localhost (no en todas las interfaces de red).
Modificá el puerto en docker-compose.yml:
```bash
# Como pedrito:
su - pedrito
cd /home/pedrito/hechakuaa_bot
# Editar docker-compose.yml
nano docker-compose.yml
```
Buscá la línea:
```yaml
- "8080:8080"
```
Cambiala por:
```yaml
- "127.0.0.1:8080:8080"
```
Guardar y salir. Esto hace que el bot solo sea accesible desde el mismo servidor (por Nginx),
no desde internet.
### 13.2 Construir la imagen Docker
```bash
# Construir la imagen (tarda 1-3 minutos la primera vez)
docker compose build
# Salida esperada al final:
# => exporting to image
# => => naming to docker.io/library/hechakuaa_bot-pedrito
```
### 13.3 Primera prueba: correr en primer plano
Antes de dejarlo en background, verificamos que arranca bien:
```bash
docker compose up
```
Deberías ver logs como:
```
pedrito-1 | {"event": "migration_aplicada", "version": "001_initial_schema", ...}
pedrito-1 | {"event": "database_iniciada", ...}
pedrito-1 | {"event": "health_server_iniciado", "port": 8080, ...}
pedrito-1 | {"event": "bot_telegram_iniciado", ...}
pedrito-1 | {"event": "pedrito_hechakuaa_corriendo", ...}
pedrito-1 | {"event": "poller_iniciado", ...}
pedrito-1 | {"event": "login_exitoso", ...}
```
Si aparece `login_exitoso`, el bot se conectó al PJ. Detenerlo con `Ctrl+C`.
> **Si no aparece `login_exitoso`:** ver la sección [Troubleshooting](#19-troubleshooting).
### 13.4 Correr en background (modo producción)
```bash
# Arrancar en modo detached (background)
docker compose up -d
# Verificar que el contenedor está corriendo
docker compose ps
```
Salida esperada:
```
NAME IMAGE COMMAND SERVICE STATUS PORTS
hechakuaa_bot-... hechakuaa_bot-pedrito "uv run python main.…" pedrito running 127.0.0.1:8080->8080/tcp
```
El `STATUS` debe decir `running`.
### 13.5 Configurar Docker para que arranque en cada reboot
```bash
# Como root:
exit # salir de pedrito
# El servicio de Docker ya inicia automáticamente
# Solo verificar que el contenedor tiene restart policy correcta
systemctl enable docker
```
El `docker-compose.yml` ya tiene `restart: unless-stopped`, lo que hace que el contenedor
se reinicie automáticamente si el VPS se reinicia.
---
## 14. Activar el modo webhook
En modo webhook, Telegram envía los mensajes al bot directamente (HTTPS push), en lugar de
que el bot los pida constantemente (polling). Es más eficiente y tiene menor latencia.
### 14.1 Generar el WEBHOOK_SECRET
Este token es como una contraseña que Telegram incluye en cada request para probar que es legítimo:
```bash
# Generar un token aleatorio seguro:
python3 -c "import secrets; print(secrets.token_hex(32))"
# Salida: una cadena de 64 caracteres hexadecimales
```
Copiar la salida. La vamos a usar en el siguiente paso.
### 14.2 Actualizar .env con los datos del webhook
```bash
su - pedrito
nano /home/pedrito/hechakuaa_bot/.env
```
Completar estas dos líneas:
```bash
WEBHOOK_URL=https://pedrito.inqualityhq.com
WEBHOOK_SECRET=<el token que generaste arriba>
```
Guardar y salir.
### 14.3 Reiniciar el bot para aplicar el cambio
```bash
cd /home/pedrito/hechakuaa_bot
docker compose restart
# Ver los logs para confirmar que el webhook se registró:
docker compose logs --tail=20
```
Buscar en los logs:
```
{"event": "webhook_registrado", "url": "https://pedrito.inqualityhq.com/webhook", ...}
```
### 14.4 Verificar que el webhook quedó registrado en Telegram
```bash
# Reemplazá TU_BOT_TOKEN con el valor real del .env
curl -s "https://api.telegram.org/bot<TU_BOT_TOKEN>/getWebhookInfo" | python3 -m json.tool
```
Salida esperada:
```json
{
"ok": true,
"result": {
"url": "https://pedrito.inqualityhq.com/webhook",
"has_custom_certificate": false,
"pending_update_count": 0,
...
}
}
```
El campo `url` debe mostrar la URL correcta. Si está vacío o muestra otra URL, revisá los logs del bot.
---
## 15. Verificación final
Correr esta lista de verificaciones antes de dar el deploy por terminado:
```bash
# 1. El contenedor Docker está corriendo
docker compose ps
# STATUS debe ser "running"
# 2. Los logs no tienen errores
docker compose logs --tail=30
# Buscar "login_exitoso" y "pedrito_hechakuaa_corriendo"
# NO debe aparecer: ERROR, exception, Traceback
# 3. El health check responde
curl -s https://pedrito.inqualityhq.com/health | python3 -m json.tool
# Debe retornar JSON con "status": "ok"
# 4. El webhook está registrado (si activaste el modo webhook)
curl -s "https://api.telegram.org/bot<TU_BOT_TOKEN>/getWebhookInfo" | python3 -m json.tool
# url debe ser: https://pedrito.inqualityhq.com/webhook
# 5. El .env tiene los permisos correctos
ls -la /home/pedrito/hechakuaa_bot/.env
# Debe mostrar: -rw------- 1 pedrito pedrito
# 6. El directorio data existe y tiene contenido
ls -la /home/pedrito/hechakuaa_bot/data/
# Debe aparecer pasante.db y snapshot_*.json
# 7. Nginx redirige HTTP a HTTPS
curl -I http://pedrito.inqualityhq.com/health
# Debe retornar: HTTP/1.1 301 Moved Permanently
```
Si todos los checks pasan, avisarle a Marcos para que pruebe mandándole `/start` al bot en Telegram.
---
## 16. Monitoreo y logs
### Logs del bot en tiempo real
```bash
# Como pedrito:
su - pedrito
cd /home/pedrito/hechakuaa_bot
# Ver logs en tiempo real (Ctrl+C para salir)
docker compose logs -f
# Ver solo los últimos 50 registros
docker compose logs --tail=50
# Ver logs con timestamps más legibles
docker compose logs --tail=50 --timestamps
```
### Logs de Nginx
```bash
# Accesos (como root):
tail -f /var/log/nginx/access.log
# Errores:
tail -f /var/log/nginx/error.log
# Solo errores del bot (filtra por el path):
grep "/webhook\|/health" /var/log/nginx/access.log | tail -20
```
### Eventos útiles que buscar en los logs del bot
| Evento | Qué significa |
|--------|---------------|
| `login_exitoso` | Se conectó al PJ correctamente |
| `migration_aplicada` | Se aplicó una migración de DB (solo al arrancar) |
| `health_server_iniciado` | El server HTTP de health está escuchando |
| `webhook_registrado` | El webhook se registró con Telegram correctamente |
| `polling_tenant_ok` | Revisión de notificaciones completada sin errores |
| `polling_tenant_error` | Error al revisar notificaciones de un abogado |
| `pj_error_conexion` | El PJ no respondió (puede ser transitorio) |
| `bot_notificacion_enviada` | Se envió una notificación a Telegram |
| `onboarding_completado` | Un abogado completó el registro |
| `health_alerta_enviada` | El bot detectó que el poller está detenido y avisó al admin |
### Ver historial de eventos vía HTTP
```bash
# Últimos 50 eventos del audit log:
curl -s https://pedrito.inqualityhq.com/health/history | python3 -m json.tool
# Últimos 100 eventos:
curl -s "https://pedrito.inqualityhq.com/health/history?limit=100" | python3 -m json.tool
```
---
## 17. Health check HTTP
El bot expone un endpoint HTTP en `https://pedrito.inqualityhq.com/health` que retorna el estado actual.
### Ejemplo de respuesta normal (status 200)
```json
{
"status": "ok",
"timestamp": "2026-05-25T10:00:00+00:00",
"tenants_activos": 3,
"last_poll": "2026-05-25T09:58:42+00:00",
"last_poll_tenants": 3,
"seconds_since_last_poll": 78,
"poll_interval_minutes": 60,
"webhook_mode": true
}
```
### Ejemplo de respuesta degradada (status 503)
Si el poller no actualizó en más de `HEALTH_ALERT_HOURS` horas, retorna:
```json
{
"status": "degraded",
...
}
```
En ese caso, el bot también manda un mensaje de alerta automáticamente al Telegram de Marcos.
### Integrar con un monitor externo (opcional)
Si tienen un servicio de uptime monitoring (UptimeRobot, BetterUptime, etc.):
- URL a monitorear: `https://pedrito.inqualityhq.com/health`
- Método: `GET`
- Código esperado: `200`
- Intervalo: `5 minutos`
---
## 18. Cómo actualizar el bot
Cuando hay cambios en el código (nuevo commit en el repositorio):
```bash
# Como pedrito:
su - pedrito
cd /home/pedrito/hechakuaa_bot
# 1. Traer los cambios del repositorio
git pull
# 2. Reconstruir la imagen con el nuevo código
docker compose build
# 3. Reiniciar el contenedor con la nueva imagen
docker compose up -d
# 4. Verificar que levantó bien
docker compose ps
docker compose logs --tail=20
```
> **Importante:** El archivo `.env` y el directorio `data/` **no se tocan con `git pull`** —
> están en `.gitignore`. Las credenciales y el historial de notificaciones persisten
> entre actualizaciones.
> **Si hubo cambios en `.env.example`:** revisar manualmente si hay nuevos campos que agregar al `.env` existente.
> ```bash
> # Ver diferencias entre el template nuevo y el .env actual:
> diff .env.example .env
> ```
---
## 19. Troubleshooting
### El contenedor no arranca
```bash
# Ver el error específico:
docker compose logs --tail=50
# O ver el estado del contenedor:
docker compose ps
```
**`FERNET_KEY inválida`**
La clave en `.env` es incorrecta o tiene espacios/comillas. Verificar que el valor
en `.env` no tiene comillas ni espacios al inicio/final.
**`telegram.error.InvalidToken`**
El `TELEGRAM_BOT_TOKEN` en `.env` tiene un error. Verificar copiando el token
directamente desde la conversación con @BotFather en Telegram.
**`ModuleNotFoundError: No module named 'X'`**
La imagen necesita ser reconstruida:
```bash
docker compose build --no-cache
docker compose up -d
```
**`Error response from daemon: Cannot connect to the Docker daemon`**
Docker no está corriendo:
```bash
systemctl start docker
docker compose up -d
```
---
### El bot arranca pero no responde en Telegram
1. Verificar que el contenedor está corriendo: `docker compose ps`
2. Verificar que el webhook está registrado: `curl -s "https://api.telegram.org/bot<TOKEN>/getWebhookInfo"`
3. Verificar que Nginx está ok: `systemctl status nginx`
4. Verificar que el health check responde: `curl -s https://pedrito.inqualityhq.com/health`
5. Revisar logs de Nginx: `tail -20 /var/log/nginx/error.log`
---
### Error de credenciales del PJ
```bash
su - pedrito
cd /home/pedrito/hechakuaa_bot
# Repetir el cifrado de credenciales
uv run python scripts/cifrar_credenciales.py
# Reiniciar el bot
docker compose restart
docker compose logs --tail=10
```
---
### El certificado SSL venció o hay error de SSL
```bash
# Renovar manualmente (normalmente es automático):
certbot renew
# Recargar Nginx para que use el nuevo certificado:
systemctl reload nginx
```
---
### El webhook no recibe mensajes (pero el bot arranca)
```bash
# 1. Verificar que Nginx está reenviando correctamente:
tail -20 /var/log/nginx/access.log
# Debe aparecer peticiones POST a /webhook cuando alguien manda un mensaje
# 2. Verificar el health check del bot directamente (sin pasar por Nginx):
# Desde el propio servidor:
curl -s http://127.0.0.1:8080/health
# 3. Volver a registrar el webhook manualmente:
curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://pedrito.inqualityhq.com/webhook&secret_token=<WEBHOOK_SECRET>"
```
---
### El VPS se reinició y el bot no volvió
El contenedor tiene `restart: unless-stopped`, así que debería volver solo. Si no:
```bash
su - pedrito
cd /home/pedrito/hechakuaa_bot
docker compose up -d
```
Si Docker en sí no arrancó:
```bash
systemctl start docker
su - pedrito
cd /home/pedrito/hechakuaa_bot
docker compose up -d
```
---
### Ver dos instancias corriendo (causa conflictos de Telegram)
```bash
docker compose ps
# Si hay más de un contenedor corriendo:
docker compose down
docker compose up -d
```
---
### Error de permisos en el directorio data/
```bash
# Como root:
chown -R 1000:1000 /home/pedrito/hechakuaa_bot/data
# Reiniciar:
su - pedrito
cd /home/pedrito/hechakuaa_bot
docker compose restart
```
---
## 20. Referencia rápida de comandos
### Gestión del bot (como usuario `pedrito`)
```bash
cd /home/pedrito/hechakuaa_bot
# Estado
docker compose ps
# Iniciar / detener / reiniciar
docker compose up -d
docker compose down
docker compose restart
# Logs en tiempo real
docker compose logs -f
# Logs últimos 50 registros
docker compose logs --tail=50
# Actualizar (git pull + rebuild + restart)
git pull && docker compose build && docker compose up -d
```
### Nginx (como `root`)
```bash
# Verificar configuración
nginx -t
# Recargar sin cortar conexiones
systemctl reload nginx
# Reiniciar completamente
systemctl restart nginx
# Ver logs de error
tail -f /var/log/nginx/error.log
```
### SSL
```bash
# Renovar certificado manualmente
certbot renew
# Ver fecha de vencimiento del certificado actual
certbot certificates
```
### Rutas de archivos importantes
| Archivo | Ruta absoluta |
|---------|---------------|
| Código del bot | `/home/pedrito/hechakuaa_bot/` |
| Configuración | `/home/pedrito/hechakuaa_bot/.env` |
| Base de datos SQLite | `/home/pedrito/hechakuaa_bot/data/pasante.db` |
| Snapshots de notificaciones | `/home/pedrito/hechakuaa_bot/data/snapshot_*.json` |
| Docker Compose | `/home/pedrito/hechakuaa_bot/docker-compose.yml` |
| Config Nginx del bot | `/etc/nginx/sites-available/pedrito` |
| Logs de Nginx | `/var/log/nginx/` |
| Certificado SSL (cert) | `/etc/letsencrypt/live/pedrito.inqualityhq.com/fullchain.pem` |
| Certificado SSL (clave) | `/etc/letsencrypt/live/pedrito.inqualityhq.com/privkey.pem` |
---
*Repositorio: https://github.com/InqGit/hechakuaa_bot*
*Ante dudas técnicas no cubiertas acá, contactar a Marcos Benítez.*