Blog

  • 🧩 Guía de integración ERPNext + n8n en el mismo VPS (usando Docker)

    📌 Requisitos previos

    • ERPNext funcionando en https://erp.tudominio.com (según la guía anterior).
    • n8n funcionando en https://n8n.tudominio.com o similar.
    • Ambos contenedores en la misma red Docker (en tu caso, mqtt_network; si usaste la red erp_network de la guía base, deberás crear una red común o conectarlos).

    🔗 1. Asegurar que los contenedores están en la misma red Docker

    Para que n8n pueda comunicarse con ERPNext usando el nombre del contenedor (erpnext-web), deben compartir red.

    Opción A – Si tu n8n ya está en una red existente (por ejemplo mqtt_network):
    Conecta el contenedor de ERPNext a esa red:

    bash

    docker network connect mqtt_network erpnext-web

    (Los demás servicios como dbredis, etc., no necesitan ser accesibles desde n8n).

    Opción B – Crear una red común dedicada (si no usas mqtt_network):

    bash

    docker network create integration_net
    docker network connect integration_net erpnext-web
    docker network connect integration_net n8n

    Verifica la conectividad desde n8n:

    bash

    docker exec -it n8n wget -qO- http://erpnext-web:8000/api/method/ping

    Deberías recibir "message":"pong". Si falla, revisa que el nombre del contenedor de ERPNext sea exactamente erpnext-web y que el puerto 8000 esté escuchando dentro del contenedor (está mapeado a 127.0.0.1:8000 en el host, pero internamente es 0.0.0.0:8000).

    🔑 2. Obtener credenciales de API en ERPNext

    1. Accede a https://erp.tudominio.com con el usuario Administrator.
    2. Busca en la barra superior “API” y selecciona API Key.
    3. Crea un nuevo API Key (puedes llamarlo “n8n_integration”).
    4. Copia el API Key y el API Secret; solo se muestran una vez.
    5. (Opcional) En la configuración del DocType “User”, asigna a este par de claves los roles necesarios (por ejemplo, “System Manager” para pruebas controladas; en producción limita los permisos).

    ⚙️ 3. Configurar el nodo en n8n

    🌐 Opción recomendada: Nodo HTTP Request

    Es el método más universal y robusto. Usa autenticación “Generic Credential Type” o directamente “Header Auth”, pero la autenticación por token de Frappe se pasa como cabecera personalizada. Sigue estos pasos:

    1. Añade un nodo HTTP Request.
    2. Método: según la operación (GET, POST, PUT, DELETE).
    3. URL: la endpoint de ERPNext. Las URLs base son:
      http://erpnext-web:8000/api/resource/{DocType} (para CRUD de documentos)
      http://erpnext-web:8000/api/method/{function} (para métodos remotos)
      Ejemplo: http://erpnext-web:8000/api/resource/Customer
    4. En Headers añade:
      • Authorization con valor token <API_Key>:<API_Secret>
        (Ejemplo: token 4f9e8e5c2b1a4d3:1a2b3c4d5e6f7a8b)
      • Content-Type: application/json (cuando envíes datos)
    5. Para probar, usa un GET a http://erpnext-web:8000/api/method/frappe.auth.get_logged_user; deberías recibir el nombre de usuario.

    🧩 Alternativa: Nodo ERPNext (comunitario)

    Si tu instancia de n8n tiene disponible el nodo “ERPNext” (instalado desde la comunidad), configúralo así:

    • Hosthttp://erpnext-web:8000
    • API Key: el key del paso anterior
    • API Secret: el secret del paso anterior
    • SSL: desactivado (conexión http interna)

    Luego selecciona la operación deseada (Get, Create, Update, Delete) y el DocType.

    📦 4. Ejemplo práctico: crear un Cliente en ERPNext desde n8n

    Supongamos que tienes un formulario web y quieres crear un cliente en ERPNext.

    1. Nodo Webhook (o el trigger que uses) recibe los datos.
    2. Nodo Set (o Function) para mapear los campos al formato que espera ERPNext.
    3. Nodo HTTP Request con:
      • Método: POST
      • URL: http://erpnext-web:8000/api/resource/Customer
      • Headers:
        • Authorization: token <key>:<secret>
        • Content-Type: application/json
      • Body (JSON):json{ “customer_name”: “Nombre del cliente”, “customer_type”: “Company”, “customer_group”: “All Customer Groups”, “territory”: “All Territories” }
    4. Ejecuta el flujo y comprueba en ERPNext que el cliente se ha creado.

    🔄 5. Sincronización inversa: exportar datos de ERPNext a hojas de cálculo o bases de datos

    Puedes usar n8n para leer datos de ERPNext y guardarlos en Google Sheets, Excel o PostgreSQL. Ejemplo:

    1. Nodo Schedule Trigger o Webhook.
    2. Nodo HTTP Request (GET) a http://erpnext-web:8000/api/resource/Sales Order?filters=[["status","=","Draft"]]&fields=["name","customer","grand_total"].
    3. Nodo Item Lists (o Function) para iterar los resultados.
    4. Nodo Google Sheets o PostgreSQL para escribir las filas.

    La API de ERPNext permite filtros avanzados, limitación, paginación, etc. Documentación completa: Frappe API.

    🧪 6. Solución de problemas comunes

    • Error de conexión: comprueba que ambos contenedores estén en la misma red Docker (paso 1) y que el nombre del contenedor sea correcto.
    • 401 Unauthorized: revisa que la cabecera Authorization tenga el formato exacto: token <API_Key>:<API_Secret> (sin espacios extra, los dos puntos juntos).
    • 403 Forbidden: el usuario asociado a la API Key no tiene permisos suficientes sobre el DocType. Otorga los roles adecuados en ERPNext (por ejemplo, “System Manager” o roles específicos).
    • 404 Not Found: verifica la ruta de la API. Usa la barra de herramientas de Frappe (dentro de ERPNext) para explorar los DocTypes y métodos disponibles.
    • Errores de campos: los nombres de campo son los mismos que ves en la interfaz de ERPNext, pero sin espacios (ejemplo: customer_name en lugar de “Customer Name”). Puedes usar el endpoint /api/method/frappe.client.get_meta para obtener los campos de un DocType.

    ✅ Nota final

    Esta configuración te permite automatizar completamente los procesos entre tu app .NET, n8n y ERPNext. Recuerda que puedes crear flujos avanzados combinando webhooks, MQTT (ya tienes Mosquitto), IA (Ollama) y la API de ERPNext. La clave es la conectividad Docker y la API de Frappe bien documentada.

    Si en el futuro necesitas exponer la API de ERPNext al exterior (para tu app .NET, por ejemplo), basta con añadir en Nginx un bloque location /api que haga proxy hacia el contenedor, similar a como haces con los estáticos, y usar el mismo dominio o un subdominio. Pero por ahora, la comunicación interna es suficiente para la integración con n8n.

  • 🧩 Guía de instalación de ERPNext en VPS (Ubuntu + Docker + Nginx)

    📌 Requisitos previos

    • VPS con Ubuntu 24.04 (o similar)
    • Al menos 4 GB de RAM (recomendado 8 GB)
    • Docker y Docker Compose instalados
    • Nginx y Certbot instalados en el host
    • Un dominio o subdominio apuntando a la IP del VPS (en esta guía usamos erp.tudominio.com)

    1. Preparar el entorno

    bash

    mkdir -p ~/erpnext && cd ~/erpnext

    2. Archivo de variables de entorno (.env)

    Crea el archivo sin espacios alrededor del =:

    bash

    cat > .env << 'EOF'
    MYSQL_ROOT_PASSWORD=ContraseñaSegura123
    ADMIN_PASSWORD=OtraContraseña456
    SITE=erp.tudominio.com
    MAIL_FROM=erp@tudominio.com
    EOF

    3. docker-compose.yml con configuración corregida de Redis

    Copia este contenido exacto (ya incluye las variables correctas REDIS_CACHEREDIS_QUEUEREDIS_SOCKETIO):

    yaml

    services:
      db:
        image: mariadb:10.6
        container_name: erpnext-db
        restart: unless-stopped
        environment:
          MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
        volumes:
          - db-data:/var/lib/mysql
        networks:
          - erp_network
    
      redis-cache:
        image: redis:7-alpine
        container_name: erpnext-redis-cache
        restart: unless-stopped
        command: redis-server --appendonly yes
        volumes:
          - redis-cache-data:/data
        networks:
          - erp_network
    
      redis-queue:
        image: redis:7-alpine
        container_name: erpnext-redis-queue
        restart: unless-stopped
        command: redis-server --appendonly yes
        volumes:
          - redis-queue-data:/data
        networks:
          - erp_network
    
      erpnext:
        image: frappe/erpnext:v15
        container_name: erpnext-web
        restart: unless-stopped
        depends_on:
          - db
          - redis-cache
          - redis-queue
        environment:
          - FRAPPE_DB_HOST=db
          - FRAPPE_DB_PORT=3306
          - FRAPPE_DB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          - REDIS_CACHE=redis://redis-cache:6379
          - REDIS_QUEUE=redis://redis-queue:6379
          - REDIS_SOCKETIO=redis://redis-cache:6379
          - FRAPPE_SITE_NAME_HEADER=${SITE}
          - FRAPPE_ADMIN_PASSWORD=${ADMIN_PASSWORD}
          - FRAPPE_SMTP_SERVER=172.17.0.1   # Gateway del host hacia Docker
          - FRAPPE_SMTP_PORT=25
          - FRAPPE_SMTP_LOGIN=
          - FRAPPE_SMTP_PASSWORD=
          - FRAPPE_MAIL_FROM=${MAIL_FROM}
        volumes:
          - sites-data:/home/frappe/frappe-bench/sites
        networks:
          - erp_network
        ports:
          - "127.0.0.1:8000:8000"
    
      # Workers y scheduler (necesarios)
      erpnext-queue-short:
        image: frappe/erpnext:v15
        container_name: erpnext-queue-short
        restart: unless-stopped
        command: bench worker --queue short
        depends_on:
          - erpnext
        environment:
          - FRAPPE_DB_HOST=db
          - FRAPPE_DB_PORT=3306
          - FRAPPE_DB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          - REDIS_CACHE=redis://redis-cache:6379
          - REDIS_QUEUE=redis://redis-queue:6379
          - REDIS_SOCKETIO=redis://redis-cache:6379
        volumes:
          - sites-data:/home/frappe/frappe-bench/sites
        networks:
          - erp_network
    
      erpnext-queue-long:
        image: frappe/erpnext:v15
        container_name: erpnext-queue-long
        restart: unless-stopped
        command: bench worker --queue long
        depends_on:
          - erpnext
        environment:
          - FRAPPE_DB_HOST=db
          - FRAPPE_DB_PORT=3306
          - FRAPPE_DB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          - REDIS_CACHE=redis://redis-cache:6379
          - REDIS_QUEUE=redis://redis-queue:6379
          - REDIS_SOCKETIO=redis://redis-cache:6379
        volumes:
          - sites-data:/home/frappe/frappe-bench/sites
        networks:
          - erp_network
    
      erpnext-queue-default:
        image: frappe/erpnext:v15
        container_name: erpnext-queue-default
        restart: unless-stopped
        command: bench worker --queue default
        depends_on:
          - erpnext
        environment:
          - FRAPPE_DB_HOST=db
          - FRAPPE_DB_PORT=3306
          - FRAPPE_DB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          - REDIS_CACHE=redis://redis-cache:6379
          - REDIS_QUEUE=redis://redis-queue:6379
          - REDIS_SOCKETIO=redis://redis-cache:6379
        volumes:
          - sites-data:/home/frappe/frappe-bench/sites
        networks:
          - erp_network
    
      erpnext-schedule:
        image: frappe/erpnext:v15
        container_name: erpnext-schedule
        restart: unless-stopped
        command: bench schedule
        depends_on:
          - erpnext
        environment:
          - FRAPPE_DB_HOST=db
          - FRAPPE_DB_PORT=3306
          - FRAPPE_DB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
          - REDIS_CACHE=redis://redis-cache:6379
          - REDIS_QUEUE=redis://redis-queue:6379
          - REDIS_SOCKETIO=redis://redis-cache:6379
        volumes:
          - sites-data:/home/frappe/frappe-bench/sites
        networks:
          - erp_network
    
    volumes:
      db-data:
      redis-cache-data:
      redis-queue-data:
      sites-data:
    
    networks:
      erp_network:
        driver: bridge

    4. Iniciar los servicios

    bash

    docker compose up -d

    5. Crear el sitio de ERPNext

    bash

    source .env
    docker exec -it erpnext-web bench new-site ${SITE} \
      --mariadb-root-password "${MYSQL_ROOT_PASSWORD}" \
      --admin-password "${ADMIN_PASSWORD}" \
      --db-host db \
      --install-app erpnext \
      --set-default

    6. Configurar el correo saliente

    bash

    docker exec -it erpnext-web bench set-config --global smtp_server 172.17.0.1
    docker exec -it erpnext-web bench set-config --global smtp_port 25
    docker exec -it erpnext-web bench set-config --global smtp_mail_from ${MAIL_FROM}
    docker exec -it erpnext-web bench clear-cache

    7. Forzar la contraseña del administrador

    bash

    docker exec -it erpnext-web bench set-admin-password 'TuContraseñaFinal'

    8. Construir los activos estáticos (CSS/JS)

    bash

    docker exec -it erpnext-web bench --site ${SITE} build --force

    9. Servir los estáticos con Nginx (solución robusta)

    Los estáticos se copian del contenedor al host resolviendo enlaces simbólicos con tar -h:

    bash

    # Copiar archivos reales del contenedor al host
    sudo rm -rf /var/www/assets
    sudo docker exec erpnext-web tar -ch -C /home/frappe/frappe-bench/sites assets | sudo tar -x -C /var/www
    
    # Permisos para Nginx
    sudo chown -R www-data:www-data /var/www/assets
    sudo chmod -R a+rX /var/www/assets

    10. Configurar Nginx como proxy inverso con SSL

    Crea el archivo /etc/nginx/sites-available/erp:

    nginx

    server {
        listen 80;
        listen [::]:80;
        server_name erp.tudominio.com;
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name erp.tudominio.com;
    
        ssl_certificate /etc/letsencrypt/live/erp.tudominio.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/erp.tudominio.com/privkey.pem;
        include /etc/letsencrypt/options-ssl-nginx.conf;
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    
        location /assets {
            alias /var/www/assets;
            expires max;
            add_header Cache-Control "public, immutable";
        }
    
        location / {
            proxy_pass http://127.0.0.1:8000;
            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;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }

    Habilita el sitio y obtén el certificado SSL:

    bash

    sudo ln -s /etc/nginx/sites-available/erp /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
    sudo certbot --nginx -d erp.tudominio.com

    11. Acceder

    Abre https://erp.tudominio.com e inicia sesión con:

    • Usuario: Administrator
    • Contraseña: la que definiste en ADMIN_PASSWORD o con set-admin-password

    🔁 Actualizar los estáticos después de cada bench build

    Cada vez que ejecutes bench build, los bundles CSS/JS cambian de nombre y deben copiarse nuevamente al host para que Nginx los sirva. Usa este script de dos pasos:

    bash

    # Paso 1: Copiar desde el contenedor al host
    sudo docker exec erpnext-web tar -ch -C /home/frappe/frappe-bench/sites assets | sudo tar -x -C /var/www
    
    # Paso 2: Ajustar permisos
    sudo chown -R www-data:www-data /var/www/assets
    sudo chmod -R a+rX /var/www/assets
    sudo systemctl reload nginx

    ✅ Resumen de puntos clave aprendidos

    • Usa siempre REDIS_CACHEREDIS_QUEUEREDIS_SOCKETIO como variables de entorno (no FRAPPE_REDIS_*).
    • El archivo .env no debe tener espacios alrededor del =.
    • El backend de ERPNext en producción no sirve estáticos; deben servirse con Nginx (u otro servidor).
    • Para copiar los estáticos al host resuelve los enlaces simbólicos con tar -h o docker cp seguido de un ajuste, pero el método tar -h es más limpio y directo.
    • Si al acceder ves la interfaz sin estilos, basta con ejecutar el paso de actualización de estáticos (punto 9 y el script del apartado anterior).
    • La integración con n8n (pendiente) se hará mediante la API de ERPNext, usando la URL http://erpnext-web:8000 desde el contenedor de n8n si están en la misma red Docker.
  • Configuración del VPS para correo electrónico autoalojado

    Síntesis del proceso de instalación de servicios de correo en un VPS

    1. Preparación del sistema

    • Actualizar el sistema e instalar paquetes base.
    • Configurar el hostname del sistema para que coincida con el dominio de correo (ej. mail.dominio.com).
    • Asegurar conexión estable y que los puertos (25, 587, 993, 143, 110, 995) estén accesibles (firewall abierto).

    2. Instalación de componentes en orden

    2.1. Postfix (MTA – Mail Transfer Agent)

    • Paquete: postfix
    • Configuración durante la instalación: seleccionar Internet Site y escribir el nombre del sistema de correo (mail.dominio.com).
    • Posterior configuración:
      • main.cf: definir myhostnamemydomaininet_interfaceshome_mailbox = Maildir/, etc.
      • Activar autenticación SASL (usando Dovecot) y configuración de restricciones.
      • master.cf: descomentar y ajustar el servicio submission (puerto 587) con opciones SASL.

    2.2. Dovecot (IMAP/POP3)

    • Paquetes: dovecot-coredovecot-imapddovecot-pop3ddovecot-lmtpd (opcional).
    • Configuración:
      • dovecot.conf: definir protocols = imap pop3listen = *.
      • 10-auth.conf: deshabilitar texto plano (opcional en pruebas), mecanismos plain login, incluir auth-system.conf.ext (autenticación PAM).
      • 10-mail.confmail_location = maildir:~/Maildir.
      • 10-master.conf: definir socket Unix para que Postfix hable con Dovecot (/var/spool/postfix/private/auth).
      • 10-ssl.conf (más adelante): configurar certificado SSL.

    2.3. Autenticación y socket de comunicación

    • Punto crítico: Dovecot debe crear el socket /var/spool/postfix/private/auth con permisos adecuados (usuario postfix, grupo postfix). Sin esto, Postfix no puede autenticar usuarios.
    • Verificación: sudo ls -la /var/spool/postfix/private/auth. Si no existe, revisar la sección service auth en 10-master.conf y reiniciar Dovecot.

    2.4. SSL/TLS con Let’s Encrypt

    • Instalar Certbot: certbot.
    • Obtener certificado: detener servicios que usen puerto 80/443, ejecutar certbot certonly --standalone -d mail.dominio.com.
    • Configurar Postfix: en main.cf apuntar smtpd_tls_cert_file y smtpd_tls_key_file a los archivos generados.
    • Configurar Dovecot: crear 10-ssl.conf con ssl = required, y las mismas rutas de certificado.
    • Problema común: Dovecot tiene ssl = no por defecto y no levanta los puertos SSL (993, 995). Es obligatorio establecer ssl = required en dovecot.conf o en 10-ssl.conf para que los listeners imaps y pop3s funcionen.

    2.5. OpenDKIM (firmado de correos)

    • Paquetes: opendkimopendkim-tools.
    • Generar claves: para el dominio (smart-solve.com) con selector (ej. mail).
    • Crear archivos: key.tablesigning.tabletrusted.hosts.
    • Configurar Postfix: agregar el milter (smtpd_milters = inet:127.0.0.1:8891).
    • Registro DNS: publicar el TXT DKIM obtenido (mail._domainkey).

    3. Puntos críticos a los que prestar atención (lecciones aprendidas)

    PasoProblema típicoSíntomaSolución
    PostfixServicio systemd corrupto (ejecuta /bin/true)systemctl status postfix muestra active (exited), pero el proceso master no corre.Iniciar manualmente con sudo /usr/sbin/postfix start o arreglar el archivo de servicio.
    Autenticación SASLPostfix no anuncia AUTHNo aparece 250-AUTH PLAIN LOGIN en EHLO; la autenticación falla.Verificar smtpd_sasl_auth_enable = yes, asegurar que el socket de Dovecot existe y está accesible para postfix.
    Socket DovecotNo se crea /var/spool/postfix/private/authLogs de Postfix: fatal: no SASL authentication mechanisms.Revisar la sección service auth en 10-master.conf, crear el directorio con permisos y reiniciar Dovecot.
    SSL en DovecotPuertos 993/995 no escuchannetstat -tlnp no muestra 993.ssl = no en la configuración global; forzar ssl = required en dovecot.conf o en 10-ssl.conf y reiniciar.
    Certificado SSLPermisos o rutas incorrectasDovecot dice “Can’t load SSL certificate: The certificate is empty”.Verificar que el certificado exista y que el usuario dovecot tenga permisos de lectura. Usar setfacl o cambiar permisos del directorio /etc/letsencrypt/....
    SPF/DKIM/DMARCCorreos caen en spamGmail o proveedores los entregan en carpeta spam.Publicar los registros DNS correctamente y esperar propagación; monitorear con Google Postmaster Tools.
    UsuariosLos buzones no se crean automáticamenteEl usuario del sistema no tiene carpeta Maildir.Crear manualmente ~/Maildir con subcarpetas cur, new, tmp y asignar propietario.

    4. Verificación y pruebas recomendadas

    • Postfix: sudo netstat -tlnp | grep :25 y :587.
    • Dovecot: sudo netstat -tlnp | grep :993 y :143.
    • Autenticación: telnet localhost 587, EHLO, AUTH LOGIN (manual con base64).
    • SSL: openssl s_client -connect localhost:993 -quiet.
    • Envío: swaks o mail comandos para probar flujo local y externo.
    • DNS: Verificar propagación con dig o herramientas web.

    5. Orden resumido de instalación

    1. Preparar sistema (hostname, firewall, actualizaciones).
    2. Instalar Postfix (configurar Internet Site).
    3. Instalar Dovecot (IMAP/POP3) y configurar autenticación y socket.
    4. Probar autenticación (telnet, swaks) sin SSL para confirmar SASL.
    5. Instalar SSL con Let’s Encrypt y configurar Postfix/Dovecot.
    6. Instalar OpenDKIM, generar claves y configurar milter.
    7. Crear usuarios del sistema, sus buzones Maildir.
    8. Configurar registros DNS (SPF, DKIM, DMARC).
    9. Monitorear y calentar la IP (envíos graduales).

    Este esquema resume la experiencia práctica y los puntos donde se produjeron los mayores contratiempos (especialmente el socket de autenticación y la activación de SSL en Dovecot)

  • ¡Hola mundo!

    Bienvenido a WordPress. Esta es tu primera entrada. Edítala o bórrala, ¡luego empieza a escribir!