npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

proconnect-conector

v0.1.12

Published

Agente de sincronización de vistas SQL Server → Pro Connected. Servicio de Windows, una vía, solo lectura sobre el origen.

Readme

Agente de sincronización de vistas SQL Server → Pro Connected

Servicio de Windows que lee las órdenes de trabajo de una vista de SQL Server —la base del origen del concesionario— y las publica en el API de Pro Connected.

Integración de una sola vía: nada se escribe de vuelta en el origen.


Cómo funciona, en un párrafo

Cada minuto el agente pregunta a la vista qué ha cambiado desde la última vez, envía esas órdenes una a una al API y anota hasta dónde llegó. Ese "hasta dónde llegó" —el watermark— solo avanza sobre órdenes que el API ya confirmó. Si la red se cae a mitad de camino, el watermark se queda quieto y las órdenes pendientes se reenvían en el siguiente ciclo; el API es idempotente, así que un reenvío no duplica nada.


Las cinco reglas que gobiernan el diseño

Están aquí porque explican decisiones que de otro modo parecen arbitrarias al leer el código.

1. La base del cliente es de solo lectura. Solo SELECT sobre una vista. Ni un UPDATE, ni una tabla de control, ni un procedimiento. El estado del agente vive en su propio archivo SQLite. La cuenta que se pide al DBA solo tiene SELECT sobre la vista: los permisos son la barrera, el código es la intención.

2. El endpoint se define por FQDN, nunca por IP. La whitelist del firewall del cliente es por nombre de dominio. Una IP funciona el día de la instalación y se cae en silencio el día que cambia el balanceador. La validación de la configuración lo rechaza.

3. Solo tráfico saliente HTTPS por el 443. El agente no abre puertos, no levanta ningún servidor y no escucha nada. Soporta proxy explícito por HTTPS_PROXY.

4. Sin privilegios de administrador en ejecución. Corre con una cuenta de servicio dedicada que solo necesita lectura y escritura sobre su propia carpeta de datos.

5. El watermark avanza únicamente sobre registros confirmados por el API. Esta regla manda sobre cualquier consideración de rendimiento.

La asimetría que hay que entender

Ante un rechazo de contrato (400/422) la orden va a cuarentena y el watermark sí avanza. Ante un fallo de red o un 5xx, el watermark no avanza y el ciclo se aborta. Parece incoherente y no lo es:

  • Un rechazo de contrato es permanente: reintentarlo dará el mismo error dentro de un año. Si bloqueara la cola, una sola orden con la placa mal escrita dejaría de sincronizar el taller entero, indefinidamente, y nadie se enteraría.
  • Un fallo de red es transitorio: saltárselo perdería para siempre las órdenes que estaban en vuelo durante una caída de dos minutos, sin dejar traza.

Duplicar es barato y reversible. Perder no se descubre hasta que un cliente reclama.

Hay un tercer caso que conviene conocer: un 401/403 (credencial mala, ruta mal configurada) aborta el ciclo de inmediato, sin reintentos y sin avanzar. Meterlo entre los reintentables machacaría el API con una key que no sirve; meterlo entre los rechazos mandaría todo el backlog a cuarentena y esas órdenes no volverían nunca.


Requisitos previos

| | | |---|---| | Sistema | Windows Server 2016 o superior | | Node.js | 21, 22, 24, 25 o 26. La lista sale de los binarios precompilados de better-sqlite3; fuera de ella el instalador se detiene con un mensaje, no intenta compilar. Node 21 se cubre con un segundo paquete, ver abajo | | Red | Salida HTTPS (443) hacia api.connectd.pro, o api.dev.connectd.pro si el agente es de pruebas. Sin puertos de entrada | | SQL Server | Cuenta de solo lectura sobre la vista (ver docs/vista-origen.sql) | | Cuenta | Una cuenta de servicio dedicada, sin privilegios de administrador |

Sobre better-sqlite3: es un módulo nativo y no usa N-API, así que sus binarios precompilados son por versión de Node, no por línea. Por eso la lista de versiones admitidas es cerrada y el preinstall la comprueba.

Ninguna versión de la librería cubre Node 21 y las líneas modernas a la vez, así que se declaran dos en optionalDependencies --- la 12.11.1 y la 10.1.0 con alias better-sqlite3-legacy --- y es npm quien instala la que corresponde al Node que haya. El detalle está en src/state/cargar-sqlite.ts y en docs/INSTALACION.md §1.1.

Si el servidor no tiene salida a internet, lleve la instalación global desde una máquina con el mismo Node y Windows x64.


