Detrás de escenas

Cómo se creó DataToForm — Historia y arquitectura técnica

Esta no es una historia de ronda de financiación ni de pivote estratégico. Es la historia de un problema mundano que vi repetirse demasiadas veces, y una serie de decisiones técnicas que tomé — a veces bien, a veces con retrospectiva.

El problema que nadie nombra

En un momento dado, observo a una coordinadora de RRHH pasando veinte minutos rellenando un formulario de incorporación en su SIRH. Tiene su hoja de cálculo Excel delante, copia "Martínez", pega, copia "12/03/1987", pega, copia el IBAN, pega. Para un solo empleado. Tiene doce que procesar esta mañana. Y mañana estarán los formularios del seguro de salud, los formularios de pensiones, los accesos informáticos...

Este problema no tiene nombre en las herramientas existentes. Zapier dice "conectaremos tus apps". Make dice "crearemos un flujo de trabajo". Las herramientas RPA dicen "programaremos un robot". Pero aquí hablamos de una persona con un archivo Excel y un formulario web sin API. Sin webhook. Sin conector. Un humano, un archivo, una URL.

La pregunta que me hago: ¿podemos automatizar el copiar-pegar sin eliminar al humano de la ecuación?

El primer prototipo: un script de Playwright

El primer código que escribo es un script de Playwright. Playwright es la biblioteca de Microsoft para controlar programáticamente un navegador Chromium — el mismo navegador que usaría un humano, no un simple analizador HTML. Esta es la primera decisión clave: no hacer scraping del DOM estático, sino abrir una ventana real, con el renderizado completo de JavaScript, los elementos dinámicos, los componentes React o Vue que se generan en el lado del cliente.

El script básico cabe en unas cincuenta líneas:

  1. Abrir la URL en Chromium
  2. Listar los elementos interactivos visibles (input, select, textarea)
  3. Para cada elemento, obtener su label, placeholder, name, id
  4. Mapear manualmente columna Excel → selector CSS
  5. Rellenar mediante page.fill(), page.selectOption(), page.check()

Funciona. En el formulario de prueba. Durante cinco minutos. Luego probamos otro formulario.

La complejidad oculta de los formularios web

La diversidad de los formularios web es un abismo. Lo que imaginamos como "un campo de texto" esconde en realidad decenas de variantes:

  • Los selects en cascada: seleccionar "España" en "País" muestra "Madrid" en "Comunidad" que muestra "Madrid" en "Provincia". Cada selección es un evento asíncrono que dispara una recarga parcial.
  • Los selectores de fecha personalizados: el input[type="date"] nativo a menudo se reemplaza por un componente JavaScript con su propio calendario. page.fill() no hace nada.
  • Los campos ocultos: algunos formularios SIRH ocultan campos hasta que el formulario está en un estado específico (pestaña activa, paso del asistente...).
  • Los selects disfrazados: elementos <div> que se comportan como <select>, con roles ARIA y navegación por teclado, pero sin ningún <option> en el DOM.
  • Los campos de autocompletado: escribir en un campo dispara una solicitud API, aparece un menú desplegable, hay que hacer clic en él.

El form-inspector.js que escanea formularios se ha vuelto progresivamente más sofisticado: filtra los elementos visibles (sin display:none, sin visibility:hidden, sin opacity:0), deduce el label asociado a un campo recorriendo el DOM hacia arriba (label[for], aria-label, aria-labelledby, texto del padre), puntúa cada campo detectado por su probabilidad de ser relevante.

La IA en todo esto — dónde, por qué y sobre todo cómo

La cuestión del mapeo columnas/campos es donde entra la IA. ¿Cómo saber que la columna "APELLIDOS" debe ir al campo "last_name", y que "Fecha nacimiento" corresponde a "Birthdate" en un formulario en inglés? Eso es lenguaje natural, semántica — un LLM está hecho para eso.

Pero hay una regla que establezco desde el principio, no negociable: la IA nunca ve los valores reales de tus datos. Solo recibe:

  • La lista de nombres de columnas del archivo Excel (["Nombre", "APELLIDOS", "Fecha_nacimiento", "IBAN", ...])
  • La lista de campos detectados en el formulario (["first_name", "last_name", "dob", ...] con sus labels)

