Muchas aplicaciones VFP tienen un PC en un rincón con un exe abierto y un Timer dentro: importa los pedidos de la tienda web, manda los correos pendientes, hace el cierre de cada noche. Funciona mientras nadie lo cierre, nadie cierre la sesión y Windows no se reinicie por una actualización.
En este tutorial vamos a hacer pedidos, un servicio de Windows escrito en Visual FoxPro que hace ese trabajo sin nadie delante. Importa los pedidos que una tienda web deja en una carpeta, cuenta cada 30 segundos los que esperan, escribe un resumen cada noche y, cada mañana, manda los pendientes al almacén. Arranca con el equipo, sigue funcionando aunque nadie inicie sesión y se vuelve a levantar si se cae.
Casi todo el código lo trae ya el proyecto: FoxService crea cada servicio nuevo con este importador dentro, y funciona la primera vez que lo ejecutas. Lo recorremos, le añadimos un trabajo nuestro y lo instalamos.
Lo que necesitas
- Visual FoxPro 9 (o VFP Advanced 10.2 de 32 bits).
- FoxCli instalado. Trae FoxForge, el taller que se engancha al IDE de VFP, y FoxService. Sin licencia funciona igual; la diferencia la vemos al final.
- Windows 10 u 11. Si no tienes el runtime de .NET 10 de 32 bits, el instalador de FoxCli te ofrece descargarlo e instalarlo: el servicio lo necesita para arrancar.
Abre VFP como siempre, sin «Ejecutar como administrador». Al compilar como administrador, VFP registra el componente en HKLM, y ese registro estorba a las compilaciones siguientes. Instalar el servicio sí necesita permiso de administrador, pero ese permiso lo pide Windows en su momento, con su ventana de siempre.
Paso 1. Cargar FoxForge
En la ventana de comandos de VFP:
DO "C:\Program Files (x86)\FoxForge\FoxForge.app"Cambia la ruta si instalaste FoxForge en otra carpeta. Aparece un menú nuevo en la barra de VFP, FoxForge, y desde ahí se hace todo lo demás.
Paso 2. Crear el proyecto
FoxForge > Nuevo proyecto > Servicio de Windows (FoxService). En la ventana «Nuevo servicio de Windows» solo hacen falta dos cosas:
- Nombre del servicio:
pedidos. Es también el nombre del.exey de la DLL, y el que verás en Servicios de Windows. - Carpeta: la tuya de proyectos. Yo he usado
C:\a1\tutorial-svc.
Deja la cuenta de Windows que trae, «El sistema (LocalSystem)»: puede con todo en este equipo, como el programa del Timer al que sustituye. Las otras dos son para cuando el servicio tiene que llegar a una carpeta de red, y ¿Qué elijo? abre la guía, que explica cuándo conviene cada una. Lo que hay en Opciones avanzadas… trae un valor que sirve para empezar.

Pulsa Crear. FoxForge crea el proyecto, lo abre en el Administrador de Proyectos y te recibe con una ventana que dice qué es un servicio y por dónde empezar: Primeros pasos abre la guía y Abrir main.prg, el único fichero que vas a tocar.