Instalación

1. Preparar la base de datos

Entregue docs/vista-origen.sql al DBA del cliente. Crea la vista y una cuenta de solo lectura, y está comentado para que puedan auditarlo antes de ejecutarlo. Pídales de vuelta el nombre de la instancia, el de la base y la contraseña.

2. Instalar el CLI

Consola como administrador:

npm install -g proconnect-conector

Deja el comando proconnect disponible en todo el equipo.

3. Dar de alta el agente

proconnect init

Un asistente pregunta lo que hace falta y comprueba sobre la marcha: prueba la API key contra el API antes de escribirla, deja elegir el taller de la lista que devuelve la plataforma en vez de teclear 24 hexadecimales, conecta a SQL Server y saca de la vista real los nombres de las columnas para proponer el mapeo. Al terminar cifra los secretos con DPAPI y ofrece registrar el servicio.

Todo queda en C:\ProgramData\ProConnect\agentes\<nombre>\: configuración, estado, registros y secretos, en una sola carpeta que se puede respaldar de una pieza.

Un agente por taller:

proconnect init --agent taller-sur

Lo que el asistente no hace es inventar. Si el API no responde pregunta el workshopId a mano y lo dice; si SQL no conecta deja el mapeo vacío y avisa. Y el mapeo que propone hay que revisarlo: una columna asignada al campo equivocado no da error en ninguna parte — manda el dato incorrecto en silencio.

4. Comprobar antes de dejarlo corriendo

proconnect test-conn
proconnect dry-run

test-conn es la herramienta más importante del día de la instalación. No dice "conecta / no conecta": dice cuál de las cinco cosas falla —configuración, secretos, SQL, DNS/proxy o API key— y qué hacer con cada una. dry-run da un ciclo contra la base real sin enviar nada.

5. El servicio

proconnect service-install --agent taller-norte

En services.msc aparece como ProConnectAgent-taller-norte; el nombre con el que Windows lo conoce —el de sc, Get-Service y proconnect list— es proconnectagenttallernorte.exe. Con NSSM en lugar de node-windows hay que usar ESE nombre, o el CLI no verá el servicio:

nssm install proconnectagenttallernorte.exe "C:\Program Files\nodejs\node.exe"
nssm set proconnectagenttallernorte.exe AppParameters "<ruta del paquete>\dist\service\main.js --config C:\ProgramData\ProConnect\agentes\taller-norte\config.json"
nssm set proconnectagenttallernorte.exe DisplayName ProConnectAgent-taller-norte

La guía que se lleva a la implementación —qué tener antes de ir, el paso a paso, qué pregunta el asistente y preguntas frecuentes— está en docs/PUESTA-EN-MARCHA.md.

El runbook largo, con la evidencia de cada paso y el camino a mano, en docs/INSTALACION.md.


Varias sedes en el mismo servidor

Autos Andinos tiene dos talleres y una sola API key. Cada sede es un agente con su carpeta, su config.json, su state.db, sus logs y su servicio.

C:\ProgramData\ProConnect\agentes\
  taller-norte\   workshopId 6b30a1c4f0d2e5a7b9c10002   idPrefix "NORTE-"
  taller-sur\     workshopId 6b30a1c4f0d2e5a7b9c10003   idPrefix "SUR-"

Un solo proceso repartiendo por sede compartiría un único watermark: un problema en El Norte dejaría de sincronizar Sur, y al revés.

idPrefix: normalmente vacío

La orden viaja a Pro Connected con el mismo número que tiene en el DMS del taller. Ese es el número que el taller busca y el que dice por teléfono, así que cualquier cosa que se le añada delante convierte cada consulta de soporte en una traducción a mano.

Hasta la 0.1.11 el asistente proponía un prefijo por sede (NORTE-, SUR-) y esta guía lo llamaba obligatorio, porque se creía que la unicidad del identificador en la plataforma era global. No lo es: las órdenes se identifican y se buscan por taller —se elige el taller y después la orden— así que dos sedes con la OT 000123 no chocan.

El campo sigue existiendo, y sirve para un caso real aunque poco frecuente: un origen que repite números entre sedes y a alguien le viene bien distinguirlos de un vistazo. No hace falta para que la plataforma los guarde bien.


Operación

