En estos días he estado terminando de programar Sublime POS 4 multinegocio con soporte para internet. Básicamente es:

  • Sublime POS 4 el punto de venta con todas sus características
  • Puedes usarlo en la nube o en red local; ya no guarda los datos en el navegador
  • Tiene gestor de negocios para facilitar su renta

En este tutorial solamente voy a tocar aspectos técnicos sobre su instalación en un servidor Linux sobre todos los pasos que hay que seguir.

Clonar repositorio

Clonar el repositorio, revisar si estás en rama correcta.

Haz go mod tidy por las dudas

Configuración de archivo de entorno

Configura el archivo .env copiando el .env.example

  • Para CLAVE_JWT usa head -c 32 /dev/urandom | xxd -p -u | tr -d '\n'
  • Para CLAVE_ENCRIPTACION_BD usa head -c 32 /dev/urandom | xxd -p -u | tr -d '\n'
  • Para CLAVE_ENCRIPTACION_RESPALDOS usa openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 32
  • En Entorno escribe production
  • Finalmente en PUERTO_SERVIDOR elige un puerto desocupado. Recuerda que vas a configurar el proxy a este puerto más adelante. En mi caso es :8205

Primera compilación

Ejecuta make

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ make
gofmt -w .
go build -tags "json1" -ldflags "-X 'main.fechaCompilacion=2026-09-18 15:54:55 UTC' -s -w" -o api_spos4.exe
./api_spos4.exe
Escuchando en :8205. Presiona CTRL + C para salir

Ya viste que ya compiló, que no hubo problemas con el puerto. Detenlo y asegúrate de conocer la ruta actual con pwd

Nota importante aquí tiene la extensión .exe así que así debes ponerla. No importa, es porque el makefile lo compila así. Solo fíjate en el nombre del ejecutable que aparece arriba de Escuchando en :

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ pwd
/home/parzibyte/spos4_prod/api

Entonces es /home/parzibyte/spos4_prod/api y el ejecutable que hizo make está en /home/parzibyte/spos4_prod/api/api_spos4.exe

Servicio de systemd

Vamos a crear un servicio y probar. Entonces:

sudo vim /etc/systemd/system/spos4_prod.service

Y le ponemos lo siguiente. La ruta que he venido mencionando es importante porque debemos ponerla en el WorkingDirectory.

Lo mismo para el ExecStart, que es WorkingDirectory mas el ejecutable

[Unit]
Description=Sublime POS 4 producción
After=network.target

[Service]
User=parzibyte
Group=parzibyte
# Ruta al directorio donde está el servidor de Go
WorkingDirectory=/home/parzibyte/spos4_prod/api
# Comando para iniciar la aplicación
ExecStart=/home/parzibyte/spos4_prod/api/api_spos4.exe

Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Guardamos. Se llama spos4_prod.service así que lo vamos a activar e iniciar. No es necesario ponerle el .service

sudo systemctl daemon-reload
sudo systemctl enable spos4_prod
sudo systemstl start spos4_prod

En mi caso:

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl daemon-reload
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl start spos4_prod
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl status spos4_prod
● spos4_prod.service - Sublime POS 4 producción
     Loaded: loaded (/etc/systemd/system/spos4_prod.service; enabled; preset: disabled)
     Active: active (running) since Fri 2026-09-18 10:12:56 CST; 6s ago
   Main PID: 1820933 (api_spos4.exe)
      Tasks: 6 (limit: 48900)
     Memory: 9.2M (peak: 9.5M)
        CPU: 56ms
     CGroup: /system.slice/spos4_prod.service
             └─1820933 /home/parzibyte/spos4_prod/api/api_spos4.exe

Sep 18 10:12:56 vmi2711835.contaboserver.net systemd[1]: Started Sublime POS 4 producción.

Debe decir running y todo bonito. Ahora simplemente probamos ya para pasar al siguiente punto. Hacemos un curl a la fecha de compilación.

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ curl http://localhost:8205/fecha_compilacion
"2026-09-18 15:54:55 UTC"

Aquí 8205 es el puerto configurado en .env

Proxy de Nginx

Pasemos a configurar Nginx. Haremos que redireccione una ruta a nuestro puerto 8205

sudo vim /etc/nginx/conf.d/default.conf