pedidos\
├── pedidos.pjx el proyecto de VFP
├── foxforge.json la marca de proyecto de servicio
├── foxservice.json la configuración del servicio
├── src\
│ ├── main.prg tus trabajos: lo único que editas
│ ├── foxservice.prg la clase base, que es de FoxService
│ ├── config.fpw viaja dentro de la DLL
│ └── orders.frx el informe del ejemplo
└── dist\ aquí sale el servicio al compilar
├── data\ los datos
└── inbox\ la carpeta que vigila el ejemplo, con un CSV de dos pedidosPaso 3. Verlo funcionar
Antes de leer una línea de código, pruébalo. Ctrl+F7 compila (FoxForge > Proyecto actual > Compilar servicio) y deja el servicio en dist\. Sin licencia de FoxCli, el aviso de «Servicio compilado» añade que sale en evaluación.
Ctrl+F8 (Ejecutar en consola) lo arranca en una consola, con su log en pantalla, sin instalar nada:
INFO job PrepareData: orders.dbf created
INFO job PrepareData: orders.dbf ready: 0 order(s)
INFO job ImportOrders: orders_0001.csv: 2 new, 0 repeated
INFO job CountPending: Pending orders: 2
El servicio ha creado la tabla orders.dbf, ha importado el CSV que esperaba en inbox\ y lo ha movido a inbox\processed\. A los 30 segundos cuenta los pedidos pendientes, y después cada 30 segundos más. Ctrl+C lo para.
Paso 4. Lo que trae main.prg
Abre src\main.prg. Debajo de un comentario largo que explica las anotaciones está la clase; sin el cuerpo de los métodos, es esto:
DEFINE CLASS PedidosService AS FoxServiceBase OF foxservice.prg OLEPUBLIC
*!* @startup
PROCEDURE PrepareData && al arrancar: la tabla, si no existe
*!* @every 30s
PROCEDURE CountPending && cada 30 segundos: los pendientes
*!* @watch inbox *.csv
PROCEDURE ImportOrders(tcPath, tcChange) && cada CSV que llega a inbox\
*!* @daily 23:55
PROCEDURE DailySummary && cada noche: el resumen del día
PROCEDURE RebuildSummary && sin línea *!*: solo cuando lo lanzas tú
PROTECTED PROCEDURE OpenOrders && auxiliar: abre la tabla y la selecciona
PROTECTED PROCEDURE WriteSummary && auxiliar: lo que hacen los dos resúmenes
ENDDEFINECon esto ya se ve cómo funciona todo:
- Cada método público de la clase es un trabajo, y la línea
*!*de encima dice cuándo corre: cada cierto tiempo (@every), a una hora (@daily), al arrancar (@startup) o cuando llega un fichero a una carpeta (@watch). Sin línea*!*, el trabajo no corre solo: lo lanzas tú. - Lo que no es un trabajo va
PROTECTED PROCEDURE.OpenOrdersabre la tabla, la crea con su índice la primera vez y la deja seleccionada; todos los trabajos empiezan por ella, así que da igual cuál corra primero. - Las rutas salen de
THIS.Path("data"), la carpetadatadefoxservice.json, y nunca deSET DEFAULT: el directorio actual es del proceso entero. THIS.Log()escribe en el log del servicio, el mismo que has visto en la consola.- No hay
TRY ... CATCH. Un error de VFP llega solo al log, con su número y su línea, el trabajo cuenta como fallido y el servicio sigue.
Los Primeros pasos de la guía (FoxForge > Ayuda > Guía de FoxService) recorren el código de cada trabajo línea a línea: por qué el CSV pasa por STRCONV(), por qué la fecha va con EVALUATE("{^...}") y cómo el SEEK evita importar dos veces el mismo pedido.
Paso 5. Probarlo a mano
Con el servicio otra vez en la consola (Ctrl+F8), copia el CSV de dist\inbox\processed\ a dist\inbox\. El log dice:
INFO job ImportOrders: orders_0001_20261012143307.csv: 0 new, 2 repeatedEl trabajo es idempotente: los dos pedidos ya estaban, y el SEEK por id se los salta. El fichero vuelve a processed\, con la hora en el nombre para no pisar el anterior.
El resumen corre a las 23:55, pero no hace falta esperar. FoxForge > Proyecto actual > Ejecutar un trabajo ahora… enseña los trabajos con cuándo corre cada uno, los manuales primero:

Elige RebuildSummary y pulsa Ejecutar. Si el servicio está en marcha, el trabajo corre dentro de él; si no, en una consola que se queda abierta con el resultado. En dist\data\ aparece summary_20261012.txt:
Orders: 2, total: 173.40Y si algo falla y quieres verlo por dentro, Proyecto actual > Banco de pruebas… ejecuta un trabajo desde el fuente, sin compilar, con el depurador de VFP delante: un SET STEP ON funciona.
Paso 6. Añadir un trabajo: el almacén
Ahora uno nuestro. Cada mañana, el almacén quiere un CSV con los pedidos pendientes, y esos pedidos tienen que quedar marcados como enviados para no mandarlos otra vez. Añade este método a la clase, debajo de RebuildSummary:
*-- Every morning: the pending orders, to a CSV for the warehouse,
*-- and marked as sent.
*!* @daily 07:00
PROCEDURE SendToWarehouse
LOCAL lnPending, lcFile
THIS.OpenOrders()
COUNT FOR status == "P" TO lnPending
IF lnPending = 0
THIS.Log("Nothing to send to the warehouse")
RETURN
ENDIF
lcFile = THIS.Path("data") + "warehouse_" + TTOC(DATETIME(), 1) + ".csv"
COPY TO (lcFile) FIELDS id, customer, amount FOR status == "P" TYPE CSV
REPLACE ALL status WITH "S" FOR status == "P"
THIS.Log(TRANSFORM(lnPending) + " order(s) sent to the warehouse: " + lcFile)
ENDPROCLos nombres y los comentarios van en inglés, como el resto del ejemplo; en tus servicios, pon los tuyos.
@daily 07:00va en formato de 24 horas, a la hora del equipo donde corre el servicio. Un@daily 7:00 AMno llega al servidor: Ctrl+F7 para la compilación y enseña la línea,main.prg(134): error: @daily needs a local time HH:MM from 00:00 to 23:59.- Empieza por
OpenOrders(), como los demás: la tabla abierta y seleccionada, así queCOUNTyREPLACEtrabajan sobre ella. - La hora va en el nombre del fichero (
TTOC(DATETIME(), 1)) porqueCOPY TOsobrescribe sin preguntar, aunque tengasSET SAFETY ON. Con la fecha sola, una segunda ejecución el mismo día pisaría el fichero de la mañana. COPY TO ... TYPE CSVescribe en Windows-1252, como la tabla: «Ferretería» sale con su tilde.
Ctrl+F7, y sin esperar a las siete: Ejecutar un trabajo ahora… > SendToWarehouse. El log dice:
INFO job SendToWarehouse: 2 order(s) sent to the warehouse: C:\a1\tutorial-svc\pedidos\dist\data\warehouse_20261012143501.csvid,customer,amount
1001,"Ferretería Ruiz",125.40
1002,"Bar Manolo",48.00Ejecútalo otra vez: «Nothing to send to the warehouse». Los dos pedidos ya están marcados, y el contador de pendientes dirá 0 en su próximo turno.
Paso 7. Instalarlo como servicio de Windows
FoxForge > Proyecto actual > Servicio de Windows… enseña el estado del servicio y los botones para manejarlo. Pulsa Instalar: Windows pide permiso de administrador, y el servicio queda instalado y en marcha. Desde ahora arranca solo al encender el equipo, aunque nadie inicie sesión, y Windows lo vuelve a arrancar si se cae.

Debajo de los botones, la ventana enseña lo que dice status (la cuenta, el arranque automático, qué hace Windows si se cae) y, más abajo, una fila por trabajo: cuántas veces ha corrido, cuántas ha fallado o se ha saltado, y cuándo arrancó por última vez. Copia un CSV a dist\inbox\ y míralo en el log, ya sin consola: está en dist\logs\pedidos_20261012.log, uno por día.
Para compilar otra vez, páralo antes con Parar: Windows no deja sustituir los ficheros de un servicio en marcha, y si lo olvidas, Ctrl+F7 te lo recuerda. Compila, y Arrancar.
Paso 8. Llevarlo a un servidor
La carpeta dist\ lleva todo lo que el servicio necesita, el runtime de VFP incluido. En el servidor no hace falta Visual FoxPro ni licencia; solo Windows con el runtime de .NET 10 de 32 bits. Copia dist\ entera a un disco local del servidor y, en una consola de administrador en esa carpeta:
pedidos.exe install --startLo instala con la cuenta de foxservice.json (la del sistema, la que elegiste al crearlo) y lo arranca. pedidos.exe status dice cómo está, y pedidos.exe uninstall lo quita, dejando los logs. Si en el servidor el servicio tiene que llegar a una carpeta de red, la guía explica qué cuenta usar.
Paso 9. La licencia
Sin licencia, el servicio funciona igual: todos los trabajos corren, sin límites y sin fecha de caducidad. Lo único que cambia es que lo dice una vez al arrancar, con una línea en su log y una entrada en el Registro de eventos:
WARN Built with FoxCli (evaluation): this service runs every job, without limits. A FoxCli license seals it: https://irwinrodriguez.dev/foxcliCon una licencia de FoxCli cargada en FoxForge > Licencia de FoxCli…, Ctrl+F7 sella el servicio con tu nombre: esa línea desaparece, y el log y status dicen a quién está licenciado. La licencia es del programador que compila: los servidores donde instalas tus servicios no necesitan ninguna.
Y ahora qué
- La guía de FoxService está en FoxForge > Ayuda > Guía de FoxService: qué puede vivir en un trabajo y qué no, los timeouts y la cuarentena, las cuentas de Windows,
@watcha fondo, los informes en PDF y VFP Advanced. - Con un agente: FoxStack le da a Claude los comandos de
foxservicecomo herramientas, para crear, compilar e instalar servicios desde una conversación. - Ideas para seguir con
pedidos: queSendToWarehousemande el CSV por correo en vez de dejarlo endata\, o un segundo@watchpara una carpeta de devoluciones.