Devuelve un JSON de mapeo. Eso es todo. Las 500 filas de tu archivo Excel nunca pasan por un servidor de IA.

La arquitectura LLM dual para Excel

Para archivos Excel complejos — hojas de cálculo no tabulares, celdas combinadas, títulos a mitad de tabla, datos distribuidos en múltiples pestañas — una sola llamada al LLM no es suficiente. Diseñé un pipeline en dos etapas:

  1. Primera llamada (extractExcelData): le damos al LLM el texto sin formato del Excel (unos cientos de caracteres, nunca los valores sensibles). Identifica qué filas son encabezados, dónde comienza la tabla de datos, qué columnas son relevantes. Devuelve un objeto estructurado.
  2. Segunda llamada (suggestMapping): con la lista limpia de encabezados extraída, sugerimos el mapeo a los campos del formulario.

Rate limiting compartido

Ambas llamadas al LLM comparten el mismo contador de rate limiting (checkRateLimit() en lib/llm.js). Por defecto: 20 solicitudes por minuto, por IP. No es una restricción arbitraria — es la realidad de las APIs LLM gratuitas o baratas.

La decisión arquitectónica más importante

Pronto tomo una decisión que algunos encuentran restrictiva: DataToForm nunca enviará un formulario. Sin parámetro opcional "auto-submit", sin modo sin supervisión. Por diseño.

La función fillCurrentPage() en generic-form-filler.js rellena los campos y se detiene. No acepta un selector de envío. Si miras la API /api/fill-current-row, no hay ningún parámetro submit en el esquema. No es un olvido — es deliberado.

¿Por qué? Tres razones:

  1. Responsabilidad legal. Una declaración fiscal incorrecta, un IBAN erróneo, un expediente de siniestro incompleto: las consecuencias de un envío automático pueden ser graves.
  2. La confianza se construye. Una herramienta que nunca envía sin tu aprobación es una herramienta que puedes probar sin ansiedad.
  3. Los agentes IA hacen lo contrario. Browser Use, Skyvern, Operator envían automáticamente. Su caso de uso es diferente al nuestro: datos masivos de Excel con revisión fila por fila.
Un LLM en modo agente puede alucinar un valor y enviarlo. DataToForm no puede alucinar un valor porque no razona durante el relleno — aplica mecánicamente tu mapeo validado.

La stack técnica y por qué estas decisiones

Node.js ESM, sin framework servidor pesado

El backend es Node.js con módulos ES nativos. Express para el enrutamiento HTTP. Multer para las subidas de archivos. Sin framework "baterías incluidas" — cada dependencia está ahí por una razón precisa.

Playwright Chromium, no Puppeteer

Playwright es más robusto que Puppeteer para el manejo de selectores, la espera de elementos y el soporte de iframes. Chromium headless en el servidor, pero con opción de abrir una ventana visible real cuando el usuario necesita iniciar sesión él mismo — ninguna credencial pasa por el servidor.

WebSocket para seguimiento en tiempo real

Analizar un formulario puede llevar de 5 a 15 segundos. El LLM puede tardar de 3 a 8 segundos. Enviamos eventos al cliente via WebSocket durante todo el proceso: "Abriendo navegador", "Analizando campos", "IA trabajando"...

JavaScript vanilla en el cliente — sin framework, sin bundler

El frontend es JavaScript puro. Sin React, sin Vue, sin Vite, sin Webpack. Un archivo app.js, un style.css, un index.html. Los módulos ES nativos del navegador reemplazan a los bundlers. Edita un archivo, recarga el navegador, ve el resultado.

Dónde estamos ahora

DataToForm está en beta abierta. La primera fila de cada documento se procesa gratis — esto es una restricción FREE_ROW_LIMIT = 1 en server.js, no un límite del lado del cliente que se pueda eludir.

Lo que no cambiará:

  • La IA nunca verá tus valores reales
  • El formulario nunca se enviará automáticamente
  • El archivo nunca se almacenará en el servidor

Pruébalo con tu primer archivo

Importa tu Excel, introduce la URL del formulario. DataToForm rellena la primera fila gratis.

Empezar gratis

← Volver al blog