| Comando | Para qué | |---|---| | proconnect list | Todos los agentes del equipo, su servicio y su watermark | | proconnect test-conn | Diagnóstico completo. Lo primero ante cualquier problema | | proconnect dry-run | Un ciclo contra la base real sin enviar nada | | proconnect once | Un ciclo real y termina | | proconnect restart | Reinicia el servicio para que tome un cambio del config.json | | proconnect update | Trae la última versión publicada y devuelve los servicios al aire | | proconnect panel-key | Guarda la credencial del panel de administración y reinicia el agente | | proconnect quarantine-list | Órdenes rechazadas y por qué | | proconnect quarantine-retry | Las relee del origen y las reintenta | | proconnect watermark-show | Punto de sincronización actual | | proconnect watermark-set <valor> | Lo fija a mano. null para empezar de cero | | proconnect remove | Da de baja un agente |

Todos aceptan --agent <nombre>. Con un solo agente se puede omitir; con varios se exige, y no por comodidad: ejecutar watermark-set sobre el taller equivocado reenvía su histórico entero.

restart existe porque la configuración se lee una vez, al arrancar. Releerla en caliente significaría que un archivo a medio guardar cambia el mapeo en mitad de un ciclo.

update para los servicios, instala y los vuelve a arrancar, en ese orden: en Windows un proceso vivo mantiene bloqueado el .node nativo que tiene cargado, y un npm install -g con el servicio en marcha falla con un EPERM que no menciona el servicio por ningún lado.

Todos aceptan --config <ruta> para elegir la instancia.

quarantine:retry relee la orden del origen, no reenvía la copia guardada: lo normal tras un rechazo es que alguien haya corregido el dato, y reenviar la copia vieja repetiría el mismo error. Además, el payload en cuarentena está enmascarado.


Qué se registra

Un log JSON por ciclo, en logs\, con rotación diaria y 30 días de retención:

{
  "filas_leidas": 12, "creadas": 10, "duplicadas": 1, "rechazadas": 1,
  "fallidas": 0, "ms_sql": 84, "ms_api": 1320,
  "watermark_actual": "0000000000004e20", "desenlace": "completo"
}

Los logs no llevan la API key, la contraseña de SQL ni datos personales completos. Documento, teléfono, correo, nombre y placa se enmascaran (300****567, ju***@correo.com). Todo lo que se registra pasa obligatoriamente por el enmascarador: no hay una vía alternativa que dependa de la disciplina de quien escribe el código.

El latido de salud

Cada 60 segundos, con su propio reloj y al margen del ciclo de sincronización, el agente manda un latido al panel de administración de Pro Connected. Lleva:

| Qué | Para qué sirve | |---|---| | Versión del conector, sistema operativo, arquitectura, IP de la LAN y nombre del host | Soporte sabe a qué máquina entrar sin preguntárselo al TI del taller | | Órdenes confirmadas hoy, órdenes retenidas, errores de envío y ciclos abortados del día | Distinguir "hoy no hubo órdenes" de "hoy no se pudo enviar ninguna" | | El último ciclo completado, o null si todavía no hay ninguno | Ver el ritmo real de trabajo |

Lleva contadores, nunca datos de negocio. Ni órdenes, ni placas, ni nombres, ni el usuario de Windows del servicio. Hay una prueba que falla si alguna vez se cuela un payload de orden en el latido.

Por qué tiene su propio reloj y no sale al cerrar el ciclo. Porque la señal que importa es la ausencia de latido, y para que signifique algo tiene que ser regular. Si saliera con el ciclo: un ciclo largo drenando un backlog dejaría minutos de silencio sin que nada esté mal, un schedule.intervalSeconds de 300 latiría cada 5 minutos, y con SQL Server caído no habría ciclo que cerrar — que es exactamente cuando hace falta enterarse. Con reloj propio, "sin latido durante más de una hora" significa siempre lo mismo: el proceso no corre, o no tiene salida a internet.

Si el latido falla, se registra y ya: nunca falla el ciclo. Es telemetría; si no llega se pierde visibilidad, no datos. Dos salvedades que sí se notan en el log:

  • Un rechazo de credencial (401/403) se registra como error en cada latido, no una vez. Es el fallo más probable el día de la instalación y el más engañoso: la sincronización funciona perfectamente y en el panel el conector no aparece.
  • Un fallo de red repetido se registra la primera vez y una de cada diez, para no enterrar el log con 1.400 líneas al día.

Si el agente no puede leer su propio estado local, no late. Latir con los contadores en cero pintaría en el panel un agente vivo y sin errores —el retrato de un taller tranquilo— justo cuando está roto. El silencio, en cambio, el panel lo sabe interpretar.

