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_HOSTySMTP_PORTson correctos.SMTP_AUTH=truecuando tu proveedor requiere autenticación.SMTP_STARTTLS=truepara el puerto 587.SMTP_USERNAMEySMTP_PASSWORDson válidos.- El proveedor permite el inicio de sesión SMTP/contraseña de aplicación.
NOTIFICATIONS_EMAIL_ENABLED=truecuando 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_PASSWORDdifiere entre PostgreSQL y el backend.DB_ENCRYPTION_KEYfalta 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=trueen 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:
- Panel de administración — otro administrador abre Configuración > Panel de seguridad > IPs bloqueadas y hace clic en Desbloquear.
- Esperar — los bloqueos temporales expiran tras
SECURITY_TEMP_LOCK_MINUTES(5 minutos por defecto). Los bloqueos permanentes no expiran por sí solos. - 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_PASSWORDpuede restablecerse normalmente con acceso de administrador a PostgreSQL.TIMING_TOKEN_SECRETyPUBLIC_LINK_PASSWORD_SESSION_SECRETpueden regenerarse, pero las sesiones/tokens existentes pueden expirar.DB_ENCRYPTION_KEYes 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
- Crea un nuevo
.envcon nuevos secretos. - Restaura la base de datos desde tu copia de seguridad (
pg_dump/pg_restore). - Todos los datos de negocio (clientes, ofertas, productos, usuarios) serán totalmente accesibles.
- 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=trueestaba 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:
- Si SMTP está configurado, usa la página de inicio de sesión: ¿Olvidaste tu contraseña?
- Si otro administrador puede iniciar sesión, abre Usuarios en el panel de administración y restablece la contraseña.
- 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 -vno 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.