esofitec. CoreDocs

esofitec. CoreDocs

Addons estándar / Módulo de envío de mailings / Instalación y soporte

Configuración

Cada ejecutable tiene su propia configuración y no son intercambiables, aunque el fichero principal se llame igual en los dos. Todas las claves se leen al arrancar y las que faltan provocan un error inmediato: es intencionado, para que un despliegue incompleto se detecte al abrir y no a mitad de un envío de cuatrocientos correos.

Configuración de la extensión#

appsettings.json#

Bajo la sección SitMailerAppSettings:

Clave Para qué sirve
Bd_ServerName Instancia de SQL Server
Bd_BdName Base de datos de la empresa de a3ERP
Bd_User Usuario de SQL Server
Bd_Password Contraseña de ese usuario
EmailColumnName Título de la columna de la vista que contiene los destinatarios
PostOperationColumnName Título de la columna de la vista que contiene los datos de operación posterior
AttachmentsColumnName Título de la columna de la vista que contiene las rutas de los ficheros vinculados que se adjuntan
PlantillasPath Carpeta donde el usuario guarda las plantillas
EsPlantillaWord true si las plantillas se generan con Word, false si son HTML puro

EmailColumnName, PostOperationColumnName y AttachmentsColumnName son títulos de columna, no nombres de campo de tabla: tienen que coincidir con el alias que devuelve la consulta de la vista. Los valores habituales son Email documentos, Post Operation y Archivos vinculados.

EsPlantillaWord a true hace que el módulo limpie del cuerpo en texto plano el marcado que añade Word. Con plantillas hechas en Word y esta clave a false, los correos llegan con etiquetas y espaciado extraño en los clientes que muestran la versión de texto plano.

appsettings.xml#

Contiene las vistas, una por cada entrada Consulta, con el título que verá el usuario y la consulta SQL dentro de un bloque CDATA para poder escribir SQL multilínea sin escapar nada:

<?xml version="1.0" encoding="utf-8"?>
<SitMailerAppXmlSettings>
  <Consultas>
    <Consulta>
      <Titulo>Aviso de vencimiento próximo</Titulo>
      <Sql><![CDATA[
        SELECT CLI.NOMCLI       AS 'Nombre cliente',
               CLI.E_MAIL_DOCS  AS 'Email documentos',
               CAR.FECHA        AS 'Fecha vencimiento',
               CAR.IMPORTEMON   AS 'Importe total',
               (SELECT NUMCARTERA
                FROM CARTERA
                WHERE NUMCARTERA = CAR.NUMCARTERA
                FOR JSON AUTO)   AS 'Post Operation'
        FROM CARTERA CAR
             INNER JOIN CLIENTES CLI ON CLI.CODCLI = CAR.CODCLI
        WHERE CAR.PAGADO = 'F'
          AND CAR.FECHA BETWEEN GETDATE() AND DATEADD(DAY, 7, GETDATE())
      ]]></Sql>
    </Consulta>
  </Consultas>
</SitMailerAppXmlSettings>

El contrato entre la vista y la plantilla#

Esta es la parte que hay que tener clara para dar soporte, porque explica el noventa por ciento de las incidencias.

Los alias de las columnas de la consulta cumplen cuatro papeles distintos:

  • El alias configurado en EmailColumnName da los destinatarios. Admite varias direcciones separadas por comas o puntos y comas. Las filas con ese valor vacío se descartan y se reportan al usuario.
  • El alias configurado en PostOperationColumnName se guarda tal cual en la cola. Su contenido está pensado para aplicaciones externas al módulo, que lo leen para registrar en a3ERP la constancia del envío; el módulo no lo interpreta ni lo ejecuta. Se genera con FOR JSON. Lo habitual es que quien lo consuma sea un trigger sobre la propia tabla de la cola, que actúa cuando el servicio de envío sella la fecha.
  • El alias configurado en AttachmentsColumnName da las rutas de los ficheros vinculados que se adjuntan a ese correo cuando el usuario marca la casilla de adjuntar. Lo explica Adjuntar los documentos vinculados.
  • Todos los demás alias son los marcadores de las plantillas. El módulo sustituye {{Alias de la columna}} por el valor de la fila, formateado según el tipo de dato: fechas como dd/MM/yyyy, decimales con dos posiciones y separador de miles, booleanos como Sí y No, y nulos como cadena vacía.

De ahí la consecuencia operativa más útil: añadir un dato a los correos es añadir una columna con su alias a la consulta. No hay que tocar binarios, ni subir versión, ni reiniciar nada; el usuario solo tiene que escribir el nuevo marcador en su plantilla. Del mismo modo, renombrar el alias de una columna rompe silenciosamente todas las plantillas que lo usaban: no da error, simplemente el marcador viaja literal en el correo.

Da alias legibles, que los va a escribir el usuario