El destino no se escribe: se deduce. El agente ya sabe a qué entorno apunta —lo dice api.baseUrl— y de ahí sale el panel que le corresponde. Lo único que no se puede deducir es la credencial, porque el panel la muestra una sola vez, y por eso es ella la que decide a dónde se late. heartbeat.baseUrl sigue existiendo como escotilla para quien sale por un proxy inverso propio, y gana siempre.

Migrar un taller al panel son dos comandos y ninguna edición a mano:

proconnect update      # trae la versión nueva y devuelve los servicios al aire
proconnect panel-key   # pide la credencial sin eco, la cifra y reinicia el agente

La credencial es PROCONNECT_CONNECTOR_KEY (pcdc_...) y no es la misma que la de la ingesta (pcd_...): son dos servicios distintos y el panel no puede validar la de la ingesta. Pegar una donde va la otra lo caza panel-key en el momento, igual que caza una credencial de producción en un agente de desarrollo — los dos casos dan el mismo 401 mudo si se descubren después.

proconnect update es seguro en un taller sin migrar. El cuerpo del latido tiene dos formatos y el agente elige según el destino: sin credencial de conector, manda el cuerpo antiguo al monolito byte por byte, por la misma ruta de siempre. Actualizar el paquete no exige desplegar nada ni editar el config.json. El detalle está en el §9 de docs/specs/0002-latido-de-salud-al-panel.md.


Diagnóstico de fallas comunes

| Síntoma | Causa probable | Qué hacer | |---|---|---| | test-conn: falla sql-login con ENOTFOUND o instance | La instancia con nombre no se resolvió | Escriba sql.server como HOST\INSTANCIA. Compruebe que el servicio SQL Server Browser está arrancado | | test-conn: falla sql-login con Login failed | Usuario, contraseña o base incorrectos | Verifique credenciales y que la cuenta tenga acceso a esa base | | test-conn: falla sql-login con error de certificado | TLS del servidor SQL | "trustServerCertificate": true | | test-conn: falla sql-login con wrong version number | SQL Server antiguo contra el OpenSSL 3 de Node 18+ | Añada "minTlsVersion": "TLSv1" en el bloque sql | | test-conn: falla sql-vista | La cuenta conecta pero no ve la vista | El DBA debe ejecutar docs/vista-origen.sql y conceder SELECT | | test-conn: sql-vista dice que faltan columnas del mapeo | Nombres mal escritos en mapeo | El propio mensaje lista las columnas disponibles | | test-conn: falla dns | El servidor no resuelve el FQDN | nslookup api.connectd.pro. Si hay proxy, puede que resuelva el proxy: defina HTTPS_PROXY | | test-conn: falla tcp-tls pero dns va | Firewall de salida o proxy | Abra el 443 saliente hacia el FQDN, o configure HTTPS_PROXY | | test-conn: falla api-key con 401 | Key incorrecta, revocada o expirada | Verifique PROCONNECT_API_KEY; pida una nueva si hace falta | | El servicio arranca y se detiene solo | Configuración o secretos inválidos | Es deliberado: arrancar a medias es peor. Mire el visor de eventos y corra test-conn | | No sincroniza y el log repite ciclo abortado con fatal | 401/403/404: credencial o ruta | Corra test-conn; revise api.baseUrl y api.path | | No sincroniza y el log repite omitido | Un ciclo tarda más que el intervalo | Normal durante el backlog inicial. Si persiste, suba intervalSeconds o baje batchSize | | Órdenes en cuarentena con WORKSHOP_NOT_FOUND | workshopId no pertenece a la cuenta de la key | Confírmelo con GET /body/api-v2/talleres (lo hace test-conn) | | Órdenes en cuarentena con ID_EXTERNO_CONFLICT | Dos sedes numeran igual sin prefijo | Revise idPrefix. Hay una orden perdiéndose: avise | | Al arrancar: "el estado local guarda un watermark de tipo X" | Se cambió watermark.strategy sin borrar el estado | Decida desde dónde resincronizar: watermark:set -- <valor> o borre state.db | | Faltan datos en las órdenes (p. ej. la placa) | Columna no mapeada o mal escrita | test-conn compara el mapeo con las columnas reales | | Las órdenes aparecen con la fecha corrida 5 horas | useUTC alterado en la conexión | No se debe tocar. Ver src/lib/timezone.ts | | Una orden que el taller modificó no se actualiza en la plataforma | La estrategia es identity, que solo ve altas: modificar la fila no cambia su id | Pida al DBA una columna ROWVERSION en la vista y cambie watermark.strategy a rowversion. Con esa estrategia el cambio se reenvía solo |


Desarrollo

npm install
npm run build
npm test
npm run test:coverage

