Ir al contenido
Q
QuoteNode

Installation

Solución de problemas de instalación

Problemas comunes de despliegue de QuoteNode con enlaces, CORS, SMTP, DNS, health checks de Docker y secretos perdidos.

Solución de problemas de instalación

La mayoría de los problemas de instalación provienen de discrepancias de URL, SMTP faltante, puertos bloqueados o secretos perdidos. Empieza con la URL visible en tu navegador y trabaja hacia dentro.

Los enlaces públicos se abren en mi equipo pero no para los clientes

Comprueba qué URL se copió o envió.

  • http://localhost/... funciona solo en el mismo equipo.
  • http://192.168.x.x/... funciona solo en la red local o VPN. La VPN puede ser un puente válido para equipos internos, pero no es una estrategia de enlaces públicos para clientes.
  • https://crm.example.com/... funciona públicamente solo cuando DNS, HTTPS, proxy y firewall son correctos.

Para enlaces públicos de clientes, despliega con un dominio público usando servidor público Ubuntu o Coolify.

El navegador muestra errores de CORS

CORS_ALLOWED_ORIGINS debe coincidir exactamente con el origen del navegador. Estos son orígenes diferentes:

https://crm.example.com
https://www.crm.example.com
http://crm.example.com
https://crm.example.com:8443

Corrige la variable, redespliega y reintenta en una sesión de navegador nueva.

El correo no se entrega

Comprueba esto primero:

  • SMTP_HOST y SMTP_PORT son correctos.
  • SMTP_AUTH=true cuando tu proveedor requiere autenticación.
  • SMTP_STARTTLS=true para el puerto 587.
  • SMTP_USERNAME y SMTP_PASSWORD son válidos.
  • El proveedor permite el inicio de sesión SMTP/contraseña de aplicación.
  • NOTIFICATIONS_EMAIL_ENABLED=true cuando los correos de notificación deben enviarse.

El compartido manual de enlaces públicos y la descarga de PDF pueden seguir funcionando sin SMTP.

Los enlaces de preferencias de notificación son incorrectos

Configura:

NOTIFICATIONS_PUBLIC_PREFERENCES_URL=https://<frontend-domain>/notifications/preferences

No lo dejes apuntando a localhost en un despliegue público.

Los contenedores Docker no están saludables

Ejecuta:

docker compose ps
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f gotenberg

Causas comunes:

  • PostgreSQL aún está iniciándose.
  • DB_PASSWORD difiere entre PostgreSQL y el backend.
  • DB_ENCRYPTION_KEY falta o está malformada.
  • Gotenberg no está saludable, por lo que la generación de PDF no puede iniciarse.
  • Los puertos 80/443 del host ya están usados por otro servicio.

Tras corregir la configuración, reinicia el servicio afectado o el stack:

docker compose restart backend
docker compose up -d

Mi IP está bloqueada — no puedo iniciar sesión

Hay dos causas comunes: la lista de IP permitidas bloqueó el acceso o la protección contra fuerza bruta bloqueó tu IP tras demasiados intentos fallidos de inicio de sesión.

Bloqueo por lista de IP permitidas

QuoteNode tiene una lista de IP permitidas configurable en Configuración > Seguridad > Lista de IP permitidas (o mediante la variable SECURITY_IP_WHITELIST). Cuando está activada, protege toda la superficie de administración — incluidas las pantallas de inicio de sesión y 2FA — por lo que una dirección excluida recibe 403 IP_NOT_ALLOWED y ni siquiera puede iniciar sesión. (Los health checks y los enlaces públicos de ofertas para clientes siguen accesibles.)

Primero espera dos minutos si acabas de cambiarla en la aplicación. Una activación o restricción en la aplicación está protegida contra bloqueos: tu sesión debe volver a confirmarla dentro de una ventana corta (120 s por defecto), y si tu dirección actual no está incluida, la lista se revierte automáticamente a la anterior. Así, un autobloqueo accidental desde la pantalla de Seguridad se cura solo — espera y vuelve a intentarlo. (Esta red de seguridad no se aplica a la variable SECURITY_IP_WHITELIST, que se aplica al arrancar.)

Recuperación (anulación del operador): si sigues bloqueado — p. ej. una SECURITY_IP_WHITELIST del operador, una lista almacenada inválida o no puedes esperar — añade esta variable al entorno del contenedor backend (.env o sección environment: en docker-compose) y reinicia:

SECURITY_IP_WHITELIST_FORCE_DISABLE=true
docker compose restart backend