Dentro de server añadimos:

location /apps/pos2/{
    # Dejar que Go maneje el tamaño máximo pero poner un buen límite acá
    client_max_body_size 100M;
    proxy_pass http://localhost:8205/;
}

Recuerda que ahora apps/pos2 debe estar configurado en el lado del cliente para la API. Más de eso en un momento.

Checamos sintaxis y si todo va bien lo recargamos

sudo nginx -t
sudo systemctl reload nginx

En mi caso:

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl reload nginx
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$

Como yo uso parzibyte.me y configuré /apps/pos2 entonces puedo probar accediendo a la API con https://parzibyte.me/apps/pos2/

La primera vez (porque no has configurado dist) te debe dar un 404 pero es un 404 propio de Go. Probemos que la API funciona igualmente consultando la fecha de compilación como lo hicimos con cURL hace un momento pero ahora lo haremos desde el navegador a Nginx. Naveguemos a

https://parzibyte.me/apps/pos2/fecha_compilacion

Te debe dar la misma fecha de compilación que te dio al hacer cURL. Si pasa eso entonces ya quedó; solo resta compilar el lado del cliente, subirlo con rsync a la carpeta dist y listo

Subiendo lado del cliente

Antes de salir de la terminal y solo porque no sé qué pasa si no lo haces, crea la carpeta dist que debe ser hermana del ejecutable.

mkdir dist

Y recuerda siempre la ruta absoluta a dist ya que la necesitamos más adelante. En mi caso:

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ mkdir dist
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ cd dist
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api/dist]$ pwd
/home/parzibyte/spos4_prod/api/dist

Es /home/parzibyte/spos4_prod/api/dist y la ruta de la API es https://parzibyte.me/apps/pos2 (quita la / del final)

Configuremos .env.production de JS

VITE_URL_API=https://parzibyte.me/apps/pos2

Compilamos (en la carpeta raíz, no en la carpeta api):

npm run build

Si todo va bien entonces hacemos rsync con dry run para evitar errores

rsync -rvniz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/

En mi caso la salida es:

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ rsync -rvniz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/
sending incremental file list
<f+++++++++ favicon.ico
<f+++++++++ index.html
<f+++++++++ logo.svg
<f+++++++++ manifest.json
cd+++++++++ assets/
<f+++++++++ assets/index-rE_mlUUX.js
<f+++++++++ assets/index-uds7SAWG.css
<f+++++++++ assets/ping-82822-md4el-YO.mp3
<f+++++++++ assets/wrong-47985-pJgIJXG3.mp3
cd+++++++++ images/
<f+++++++++ images/escritorio.png
<f+++++++++ images/inventario.png
<f+++++++++ images/pos.png
<f+++++++++ images/pos_móvil.png

sent 509 bytes  received 60 bytes  126,44 bytes/sec
total size is 2.113.031  speedup is 3.713,59 (DRY RUN)

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ 

Como vi que la simulación fue exitosa vuelvo a ejecutarlo pero sin dry run o sea quito la n

rsync -rviz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/

Visito de nuevo https://parzibyte.me/apps/pos2/ ya sin fecha_compilación y debe darme la webapp

Con esto ya casi tienes todo pero falta generar la licencia y todo eso. Podemos comprobarlo al registrar un negocio e ingresar porque no van a aparecer las sucursales (de hecho deberíamos manejar también ese error)

Generando licencia

Vamos a generar la licencia. Primero compilamos el registrador que está en el mismo código y hacemos que nos dé el machineid del servidor. Entonces:

parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api/dist]$ cd ..
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ cd ..
parzibyte@servidor-de-parzibyte.com[~/spos4_prod]$ cd registrador/
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/registrador]$ ls
go.mod  go.sum  main.go
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/registrador]$ go build
go: downloading go1.26.0 (linux/amd64)
go: downloading github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/registrador]$ ls
go.mod  go.sum  main.go  registrador_spos4
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/registrador]$ ./registrador_spos4
=== REGISTRADOR DE MÁQUINA BY PARZIBYTE ===
MachineID original: hunter2hunter2hunter2
Puedes escanear el siguiente código QR con el MachineID cifrado:

Esto obviamente no crea ningún servidor ni nada, solo es un simple ejecutable que puedes abrir para tener el machineid. Guarda ese machine id, luego ejecuta el firmador y pon dicho machineid, el id de cliente y las fechas.