Las pruebas corren sin SQL Server y sin red: el repositorio SQL y el transporte HTTP se sustituyen por dobles en memoria. Las de integración levantan un servidor HTTP real en 127.0.0.1 y usan el transporte undici de verdad, para ejercitar la serialización, las cabeceras y el parseo que los dobles no tocan.

La suite completa exige una versión con binario precompilado

better-sqlite3 no usa N-API: sus binarios son por versión de Node, no por línea. Con una versión fuera de la lista (21, 22, 24, 25, 26), las pruebas del estado en disco se saltan y lo avisan por consola. Para correrlas de verdad, sobre el mismo runtime que el servidor de destino:

docker compose -f docker-compose.test.yml up --build

Ese contenedor es solo para pruebas. El agente no se despliega en contenedores: corre como servicio nativo de Windows en el servidor del cliente.

Las pruebas E2E, contra un SQL Server de verdad

npm run e2e:up      # levanta SQL Server 2022 + el runner, y ejecuta tests/e2e
npm run e2e:down    # tira la base y su volumen

Levanta un mcr.microsoft.com/mssql/server:2022-latest, aplica tests/e2e/fixtures/esquema.sql —la misma tabla, vista y cuenta de solo lectura que docs/vista-origen.sql crea en casa del cliente— y corre el agente real contra ella: MssqlRepository, CicloScheduler, Mapper, SqliteStateRepository y el cliente HTTP sobre undici. Lo único simulado es el API de Pro Connected, que es un servidor en 127.0.0.1 idéntico al de tests/integration/.

El primer arranque de SQL Server tarda entre 40 y 60 segundos. No hay ningún sleep: el compose espera a un healthcheck que consulta de verdad con sqlcmd, porque el motor abre el puerto TCP bastante antes de aceptar logins y conectarse en esa ventana produce un Login failed que parece un problema de credenciales.

Para depurar una prueba que falla, con la base ya levantada:

npm run e2e:db      # solo SQL Server, con el 1433 publicado
npm run test:e2e

npm test no las ejecuta, a propósito: tests/e2e está excluido de vitest.config.ts. La suite que decide si se puede desplegar no puede depender de que el demonio de Docker haya arrancado esta mañana.

Qué cubren, y por qué no podían ser unitarias

src/sql/ no decide nada: traduce entre el agente y el driver tedious. Sustituir el driver por un doble probaría el doble. Estas son las preguntas que solo contesta un motor:

| Qué se prueba | Por qué importa | |---|---| | useUTC: true en la conexión | Un datetime de la vista escrito como 2026-09-05 08:14:02 tiene que salir en el payload como 2026-09-05T08:14:02-05:00 — ni 13:14 ni 03:14. La prueba lee la misma columna con useUTC en true y en false y comprueba que difieren; el runner corre con TZ=Asia/Tokyo para que la comparación signifique algo. Es el bug que no lanza ninguna excepción y aparece meses después en los informes | | La cuenta de solo lectura no puede escribir | INSERT, UPDATE y DELETE lanzados por el mismo camino que usaría el agente y rechazados por el motor. Los permisos son la barrera, no la disciplina | | separarInstancia | Conecta con host y con host,puerto contra el servidor real | | describirVista() | Devuelve los nombres de columna reales sin sacar ni una fila con datos del cliente | | rowversion | Llega como Buffer de 8 bytes, WHERE rv > @last funciona, y modificar una fila la vuelve a traer — la propiedad que justifica preferir esta estrategia | | identity | Solo ve altas: una fila modificada no vuelve. Es su limitación documentada, y conviene verla ocurrir antes de recomendarla | | datetime | La ventana de solapamiento relee y el sent local absorbe los duplicados sin gastar peticiones. Y con solapamiento 0, unos empates de fecha pierden filas de verdad | | TOP (@batchSize) | Se parametriza: el mismo texto de consulta devuelve cantidades distintas según el bind | | sedeFilter | 200 filas de dos sedes intercaladas con lote de 7: el filtro va dentro del TOP y no se pierde ninguna | | Reanudación | El API se cae a mitad de lote; se reabre un SqliteStateRepository nuevo sobre el mismo archivo y no se pierde ni se duplica ninguna orden | | Multi-instancia | Dos sedes sobre la misma vista, a la vez, con state.db y workshopId propios: identificadores prefijados y watermarks independientes |

La cobertura de la suite E2E sale a coverage-e2e/ y la de npm run test:coverage a coverage/. Sonar lee las dos (sonar.javascript.lcov.reportPaths) y las une: por eso src/sql/** ya no está en sonar.coverage.exclusions.