Esto desactiva completamente la lista de permitidos y permite iniciar sesión. Una vez dentro, corrige la lista en Configuración > Seguridad, luego elimina SECURITY_IP_WHITELIST_FORCE_DISABLE y reinicia de nuevo.

No dejes SECURITY_IP_WHITELIST_FORCE_DISABLE=true en producción. Desactiva todo el control de acceso por IP.

Bloqueo por protección contra fuerza bruta

QuoteNode bloquea direcciones IP tras demasiados intentos fallidos de inicio de sesión en una hora:

Umbral Fallos en 1 h Efecto
Soft 10 Retraso progresivo (1 s por intento adicional, máx. 30 s)
Bloqueo temporal 20 Bloqueado durante 5 minutos
Bloqueo permanente 50 Bloqueado hasta desbloqueo manual

Cómo desbloquear:

  1. Panel de administración — otro administrador abre Configuración > Panel de seguridad > IPs bloqueadas y hace clic en Desbloquear.
  2. Esperar — los bloqueos temporales expiran tras SECURITY_TEMP_LOCK_MINUTES (5 minutos por defecto). Los bloqueos permanentes no expiran por sí solos.
  3. Elevar umbrales temporalmente si estás bloqueado y ningún otro admin puede ayudar. Añade al .env, reinicia, inicia sesión y luego elimina:
SECURITY_MAX_FAILED_LOGINS_SOFT=9999
SECURITY_MAX_FAILED_LOGINS_TEMP_LOCK=9999
SECURITY_MAX_FAILED_LOGINS_HARD_LOCK=9999

Restaura los valores predeterminados tras recuperar el acceso.

Bloqueos de IP en enlaces públicos

El limitador de tasa de enlaces públicos bloquea direcciones IP que exceden los límites de intentos. Estos bloqueos están en memoria y expiran tras PUBLIC_RATE_LIMIT_IP_BLOCK_MINUTES (60 minutos por defecto) o tras un reinicio del backend.

La actualización se detiene con “cannot enforce offer-section ownership”

Tras una actualización el backend no arranca y docker compose logs backend muestra:

Migration V145 cannot enforce offer-section ownership: 3 offer_items row(s) point at a
section of another offer or tenant.

Qué significa

Una oferta se compone de secciones (“Hardware”, “Servicios”) y de las líneas colocadas en ellas. Esta versión hace estricto ese vínculo: una línea solo puede estar en una sección de su propia oferta. Algunas filas antiguas de su base de datos incumplen esa regla, así que la actualización se detiene y le pide una decisión en lugar de reescribir sus ofertas por su cuenta.

Nada está roto y el propio rechazo no ha modificado nada. Esta migración se ha revertido por completo: no hay cambios aplicados a medias ni nada que reparar en el historial de migraciones. Tenga en cuenta, eso sí, que migraciones anteriores de la misma actualización pueden haberse aplicado ya: su base de datos está justo antes de este paso, no necesariamente en la versión de partida. Eso también significa que los contenedores anteriores pueden funcionar con ella o no, según lo que cambiaran aquellas migraciones: no cuente con revertir las imágenes como salida.

Elija qué debe ocurrir

Opción Qué hace Qué cambia para usted
Comprobar el problema (predeterminado) Lista las ofertas y líneas afectadas. Nada. Solo lectura.
Desvincular las asignaciones erróneas Borra la sección solo de esas líneas. Las líneas siguen en su oferta con el mismo precio, cantidad y descripción. Pierden su encabezado de sección y aparecen como líneas sin agrupar.
Elegir una sección para cada línea Pregunta por cada línea y ofrece solo secciones de su propia oferta. Conserva la agrupación que desea. Las líneas omitidas quedan sin resolver y la actualización volverá a detenerse.

Las ofertas ya enviadas a clientes siguen mostrando exactamente lo que el cliente vio: el contenido de una oferta enviada se congela al enviarla, de modo que ni el enlace público ni el PDF cambian.

Localice primero la instalación correcta (Coolify o varios stacks en un servidor)

Cada comando siguiente debe apuntar a su instalación. Un servidor puede alojar varias, y más de una puede tener un servicio llamado backend, así que liste primero las candidatas: cada línea es una instalación, con su nombre de proyecto y el fichero Compose desde el que se inició:

docker inspect $(docker ps -aq --filter "label=com.docker.compose.service=backend" \
                             --filter "label=com.docker.compose.oneoff=False") \
  --format '{{index .Config.Labels "com.docker.compose.project"}} | {{index .Config.Labels "com.docker.compose.project.config_files"}}'