Recuerda que la clave pública y privada debieron ser generadas antes, y que la pública va en verificador_licencia.go

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4/firmador
$ ./firmador_spos4.exe 
Introduce el MachineID: hunter2hunter2
Introduce ID de usuario/cliente: 1
Fecha inicio [Default hoy UTC: 2026-09-18]: 
Introduce fecha fin (YYYY-MM-DD): 2027-09-18

1__2026-09-18__2027-09-18__estaeslafirmablablablablasísísí

¡Licencia generada!

Lo copias y lo pegas en un firma.txt que debe ser hermano del ejecutable. Ahora finalmente consulta a /detalles_licencia:

https://parzibyte.me/apps/pos2/detalles_licencia

Y debería decirte algo como:

{"idCliente":"1","fechaInicio":"2026-09-18","fechaFin":"2027-09-18","machineId":"hola"}

Nota para Parzibyte del futuro: ya que el sistema tiene el machineid podemos tener un endpoint para únicamente el machineid que no se vea afectado por la licencia, así evitamos tener que compilar el registrador por cada cliente

Actualizando sistema

Recomiendo preparar primero el lado del servidor y luego el lado del cliente.

Lado del servidor

Vamos a hacer que el servicio se detenga por el menor tiempo posible, así que lo vamos a detener hasta haber compilado la versión anterior.

Ingresamos a la carpeta api, descargamos cambios con git pull, borramos ejecutable anterior (no te preocupes, seguirá funcionando), compilamos con make y luego reiniciamos el servicio.

Puede que make dé un error al ejecutar porque el puerto ya está ocupado. Tal vez en el futuro modifique para que make solo compile, pero mientras tanto el hecho de que aparezca el error de que la dirección ya está ocupada significa que fue compilado correctamente.

cd spos4_prod/api/
git pull
rm api_spos4.exe
make
sudo systemctl restart spos4_prod

En mi caso:

parzibyte@servidor-de-parzibyte.com[~]$ cd spos4_prod/
parzibyte@servidor-de-parzibyte.com[~/spos4_prod]$ cd api
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ git pull
remote: Enumerating objects: 77, done.
remote: Counting objects: 100% (77/77), done.
remote: Compressing objects: 100% (18/18), done.
remote: Total 49 (delta 41), reused 37 (delta 29), pack-reused 0 (from 0)
Unpacking objects: 100% (49/49), 12.70 KiB | 151.00 KiB/s, done.
From github.com:parzibyte/spos4
   5ff40cb..8330d62  api        -> origin/api
Updating 5ff40cb..8330d62
Fast-forward
 .env                                                  |    2 +-
 .env.production                                       |    4 +-
 README.md                                             |  286 ++++++++-
 api/bd.go                                             |    9 -
 api/controlador_ajustes.go                            |   61 +-
 api/controlador_servicios.go                          |    2 +-
 api/handlers_ajustes.go                               |   10 +
 api/main.go                                           |    1 +
 api/mapa_rutas_permisos.go                            |    1 +
 src/Clases.ts                                         |  109 ++++
 src/components/Negocios/CrearNegocio.vue              |   37 +-
 src/components/PuntoDeVenta/DialogoVentaTerminada.vue |    3 +-
 src/components/Usuarios/Login.vue                     |   11 +-
 src/stores/db.ts                                      |    2 +
 src/stores/tickets.ts                                 | 2745 ++++++-------------------------------------------------------------------------------
 src/translations.ts                                   |    9 +-
 src/views/AcercaDe.vue                                |    2 +-
 src/views/Ajustes/AjustesView.vue                     |   33 +-
 18 files changed, 709 insertions(+), 2618 deletions(-)
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ rm api_spos4.exe 
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ make
gofmt -w .
go build -tags "json1" -ldflags "-X 'main.fechaCompilacion=2026-09-19 17:19:08 UTC' -s -w" -o api_spos4.exe
./api_spos4.exe
Escuchando en :8205. Presiona CTRL + C para salir2026/09/19 11:19:22 listen tcp :8205: bind: address already in use
make: *** [makefile:14: run] Error 1
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl restart spos4_prod
[sudo] password for parzibyte: hunter2
parzibyte@servidor-de-parzibyte.com[~/spos4_prod/api]$ sudo systemctl status spos4_prod
● spos4_prod.service - Sublime POS 4 producción
     Loaded: loaded (/etc/systemd/system/spos4_prod.service; enabled; preset: disabled)
     Active: active (running) since Sat 2026-09-19 11:20:38 CST; 11s ago
   Main PID: 1825397 (api_spos4.exe)
      Tasks: 6 (limit: 48900)
     Memory: 4.0M (peak: 4.6M)
        CPU: 50ms
     CGroup: /system.slice/spos4_prod.service
             └─1825397 /home/parzibyte/spos4_prod/api/api_spos4.exe

