Saltar al contenido

Next.js y Sage. Puede que no haya nada a lo que llamar.

Varios productos de Sage se ejecutan en una máquina de la oficina del cliente, sin dirección pública. Eso no es un obstáculo para la integración - es la integración, y decide qué muestra una página.

8 min de lectura

Llega un encargo que dice que el portal de clientes debe mostrar las facturas pendientes de cada cuenta, y las facturas están en Sage. Se lee como una tarea de obtención de datos. Antes de serlo, hay una pregunta que responder que decide toda la arquitectura, y la respuesta con frecuencia es que no hay ninguna dirección a la que llamar.

Sage es una familia de productos y no un producto. Algunos son servicios alojados con interfaz pública y se comportan como cualquier otra integración SaaS. Otros son aplicaciones instaladas en un servidor de la oficina del cliente, y sus datos están en un fichero de ese servidor. Una aplicación Next.js desplegada no puede alcanzar ese fichero, ni desde un componente de servidor ni desde ningún otro sitio, y ninguna cantidad de librería adecuada lo cambia.

Averigüe dónde se ejecuta, antes que nada

La primera hora del proyecto es una llamada de teléfono, no un esquema.

Pregunte qué producto de Sage, qué edición y dónde está instalado. Pregunte quién administra esa máquina. Prepárese para que la respuesta sea dudosa, y prepárese para que "está en la nube" signifique que el equipo financiero llega a un escritorio Windows a través del navegador, lo cual les parece la nube a todos los que lo usan y no es una API pública.

Solo hay dos desenlaces y no son variantes el uno del otro. O hay una interfaz accesible desde internet, y entonces tiene una integración corriente y el trabajo de verdad es dónde debe ejecutarse cada pieza. O no la hay, y entonces la siguiente sección es el proyecto entero.

La única forma que se aprueba

Cuando el sistema está dentro de la red de otro, la conexión se establece de dentro hacia fuera. Algo que se ejecuta junto a los datos de Sage - un servicio en su servidor, una exportación programada, un conector pequeño - envía datos hacia fuera por HTTPS a un endpoint que usted controla. No se abre nada de entrada, y por eso es la versión que un departamento de sistemas firma.

Su aplicación Next.js está al otro extremo de eso, y nunca es ella la que llama a Sage. Recibe, o más habitualmente lee sin más una base de datos en la que escribe el proceso receptor.

Sage en su servidor
  -> conector dentro de su red   (solo saliente)
    -> endpoint de ingesta       (valida, escribe)
      -> su base de datos        (el modelo de lectura)
        -> componentes de servidor  (renderizan)

Dibujar esto en la primera semana importa más de lo que parece, porque además es el contrato. Todo lo que está a la izquierda de su base de datos es una máquina que usted no controla, con un horario que usted no fija, en un servidor que alguien apaga en Navidad. Todo lo que está a la derecha es suyo y puede hacerse tan rápido y tan fiable como quiera.

Una vez el dibujo está sobre el papel, el resto del diseño deja de tratar sobre Sage. Es un modelo de lectura, y es el problema corriente de elegir una base de datos e indexarla para las consultas que hacen sus páginas.

La antigüedad de los datos es un campo, no un ajuste de caché

El error que viene después es tratar la frescura como una cuestión de caché. No lo es, y la distinción merece precisión porque el vocabulario se solapa.

use cache con cacheLife controla cuánto tiempo conserva Next.js un resultado calculado antes de recalcularlo. Eso es una afirmación sobre su propio renderizado. No dice absolutamente nada sobre lo viejas que son las cifras subyacentes, porque recalcular lee la misma fila que el conector escribió por última vez a las seis y media de esta mañana. Ponga la vida de caché más corta que quiera y el total de la factura no se vuelve más nuevo.

Así que la antigüedad de los datos tiene que modelarse como dato. Cada entidad sincronizada lleva la marca de tiempo de la ejecución que la produjo, y esa columna no es diagnóstico: es un campo que la interfaz muestra.

export async function AccountBalance({ accountId }: { accountId: string }) {
  const account = await db.account.findUnique({ where: { id: accountId } })
  const age = Date.now() - account.syncedAt.getTime()
 
  return (
    <section>
      <Amount value={account.balance} />
      {age > 2 * 60 * 60 * 1000 ? (
        <Warning>
          Actualizado {formatRelative(account.syncedAt)}. El sistema contable
          no ha informado desde entonces.
        </Warning>
      ) : (
        <Note>A las {formatTime(account.syncedAt)}</Note>
      )}
    </section>
  )
}

Dos horas no es un número mágico y debería elegir el suyo a partir de cada cuánto se supone que corre el conector. Lo importante es que el componente tiene tres estados en lugar de dos, y el tercero - presente, pero viejo - es el estado en el que el sistema pasará de verdad sus días malos. Una página que muestra un saldo de hace catorce horas igual que uno fresco no es un fallo de caché. Es un diseño que nunca contempló el caso.