Si obtiene más de una línea, elija la suya; no adivine. Después ejecute cada comando nombrando explícitamente ese proyecto y ese fichero, en lugar de un docker compose a secas, para no actuar sobre la instalación equivocada:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Sustituya <project> y <compose-file> por los valores de su línea y manténgalos en todos los comandos siguientes: eso es lo que hace que cada uno actúe sobre su instalación y no sobre otra. Si la línea muestra varios ficheros separados por comas, repita -f para cada uno, en ese orden. Si la lista está vacía, los contenedores no se iniciaron con Docker Compose: use el directorio que le indicó su proveedor o la guía de instalación, o pregunte a quien configuró el servidor. En Coolify, hágalo en un terminal del servidor, no del contenedor: el terminal del contenedor necesita un backend en marcha, y el suyo no lo está. (Los mensajes de la propia herramienta muestran un docker compose … corto sin esas opciones, para que se lean bien: añada su -p y -f al copiarlos.)

Haga primero una copia de seguridad

PostgreSQL sigue funcionando aunque el backend no arranque, así que puede hacer la copia sin la aplicación. El comando lee el nombre de la base y el usuario del propio contenedor, por lo que funciona sea cual sea el nombre que les diera, y no aparece ninguna contraseña en pantalla:

docker compose -p <project> -f <compose-file> exec -T postgres sh -c \
  'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom -f /tmp/before-repair.dump \
   && pg_restore --list /tmp/before-repair.dump | head -3'
docker compose -p <project> -f <compose-file> cp postgres:/tmp/before-repair.dump ./before-repair.dump
ls -lh before-repair.dump

La línea pg_restore --list imprime las primeras entradas del archivo: si las ve y ls -lh muestra un fichero no vacío, la copia es legible. Guarde before-repair.dump junto a su docker-compose.yml: está en el disco del servidor, no dentro de un contenedor, así que sobrevive a todo lo que sigue.

Comprobar el problema

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Este comando solo lee. Muestra las ofertas afectadas, qué falla en cada línea y las opciones:

Found 3 offer item(s) in 2 offer(s) assigned to a section that does not belong to that offer.

  Offer OF-2026-014 [DRAFT]
    item:    Mounting frame
    section: section "Installation" — section belongs to another offer

Vista previa y luego aplicar

Sin --apply=true cada comando es una vista previa: muestra exactamente lo que haría y termina sin haber cambiado nada, en cualquier terminal.

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink

Añada --apply=true --acknowledge-backup=true para ejecutarlo. Es la única forma que escribe:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink --apply=true --acknowledge-backup=true

Para decidir usted mismo cada línea, use --plan=choose. La herramienta hace una pregunta por línea y enumera las secciones de esa oferta, de modo que nunca escribe un identificador de base de datos. Sin --apply=true sigue limitándose a mostrar el plan:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=choose --apply=true --acknowledge-backup=true

Finalizar la actualización

Cuando la herramienta informe de que ninguna línea apunta ya a una sección incorrecta:

docker compose -p <project> -f <compose-file> up -d

La migración bloqueada no dejó nada pendiente, así que simplemente se ejecuta en el siguiente arranque. No necesita ningún comando flyway repair ni baseline — y no debe usarlos, porque ocultarían el problema de datos en lugar de resolverlo.

Verifique como siempre: abra /health y compruebe la nueva versión en Admin > Licencia.

Si indica que los datos cambiaron desde su vista previa

La herramienta aplica el plan que usted aprobó, no lo que encuentre en el momento de escribir. Si durante la confirmación apareció una asignación errónea, alguna se movió o se eliminó una sección que había elegido, rechaza toda la reparación y no cambia nada. Vuelva a ejecutar la comprobación para ver el estado actual y elija de nuevo.

Si indica que la reparación se guardó — o que no pudo confirmarlo

La reparación se ejecuta en una sola transacción, así que o se guarda entera o no se guarda. Dos finales requieren atención. Si falla un paso posterior —escribir el registro o la relectura que confirma el resultado— la herramienta mantiene la palabra SAVED en el mensaje: la reparación funcionó, así que no restaure la copia de seguridad. Si en cambio aparece UNCONFIRMED, la base de datos nunca respondió y nadie puede decir cómo terminó; no restaure ninguna copia ni repita la reparación. En ambos casos vuelva a ejecutar la comprobación de solo lectura: si informa de que no hay nada que reparar, ha terminado y puede arrancar el stack; si sigue listando las mismas líneas, no se guardó nada y puede repetir su elección.

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Una cosa que conviene leer con atención: si usó Elegir una sección para cada línea y omitió algunas, esas líneas omitidas seguirán apareciendo en la comprobación. Es lo esperado y no significa que la reparación fallara. Compare la lista con las líneas que realmente eligió arreglar: si solo quedan las omitidas, la reparación funcionó y lo que queda es la decisión que aplazó; vuelva a ejecutar la herramienta para ellas cuando quiera. Si siguen apareciendo líneas que sí eligió arreglar, no se guardó nada.