Sep 19 11:20:38 vmi2711835.contaboserver.net systemd[1]: Started Sublime POS 4 producción.

Lado del cliente

Es lo mismo que hicimos la primera vez que lo subimos; solo hay que compilar con npm run build y luego sincronizar con rsync

Entonces:

npm run build
rsync -rvniz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/
# Si la salida anterior te parece correcta hazlo sin dry run
rsync -rviz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/

De nuevo, en mi caso sale así:

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ npm run build

> spos4@0.0.0 build
> run-p type-check "build-only {@}" --


> spos4@0.0.0 type-check
> vue-tsc --build --force


> spos4@0.0.0 build-only
> vite build

vite v5.0.12 building for production...
transforming (2) vite\modulepreload-polyfill.jsBrowserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
1159 modules transformed.
dist/index.html                         2.79 kB │ gzip:   1.27 kB
dist/assets/ping-82822-md4el-YO.mp3     4.42 kB
dist/assets/wrong-47985-pJgIJXG3.mp3    5.52 kB
dist/assets/index-TmHVWOg-.css         24.89 kB │ gzip:   5.06 kB
dist/assets/index-HsgfTG-P.js         967.88 kB │ gzip: 277.01 kB

(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 24.61s

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ rsync -rvniz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/
sending incremental file list
<f..T...... favicon.ico
<f..T...... index.html
<f..T...... logo.svg
<f..T...... manifest.json
*deleting   assets/index-uds7SAWG.css
*deleting   assets/index-rE_mlUUX.js
<f+++++++++ assets/index-HsgfTG-P.js
<f+++++++++ assets/index-TmHVWOg-.css
<f..T...... assets/ping-82822-md4el-YO.mp3
<f..T...... assets/wrong-47985-pJgIJXG3.mp3
<f..T...... images/escritorio.png
<f..T...... images/inventario.png
<f..T...... images/pos.png
<f..T...... images/pos_móvil.png

sent 496 bytes  received 111 bytes  173,43 bytes/sec
total size is 2.053.499  speedup is 3.383,03 (DRY RUN)

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ rsync -rviz --delete dist/ parzibyte@servidor-de-parzibyte.com:/home/parzibyte/spos4_prod/api/dist/
sending incremental file list
<f..T...... favicon.ico
<f..T...... index.html
<f..T...... logo.svg
<f..T...... manifest.json
*deleting   assets/index-uds7SAWG.css
*deleting   assets/index-rE_mlUUX.js
<f+++++++++ assets/index-HsgfTG-P.js
<f+++++++++ assets/index-TmHVWOg-.css
<f..T...... assets/ping-82822-md4el-YO.mp3
<f..T...... assets/wrong-47985-pJgIJXG3.mp3
<f..T...... images/escritorio.png
<f..T...... images/inventario.png
<f..T...... images/pos.png
<f..T...... images/pos_móvil.png

sent 282.460 bytes  received 9.431 bytes  53.071,09 bytes/sec
total size is 2.053.499  speedup is 7,04

parzibyte@LAPTOP-3T1ON1MO MSYS /c/Users/parzibyte/Documents/desarrollo/vue/spos4
$ 

Y no te preocupes, no hay necesidad de reiniciar el servicio ni el servidor cuando haces cambios del lado del cliente pues los archivos se sirven frescos

Si el post ha sido de tu agrado te invito a que me sigas para saber cuando haya escrito un nuevo post, haya actualizado algún sistema o publicado un nuevo software. Facebook | X | Instagram | Telegram | También estoy a tus órdenes para cualquier contratación en mi página de contacto