Algunas cifras no deben mostrarse desactualizadas

Una vez decidido que la mayoría de los valores pueden mostrarse con su antigüedad al lado, decida por separado cuáles no.

Un saldo pendiente de esta mañana es útil y está claramente etiquetado. Un límite de crédito que determina si se puede aceptar un pedido es otro tipo de cifra, y mostrar uno viejo significa aceptar un pedido que el equipo financiero habría rechazado. El precio contratado es igual. El stock disponible también, si alguien está a punto de prometérselo a un cliente.

Para esos, la interfaz honesta dice que la comprobación no se puede hacer ahora mismo y ofrece el paso siguiente: retener el pedido para revisión, o decirle al cliente que alguien se lo confirmará. Es peor experiencia que una cifra y mejor que una equivocada, y el equipo financiero le dirá qué campos entran en esta categoría en unos diez minutos si se lo pregunta directamente.

Escribir de vuelta es otra promesa

Leer es la dirección fácil. Un formulario en su aplicación que crea algo al otro lado es donde la arquitectura se pone a prueba, porque no puede confirmarlo.

La Server Action escribe la solicitud en su propia base de datos y vuelve. Eso es todo lo que puede hacer con verdad. Que el registro llegue a Sage depende de una ejecución del conector que puede estar a minutos de distancia, y la acción no tiene forma de esperarla sin mantener una petición abierta más tiempo del que ninguna plataforma permite.

Lo que significa que pendiente es un estado real con su propia fila, su propia pantalla y su propio camino de fallo, no un indicador giratorio. El usuario envía, ve que ha quedado en cola, y lo ve pasar a confirmado cuando una sincronización posterior informa de vuelta. Si falla la validación del lado de Sage - una cuenta de cliente que no existe, una cuenta contable que se renombró -, ese fallo tiene que llegar a algún sitio donde alguien mire, porque ya no queda ninguna petición a la que devolver un error.

Construir esto como interfaz optimista y confiar en que salga bien es con diferencia la forma más común en que estos proyectos pierden una semana en el primer mes tras el lanzamiento. La escritura ocurrió o no ocurrió, y la aplicación es lo único que puede sostener esa diferencia.

Qué se saca de todo esto

Nada de lo anterior es un apaño para una limitación. Un modelo de lectura construido así es más rápido que cualquier llamada en vivo, sigue en pie cuando el servidor contable no lo está, se cruza con el resto de sus datos, y se puede indexar para las consultas que hacen sus páginas en lugar de para las que una API contable fue diseñada.

Lo que cuesta es la honestidad: las cifras tienen una edad, la interfaz lo dice, y las escrituras son solicitudes y no hechos. Los equipos que lo construyen así desde el principio entregan un portal en el que la gente confía. Los que lo añaden tras la primera incidencia reescriben justo los componentes que más importan.

Nuestros proyectos de empresa tienen en gran medida esta forma, y la frontera entre la aplicación y el sistema de referencia es la parte que acotamos primero.

Preguntas relacionadas

¿Puede un componente de servidor llamar a Sage directamente?
Solo si el producto de Sage en cuestión es uno de los alojados con interfaz pública. En las ediciones de escritorio y locales no hay ninguna dirección que su aplicación desplegada pueda resolver, así que la pregunta no es si es buena práctica: la llamada no se puede hacer. Averiguar en cuál de las dos situaciones está es la primera tarea del proyecto.
¿No podemos pedir que abran un puerto en su cortafuegos?
Puede pedirlo, y la respuesta de la mayoría de departamentos de sistemas es que no, con razón. Un servidor de contabilidad expuesto a internet es un riesgo con el libro mayor entero detrás. Lo habitual es lo contrario: algo dentro de su red abre una conexión saliente hacia usted, que no necesita ninguna regla de entrada y es mucho más fácil de aprobar.
¿Cómo de recientes serán los datos?
Tan recientes como la última ejecución correcta de lo que sincronice, que depende de una máquina que usted no administra. Minutos si se ejecuta de forma continua y el servidor está sano, un día si se ejecuta de noche, y más que eso cada vez que alguien reinicia el servidor y se olvida del servicio. Diseñe la interfaz para el caso malo y no para el bueno.
¿Significa esto que el proyecto no merece la pena?
No, significa que el valor está en otro sitio que en una vista en vivo. Un modelo de lectura construido a partir de un sistema contable le da páginas rápidas, sus propios índices, datos que puede cruzar con el resto de su aplicación y un sistema que sigue en pie cuando el suyo no lo está. Eso es real, y nada de ello exige que la cifra en pantalla tenga un segundo.

Volver a todos los artículos