Qué se cambió y qué hacer si sigue fallando

Cada ejecución que cambia algo escribe los identificadores de la línea, la oferta y la sección afectadas en /app/data/backups/ del volumen del backend. No contiene datos de clientes y puede adjuntarse a una solicitud de soporte. Si el fichero no se puede escribir, la herramienta imprime el mismo registro en pantalla: cópielo antes de cerrar el terminal:

docker compose -p <project> -f <compose-file> run --rm --entrypoint sh backend \
  -c 'ls -1 /app/data/backups/offer-section-repair-*.json'

Si la actualización sigue deteniéndose después:

  • vuelva a ejecutar la comprobación: las líneas omitidas siguen apareciendo;
  • si el mensaje nombra otra migración, esta herramienta no se aplica. Solo repara asignaciones de secciones de líneas de oferta;
  • si el mensaje no cambia y la comprobación no informa de nada que reparar, comunique el problema adjuntando el registro del backend y el listado anterior.

El certificado HTTPS no se emite

Para despliegues públicos:

  • DNS debe apuntar al servidor de despliegue.
  • Los puertos 80 y 443 deben ser alcanzables desde internet.
  • Solo un proxy debe terminar HTTPS para el mismo dominio.
  • En Coolify, asigna el dominio al servicio frontend.

Perdí el archivo .env

Si perdiste .env, detente y evalúa antes de rotar valores.

  • DB_PASSWORD puede restablecerse normalmente con acceso de administrador a PostgreSQL.
  • TIMING_TOKEN_SECRET y PUBLIC_LINK_PASSWORD_SESSION_SECRET pueden regenerarse, pero las sesiones/tokens existentes pueden expirar.
  • DB_ENCRYPTION_KEY es crítica. Sin la clave original, los valores cifrados no pueden descifrarse.
  • Si el cifrado de PII está habilitado, los campos de cliente/contacto cifrados dependen de DB_ENCRYPTION_KEY.

Restaura .env desde backup siempre que sea posible.

Recuperación si se pierde .env pero existe copia de seguridad de la base de datos

  1. Crea un nuevo .env con nuevos secretos.
  2. Restaura la base de datos desde tu copia de seguridad (pg_dump / pg_restore).
  3. Todos los datos de negocio (clientes, ofertas, productos, usuarios) serán totalmente accesibles.
  4. Solo se pierden unos pocos campos: códigos MFA (los usuarios se vuelven a registrar), contraseña SMTP (volver a introducir en ajustes), clave API FX (volver a introducir). Si ENCRYPT_PII=true estaba habilitado, los datos de clientes cifrados también se pierden.

Si tus copias de seguridad están cifradas con age (por defecto), necesitas el archivo de identidad age de tu kit de recuperación para descifrarlas. Consulta la guía Backup y Restauración.

Olvidé la contraseña del administrador

Usa una ruta de recuperación no destructiva:

  1. Si SMTP está configurado, usa la página de inicio de sesión: ¿Olvidaste tu contraseña?
  2. Si otro administrador puede iniciar sesión, abre Usuarios en el panel de administración y restablece la contraseña.
  3. Si ningún administrador puede iniciar sesión y el flujo de restablecimiento por correo no está disponible, restaura el acceso desde una copia de seguridad en buen estado o usa tu procedimiento de mantenimiento interno.

docker compose down -v no es un procedimiento de restablecimiento de contraseña. Elimina todos los datos — usuarios, ofertas, clientes, ajustes y copias de seguridad. Úsalo solo para entornos de prueba desechables.

Falla la verificación de licencia

Para despliegues reales, mantén:

LICENSE_SKIP_SIGNATURE_CHECK=false

Si una licencia válida aún falla, comprueba que el despliegue tiene la versión de aplicación esperada y un reloj de sistema sincronizado con NTP. Confirma que la licencia coincide con la edición/versión desplegada, luego contacta con soporte o reemplaza el archivo de licencia a través de la ruta de operador documentada. No configures el bypass de firma a true como solución temporal en producción.

Last reviewed: Recently