El alias es lo que el usuario ve en la parrilla y lo que teclea entre llaves dobles. 'Importe total' es mejor que 'IMPORTEMON', y conviene evitar alias que se diferencien solo en tildes o mayúsculas.

Qué columnas son obligatorias#

No se comportan igual, y conviene tenerlo claro antes de escribir una consulta.

La de destinatarios es obligatoria. El alias configurado en EmailColumnName tiene que estar en todas las consultas. Si falta, el proceso se aborta completo en la primera fila con un mensaje de error, y no se genera ningún correo.

La de operación posterior es opcional. Se puede omitir PostOperationColumnName del appsettings.json en las instalaciones que no necesiten operaciones posteriores, y también se puede tener configurada y que una consulta concreta no devuelva esa columna. En ese segundo caso el módulo no falla: encola los correos sin operación posterior y lo dice en un aviso discreto al pie de la pantalla, al refrescar la vista. Es a propósito, porque hay envíos que no tienen que desencadenar nada, como un resumen informativo.

La de ficheros vinculados también es opcional, y solo se mira cuando el usuario marca la casilla de adjuntar. Si la vista no la devuelve, el módulo lo avisa al pie de la misma forma y encola los correos sin adjuntos vinculados. Si la clave AttachmentsColumnName no está en el appsettings.json, la casilla no adjunta nada y no hay aviso: es la forma de desactivarla, pero también lo que pasa si se olvida la clave al actualizar.

Lo que despista al diagnosticar es que la consulta se refresca y se ve en la parrilla con toda normalidad aunque le falte la columna de destinatarios: nadie la mira hasta que el usuario pulsa Procesar. El error que sale entonces es del tipo La columna 'X' no pertenece a la tabla.

Adjuntar los documentos vinculados#

Cuando el usuario marca la casilla de adjuntar los ficheros de los documentos, el módulo lee en cada fila la columna configurada en AttachmentsColumnName y adjunta los ficheros cuyas rutas trae, separadas por |. El módulo no sabe de qué documento habla cada fila: localizar los ficheros es trabajo de la consulta, normalmente uniendo con VINCULOS. Por eso vale para cualquier tipo de documento —facturas, albaranes, ofertas, la propia cartera—, siempre que la consulta sepa llegar a él.

En VINCULOS, TIPO es el tipo del documento del que cuelga el vínculo (CabeFacV, CabeFacC, CABEALBV, CARTERA, Clientes…), IDENT es su identificador guardado como texto, e IDENTHIJO es la ruta del fichero solo cuando TIPOHIJO vale Fichero: un vínculo entre documentos, por ejemplo de un pedido de venta a uno de compra, guarda ahí otro identificador. La consulta tiene que filtrar por los dos tipos y no solo por IDENT, que se repite entre tipos de documento distintos.

Por ejemplo, para que el aviso de vencimientos de más arriba adjunte la factura de venta de cada vencimiento, basta añadir esta columna a su SELECT:

STUFF((SELECT '|' + V.IDENTHIJO
       FROM CABEFACV CV
            INNER JOIN VINCULOS V ON V.IDENT = CONVERT(varchar(20), CONVERT(bigint, CV.IDFACV))
                                 AND V.TIPO = 'CabeFacV'
                                 AND V.TIPOHIJO = 'Fichero'
       WHERE CAR.PROCEDE = 'FV'
         AND CV.SERIE = CAR.SERIE
         AND CV.NUMDOC = CAR.NUMDOC
       FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '') AS 'Archivos vinculados'

Tres detalles de esa columna que no son casuales. El identificador de la factura se convierte a texto para compararlo con IDENT, y no al revés, porque IDENT es varchar y no tiene por qué ser numérico en todos los tipos de vínculo. La factura se localiza por serie y número, porque el mismo número puede repetirse en series distintas. Y el .value() final evita que una ruta con & llegue escapada como &amp;.

De cómo funciona se siguen tres consecuencias:

  • Las rutas se leen desde el puesto que genera el envío, con su usuario de Windows, al pulsar Procesar: la extensión copia en ese momento los ficheros a la carpeta temporal del correo, y el servicio de envío solo lee esa carpeta. Una ruta de red tiene que ser accesible para los usuarios que generan los envíos, no para la cuenta de la tarea programada.
  • Una ruta que no existe se salta, y el correo sale sin ese fichero. Queda una línea Warning en el log de la extensión, pero con MailingViewModel en Error, que es el nivel habitual, no llega a escribirse.
  • Se adjunta todo lo que devuelva la columna, sin límite. En una vista que agrupa varios vencimientos por cliente, la columna tiene que juntar los ficheros de todos si se quieren todos.

Al actualizar desde una versión anterior a la 2.3.3.1

Antes de la 2.3.3.1, el módulo buscaba los ficheros por su cuenta, a partir del NUMCARTERA de la columna de operación posterior, y solo para vencimientos de factura. Una instalación que use la casilla deja de adjuntar documentos al actualizar hasta que se añade AttachmentsColumnName al appsettings.json y la columna a cada vista que la necesite. Sin la clave, ni siquiera hay aviso.

Configuración del servicio de envío#

Bajo la sección SitMailerSenderConfig:

Clave Para qué sirve
SQLServerConnectionSettings Conexión a la base de datos: ServerName, DataBase, User y Password
NotificationsEmail Dirección que recibe copia oculta de todos los envíos y el resumen de errores

El transporte se elige en la sección SitFrameworkMailerSettings, con la clave MailerType:

Valor Configuración que usa
Office365 Office365Settings, con la aplicación registrada en Entra ID: clientId, clientSecret, tenantId y el userId del buzón desde el que se envía
SMTP SmtpSettings, con host, port, user, password y enableSsl

La sección SitLicenciasClientSettings contiene las credenciales de licencia del módulo. Si la validación falla, el proceso no envía ningún correo y lo deja escrito en el log.

La copia oculta va en todos los envíos

NotificationsEmail no es solo la dirección de avisos: el módulo la añade como copia oculta de cada correo que envía, para que la organización tenga constancia de lo comunicado. Conviene que sea un buzón preparado para recibir volumen, no la cuenta personal de alguien.

Con qué credenciales se accede a la base de datos#

Siempre con autenticación de SQL Server: usuario y contraseña de la configuración de cada componente. El módulo no usa autenticación integrada de Windows ni las credenciales del usuario de a3ERP, y la contraseña se emplea tal y como está escrita en el fichero, sin descifrado.

Son dos juegos independientes y conviene que sean usuarios distintos, porque necesitan permisos muy diferentes:

Componente Permisos que necesita de verdad
Extensión Lectura sobre las tablas que consulten las vistas configuradas, y alta sobre SIT_FED_MAIL_LOG
Servicio de envío Solo lectura y actualización sobre SIT_FED_MAIL_LOG

La configuración de la extensión la pueden leer los usuarios

El appsettings.json de la extensión vive junto al ejecutable, en la carpeta de extensión de a3ERP, y quien puede ejecutar el módulo normalmente puede leer los ficheros que tiene al lado. Da por hecho que esas credenciales de SQL están al alcance de cualquier usuario del módulo: usa un usuario con el mínimo privilegio de la tabla anterior y nunca uno administrador de la instancia.

Como el usuario de SQL es el mismo para todos, en la base de datos no queda traza de qué usuario de a3ERP encoló cada envío. Si un cliente necesita esa trazabilidad, hay que resolverla por otro camino.

Contraseñas fuera de los ficheros#

Los dos ejecutables leen también variables de entorno, que tienen prioridad sobre el fichero. Es la forma de no dejar la contraseña escrita en el appsettings.json, definiendo la clave con la ruta de la sección separada por dobles guiones bajos:

SitMailerSenderConfig__SQLServerConnectionSettings__Password=...

Para el servicio de envío es cómodo, porque corre desatendido en el servidor y la variable se define en la cuenta de la tarea programada. Para la extensión tiene menos recorrido, ya que habría que definirla en cada puesto, y ahí la protección real es el mínimo privilegio del usuario de SQL.

Logs#

La sección Logging fija la ruta y el detalle del log de cada componente. Los dos toman Logging:File:Path como nombre base y escriben un fichero por día, con la fecha añadida al nombre: con C:\Logs\Sit.Mailer.Sender.log configurado, el log del servicio del 28 de septiembre de 2026 es C:\Logs\Sit.Mailer.Sender_20260928.log, y todas las ejecuciones de ese día se van añadiendo a él. FileSizeLimitBytes sigue troceando el fichero de un día si llega a superarlo. El módulo no borra los ficheros de días anteriores.

Si el fichero del día no se puede abrir, ni la extensión deja de abrirse ni el servicio deja de enviar. Pasa cuando lo tiene abierto otro proceso —la extensión lo mantiene abierto mientras la pantalla está abierta, así que en un servidor de terminales ocurre en cuanto dos usuarios la tienen abierta a la vez— o cuando lo creó otra cuenta y la que ejecuta no tiene permiso para escribir en él —en Windows Server, con los permisos por defecto, un fichero creado en C:\Logs solo lo puede modificar quien lo creó—. En ese caso el componente escribe en uno propio de la cuenta que ejecuta, <nombre>_<usuario>_<fecha>.log —por ejemplo, Sit.Mailer.App_<usuario>_20260928.log—, y deja al principio de ese fichero un aviso con la causa. Si tampoco puede abrir ese, sigue igualmente, pero sin log en fichero.

Dentro de Logging:LogLevel, el nivel Default gobierna todo, y las claves adicionales permiten subir el detalle solo de una parte del proceso: en la extensión, la carga de la pantalla y el encolado; en el servicio, el arranque y el envío. Para diagnosticar una incidencia, lo práctico es poner esas claves a Trace temporalmente, reproducir el problema y volver a dejarlas como estaban, porque a ese nivel el fichero crece deprisa.