Manual funcional & notas de versión

Agente de Consultas en Lenguaje Natural

Traduce preguntas en español a consultas sobre una base de datos, sin que el modelo de lenguaje llegue a ver los datos: solo el esquema. Cada consulta que se ejecuta queda auditable de punta a punta, y cada persona ve únicamente lo que su rol permite.

Versión 2.0.0 Node.js 18+ Express · SQLite Sin dependencias de front

Qué es

Una aplicación web multiusuario que recibe una pregunta escrita en lenguaje natural —"pedidos pendientes de clientes corporativos en Paraguay", "total facturado por país"— y devuelve la respuesta consultada contra una base de datos real, mostrando además el razonamiento intermedio: el texto exacto que se envió al modelo, el plan de consulta que produjo y el SQL que finalmente se ejecutó. Los resultados se pueden exportar a Excel, CSV y PDF, y la conversación admite repreguntas.

Está pensada para casos donde la información es sensible y no puede exponerse a un servicio de inteligencia artificial externo. El argumento central es una separación estricta de responsabilidades: el modelo de lenguaje interpreta la intención; un componente propio y auditable arma y ejecuta la consulta. El modelo nunca redacta SQL ni recibe una sola fila de datos.

El conjunto de datos incluido es 100 % sintético (90 registros de pedidos genéricos, generados con semilla determinística) y no corresponde a ninguna organización real.

Cómo funciona

Cada consulta atraviesa cinco etapas. Las etapas 4 y 5 corren enteramente en el servidor de la aplicación; el modelo de lenguaje solo participa en la etapa 3.

Pregunta

La persona inicia sesión y escribe en español en el navegador.

Prompt

El backend arma un prompt con solo el esquema de los campos que el rol de esa persona puede consultar (más las últimas consultas de la conversación, si hay) y lo envía al modelo.

Plan

El modelo responde un plan en JSON: filtros, agrupación, agregación, orden, límite.

Validación → SQL

El plan se valida campo por campo contra la whitelist del rol y se traduce a SQL parametrizado.

Ejecución

El SQL corre contra la base en solo lectura. Vuelven resultado, columnas con su tipo, criterios aplicados, prompt, plan, SQL y el modelo usado.

La respuesta al navegador incluye el prompt exacto, el plan JSON y la sentencia SQL con sus parámetros. No hay ningún paso opaco entre la pregunta y el resultado, y cada consulta queda registrada en la auditoría.

Modelo de seguridad

La garantía no descansa en que el modelo "se porte bien". El modelo elige de un vocabulario cerrado, y un validador determinista comprueba cada elemento del plan antes de construir la consulta.

El modelo solo puede elegir de

  • Los nombres de campo del esquema, exactamente como están listados.
  • Un conjunto fijo de operadores: eq, neq, contains, gt, gte, lt, lte, in, between.
  • Tres agregaciones: count, sum, avg.
  • Agrupación por uno o varios campos, y por período mediante buckets de fecha fijos (mes, trimestre, semestre, anio, dia) cuyo formato sale de un mapa interno, no del modelo. Estos mismos buckets también se pueden usar como campo de un filtro (no solo agrupar) — es lo que permite comparar un mismo período del calendario entre años (ej. "Q1 2026 vs Q1 2025") sin un rango de fechas continuo que traiga de más los meses intermedios.
  • Un filtro sobre el valor agregado (having), orden, límite y selección de columnas.

Se rechaza explícitamente

  • Cualquier campo, operador o bucket fuera de la whitelist — la consulta devuelve error 422 y no se ejecuta.
  • Subconsultas, referencias a tablas del sistema, operadores inventados, rangos, having o groupBy mal formados.
  • Una consulta sin ningún criterio (sin filtros, sin agrupación/agregación y sin límite): la herramienta responde preguntas puntuales, no exporta la tabla entera.
  • SQL escrito por el modelo: nunca se acepta; el modelo no produce SQL.

Cómo se construye la sentencia

  • Los valores viajan siempre como parámetros ligados (?), nunca concatenados en el texto de la consulta.
  • Los identificadores (campos, alias de agregación, límite) se validan contra la whitelist y se sanean antes de interpolarse.
  • El SELECT es siempre una lista explícita de campos de la whitelist, nunca *: una consulta no puede devolver columnas fuera del esquema ni campos que el rol no ve.
  • La conexión a la base de datos es de solo lectura. Aunque un plan lograra pasar la validación, la base no puede modificarse en tiempo de ejecución.
  • El modelo recibe únicamente las instrucciones y el esquema (nombres de campo y valores válidos). Ninguna fila de datos entra en el prompt.

Acceso y permisos

  • Toda la API exige sesión (usuario y contraseña, cookie httpOnly). Las contraseñas se guardan con hash scrypt; las temporales hay que cambiarlas al ingresar.
  • Hay límite de intentos de inicio de sesión y de consultas por minuto, y protección contra solicitudes desde otros orígenes.
  • Cada rol define permisos y qué campos puede consultar. El modelo ni siquiera conoce los campos que el rol no puede ver, y el validador del rol rechaza cualquier otro.
  • La auditoría registra cada consulta (pregunta, plan, SQL, cantidad de filas; nunca las filas), cada exportación y cada cambio administrativo.
  • Las filas de datos no se guardan en el navegador ni en la auditoría.

El proyecto incluye pruebas automáticas —sin llamar al modelo— del traductor de plan a SQL con intentos de inyección deliberados, del login, los permisos, los roles con campos limitados, la configuración editable, las exportaciones, las repreguntas y la operación. Se corren con npm test.

Esquema de datos y configuración

El esquema, los textos y las reglas de interpretación no están en el código: forman la configuración del cliente, que se edita desde el panel de Administración (sección 9). El conjunto de datos incluido es una sola tabla desnormalizada, pedidos, con 12 campos: 90 pedidos sintéticos de una empresa ficticia de software, hardware y servicios de TI en el Cono Sur, repartidos a lo largo de aproximadamente un año. Los campos de tipo enumerado solo admiten los valores listados.

CampoNombre visibleTipoDescripción / valores
pedido_idN.º de pedidonúmero (código)Identificador del pedido.
fechaFechafecha ISOFormato AAAA-MM-DD.
clienteClientetextoNombre del cliente.
paisPaísenumParaguay · Argentina · Uruguay · Chile
segmentoSegmentoenumCorporativo · PyME · Individual
categoriaCategoríaenumSoftware · Hardware · Consultoría · Soporte · Licencias
productoProductotextoNombre del producto o servicio.
cantidadCantidadnúmero (entero)Unidades pedidas.
precio_unitarioPrecio unitarionúmero (moneda)Precio unitario en guaraníes (Gs.).
totalTotal facturadonúmero (moneda)cantidad × precio_unitario.
estadoEstadoenumPendiente · Completado · Cancelado · En proceso
vendedorVendedorenumLaura Gómez · Marcos Ibáñez · Noelia Duarte · Ramón Silva

Configuración de cada campo (editable desde el panel):

  • Nombre técnico: el que usan el modelo y el validador (total).
  • Nombre visible: lo que ve la gente en la tabla, el Excel y el PDF ("Total facturado"). Si no se define, se arma a partir del nombre técnico.
  • Tipo: número, fecha, texto o lista de valores.
  • Formato (solo números): entero (1.234), decimal (1.234,56), moneda (1.234 Gs.) o sin separadores (códigos e identificadores). El símbolo y los decimales de la moneda se definen una vez para todo el esquema.
  • Columna real: solo si en la base se llama distinto del nombre técnico.
  • Descripción (o los valores posibles, en las listas): es lo que recibe el modelo.

La configuración se guarda por versiones: cada cambio queda registrado con quién y cuándo, se valida antes de aplicarse (las columnas deben existir de verdad, los ejemplos deben ser planes válidos) y se puede restaurar una versión anterior.

Vocabulario de consulta

El modelo devuelve un plan: un objeto JSON con las claves de abajo. Todas son opcionales salvo que la pregunta lo requiera.

Filtros

Lista de condiciones { campo, op, valor }, combinadas con AND.

OperadorSignificadoValor
eq · neqIgual · distintoUn valor.
gt · gte · lt · lteMayor / menor (o igual)Un valor numérico o fecha.
containsSubcadena (texto)Un fragmento de texto.
inPertenece a un conjuntoLista de valores no vacía.
betweenRango inclusivoLista [mín, máx], numérica o de fechas.

Agrupación y agregación

  • groupBy: un campo, o una lista de campos/dimensiones para agrupar por varias a la vez ("ventas por vendedor y por mes" → ["vendedor", "mes"]).
  • Para agrupar por período de tiempo hay buckets de fecha: mes (año-mes), trimestre (año-trimestre, ej. 2026-Q4), semestre (año-semestre, ej. 2026-S2), anio, dia. Operan sobre el campo de fecha del esquema.
  • aggregate: { campo, op, alias }, con op en count / sum / avg.
  • Sin groupBy, la agregación es un único número sobre toda la tabla filtrada — p. ej. "total facturado en Paraguay".
  • having: filtra sobre el resultado de la agregación (eq/neq/gt/gte/lt/lte/between). Solo válido junto con groupBy + aggregate.

Las columnas del resultado se rotulan solas: un total sale como "Suma de Total facturado", un promedio como "Promedio de Cantidad" y un conteo como "Cantidad".

Orden, límite y selección

  • sort: { campo, direction } con asc / desc. Con groupBy, el campo de orden debe ser el alias de la agregación o alguna de las dimensiones/buckets del groupBy.
  • limit: entero positivo.
  • select: lista de campos a mostrar. No admite el alias de una agregación (el resultado ya sale con ese nombre).

Consultas sin criterio

Un plan sin filtros, sin agrupación/agregación y sin límite equivaldría a volcar toda la tabla; se rechaza con 422 y un mensaje que pide reformular. Alcanza con cualquier criterio —un filtro, una agrupación o un límite— para que la consulta se ejecute.

Fechas

Se almacenan como texto ISO AAAA-MM-DD. Los rangos relativos —"el último mes", "el último trimestre", "en julio"— se resuelven contra una fecha de referencia y se expresan con between sobre el campo de fecha. La respuesta muestra siempre los criterios aplicados (p. ej. "Fecha entre 05/07/2026 y 05/10/2026"), armados por el sistema sin intervención del modelo.

Ejemplo de plan

// "clientes cuya facturación total está entre 20.000 y 40.000"
{
  "groupBy": "cliente",
  "aggregate": { "field": "total", "op": "sum", "alias": "total_cliente" },
  "having": { "op": "between", "value": [20000, 40000] },
  "sort": { "field": "total_cliente", "direction": "desc" }
}

Traducido a:

SELECT cliente, SUM(total) AS total_cliente
FROM pedidos
GROUP BY cliente
HAVING SUM(total) BETWEEN ? AND ?
ORDER BY total_cliente DESC
-- params: [20000, 40000]

Conversaciones y repreguntas

La pantalla organiza el trabajo en conversaciones: cada una agrupa las consultas relacionadas. En la barra lateral se ven las conversaciones y, dentro de cada una, sus consultas; al abrir una conversación se muestran sus resultados otra vez.

Con cada pregunta nueva, el sistema envía al modelo las últimas consultas de la conversación (la pregunta y el plan que se ejecutó, nunca los datos). Eso permite:

  • Refinar la consulta anterior: "y solo Paraguay", "ahora el mes pasado". El modelo devuelve un plan completo que combina lo anterior con el cambio, y se valida igual que cualquier otro.
  • Preguntar sobre la consulta anterior: "indicá el período", "qué filtros usaste". El modelo responde con un texto explicativo a partir del plan; no se ejecuta SQL ni se accede a datos.
  • Preguntar sobre la herramienta misma (solo con más de un dataset/dominio configurado): "qué tipos de consultas puedo hacerte", "qué dominios tenés disponibles". La respuesta es una tabla real con cada dominio y su descripción — armada por el servidor desde su propia configuración (nunca por el modelo), sin necesitar conversación previa y sin ejecutar ninguna consulta.
  • Reusar una pregunta anterior: tocar el globo de una pregunta ya hecha (la tuya, en la conversación actual) carga ese mismo texto en el cuadro de consulta — para repetirla tal cual o editarla antes de volver a mandarla. No la reenvía sola.
  • Cambiar de tema: si la pregunta no se relaciona con lo anterior, se trata como nueva.

El historial se guarda en el navegador por usuario y solo conserva las preguntas y los planes, nunca filas de datos. Al reabrir una conversación, cada plan se vuelve a ejecutar (sin pasar por el modelo) con los permisos que el rol tiene en ese momento y con datos actualizados.

Exportación

Cada resultado puede descargarse en cuatro formatos, si el rol tiene el permiso Exportar resultados:

FormatoContenido
Excel (.xlsx)Hoja de resultados con encabezados legibles, filtros y primera fila fija; una segunda hoja con la pregunta, quién la hizo y cuándo.
CSVEstándar: coma como separador, punto decimal, fechas AAAA-MM-DD, UTF-8.
CSV para Excel (es)Punto y coma como separador y coma decimal, para abrirlo directo en Excel en español.
PDFDocumento con título, pregunta, fecha, tabla con encabezado repetido en cada página y numeración.

Los tipos de dato se respetan para poder usar los datos sin convertir nada: en Excel los montos son números con formato de miles y moneda (se pueden sumar, filtrar y graficar), las fechas son fechas reales (dd/mm/aaaa), la agrupación por mes es una fecha con formato mm/aaaa, y los códigos van sin separador de miles. Los textos que empiezan con =, +, - o @ se marcan como texto para evitar fórmulas ejecutables.

La exportación vuelve a ejecutar la consulta con los permisos del usuario, sin el tope de la pantalla: hasta 50.000 filas en Excel y CSV y 2.000 en PDF (configurables). Si el resultado se corta, se avisa. Cada exportación queda en la auditoría.

Envío de reportes por e-mail

Además de descargar un resultado, se puede pedirlo por chat — una vez o de forma recurrente — si el rol tiene el permiso Pedir envío de reportes por e-mail. El pedido se hace en lenguaje natural, junto con la pregunta: "enviame las ventas del mes a mi e-mail", "mandame esto todos los lunes a las 8", "el reporte de cobros en PDF y Excel, todos los días a las 7".

Una vez vs. recurrente

  • Sin ninguna palabra de recurrencia en el pedido, el envío es único y se manda de inmediato — nunca se pregunta "¿una vez o recurrente?" antes, para no trabar un pedido simple.
  • Después de un envío único exitoso, el asistente ofrece programarlo de forma recurrente como un mensaje aparte en el chat ("¿Querés que también te llegue de forma recurrente? Pedímelo, por ejemplo: «todos los lunes a las 8»").
  • Para un envío recurrente (diario, semanal o mensual) hace falta la hora (y el día de la semana o del mes, según corresponda); si falta un dato, el asistente lo pregunta como parte normal de la conversación, no como un error.

Formato del adjunto

Se puede pedir Excel, CSV o PDF — uno o varios a la vez en el mismo correo (ej. "mandame el Excel y el PDF"). Sin indicar nada, el formato por defecto es Excel. El adjunto es el mismo archivo que se descarga desde Exportación, con el gráfico embebido cuando corresponde, y sin el tope de filas de la pantalla.

A quién llega

Siempre al e-mail registrado de quien pregunta — nunca a otro destinatario. Sin un e-mail cargado en su usuario, el pedido se rechaza con un aviso explícito (pedile a un administrador que lo cargue).

Gráfico embebido

Si la consulta en pantalla tenía un gráfico de comparación, el adjunto (Excel o PDF) lo incluye como imagen, con el mismo tipo (barras o líneas) que estaba activo en pantalla en ese momento. Un pedido hecho por chat sin haber navegado antes, o uno recurrente (que se genera solo, sin nadie mirando la pantalla), usa barras por defecto.

Mis reportes programados

Con el permiso de reportes aparece "Mis reportes" en la barra lateral: lista cada envío recurrente activo, cuándo es (día y hora), cuándo fue el último envío (y si falló, por qué) y un botón para cancelarlo. Hasta 20 envíos recurrentes activos por persona.

Por qué puede fallar o desactivarse solo

  • SMTP no configurado en el servidor (variables SMTP_* del entorno, ver sección 12): un envío único avisa al momento; uno recurrente queda marcado con el error en su última corrida, sin desactivarse — reintenta en el próximo horario.
  • Falla real de envío (credenciales, servidor SMTP caído, destinatario rechazado): mismo comportamiento — se avisa al pedirlo una vez, o queda registrado en la última corrida si es recurrente.
  • Un envío recurrente se desactiva solo (no vuelve a reintentarse) si, al llegar su horario, a quien lo pidió le sacaron el permiso de reportes, cambió de rol, ya no tiene e-mail cargado, o su usuario fue dado de baja. Queda visible en "Mis reportes" como pausado, con el motivo.

Todo envío —pedido una vez, programado o fallido— queda en la auditoría.

Acceso, roles y administración

La primera vez que se inicia, el servidor crea el usuario admin y muestra su contraseña temporal una sola vez en la consola (o usa la que se defina en el entorno); hay que cambiarla al ingresar.

Roles y permisos

Un rol agrupa permisos y define qué campos puede consultar (todos o una lista). Los cambios valen de inmediato, incluso para quienes ya están conectados.

PermisoPermite
Hacer consultasPreguntar y ver resultados.
Exportar resultadosDescargar Excel, CSV y PDF.
Pedir envío de reportes por e-mailPedir por chat el envío de un resultado al propio e-mail, una vez o de forma recurrente (ver Envío de reportes por e-mail).
Ver la configuraciónVer la descripción y los campos del esquema (los de su rol).
Editar esquema, textos y conexiónModificar la configuración del cliente.
Configurar la API key y el modeloCargar o cambiar la API key de Anthropic y el modelo usado para traducir preguntas.
Gestionar usuarios y rolesAltas, bajas, roles y reinicio de contraseñas. Equivale a administrador.
Ver la auditoríaConsultar el registro de actividad.

Vienen tres roles de fábrica: Administrador (todos los permisos, no se puede modificar), Editor y Usuario. El administrador crea los que necesite, por ejemplo uno "Ventas" que consulta pero no ve los campos de costos.

Panel de Administración

Se abre desde la barra lateral y solo muestra las secciones que el rol permite:

  • Usuarios — altas con contraseña temporal (se muestra una sola vez), cambio de rol, desactivación (corta la sesión al instante), reinicio de contraseña y baja.
  • Roles y permisos — alta y edición de roles con sus permisos y campos visibles.
  • Esquema y campos — nombre visible, nombre técnico, tipo, formato, columna real y descripción de cada campo; moneda; campo de fecha. Avisa si una columna no existe e importa las que faltan.
  • Textos y ejemplos — título, preguntas de ejemplo, reglas de interpretación y ejemplos de traducción para el modelo.
  • Conexión — archivo de la base y tabla. Solo se aceptan bases dentro de la carpeta de datos permitida y siempre se abren en solo lectura; las credenciales no se escriben en el panel.
  • Versiones — historial de cambios de la configuración y restauración.
  • Auditoría — consultas, exportaciones, inicios de sesión y cambios, con filtros.

Salvaguardas: nunca queda el sistema sin un administrador activo, no se puede desactivar ni eliminar el propio usuario, y las configuraciones inválidas se rechazan con un mensaje claro sin cambiar nada.

API HTTP

El navegador nunca habla con el proveedor de inteligencia artificial: todas las llamadas al modelo pasan por el backend, y la clave de API vive solo en el servidor. Toda la API, salvo el chequeo de salud y el inicio de sesión, exige sesión.

EndpointDescripción
POST /api/auth/login · POST /api/auth/logoutInicio y cierre de sesión.
GET /api/auth/me · POST /api/auth/passwordUsuario actual · cambio de contraseña propia.
POST /api/queryTraduce una pregunta (con el historial opcional) y la ejecuta.
POST /api/query/replayVuelve a ejecutar un plan guardado, sin pasar por el modelo.
POST /api/exportExporta el resultado de un plan: xlsx, csv, csv-es o pdf.
GET /api/configModelo activo, cliente, textos de la interfaz y moneda.
GET /api/schemaDescripción del dataset restringida a los campos del rol.
/api/admin/users · /roles · /config · /auditAdministración (cada grupo exige su permiso).
GET /api/healthChequeo de salud (200 o 503), sin sesión y sin detalles internos.
GET /manual.htmlEste manual.
POST/api/query

Petición

{
  "question": "pedidos del último mes",
  "history": [ { "q": "pregunta anterior", "plan": { "...": "plan que se ejecutó" } } ]
}

history es opcional (hasta 4 consultas; se descarta lo mal formado y todo plan que el rol ya no pueda ejecutar).

Respuesta correcta (200)

{
  "prompt":  "…texto exacto enviado al modelo…",
  "plan":    { "...": "el plan JSON" },
  "sql":     "SELECT pedido_id, fecha, … FROM pedidos WHERE fecha BETWEEN ? AND ?",
  "params":  ["2026-08-08", "2026-09-08"],
  "columns": [ { "key": "total", "label": "Total facturado", "type": "number", "format": "currency" } ],
  "summary": ["Fecha entre 08/08/2026 y 08/09/2026"],
  "rows":    [ "...filas resultantes..." ],
  "truncated": false,
  "model":   "identificador del modelo que respondió"
}

Si la pregunta es sobre la consulta anterior, la respuesta es { prompt, message, plan: null } con el texto explicativo, sin SQL ni filas.

Códigos de estado

400
Falta la pregunta o es demasiado larga.
401
Sin sesión.
403
Sin permiso, o contraseña temporal pendiente de cambiar.
422
El plan no pasó la validación: campo/operador/bucket fuera de la whitelist (o fuera de los campos del rol), rango mal formado, having sin agrupación, o consulta sin ningún criterio.
429
Demasiados intentos o consultas seguidas.
500
Error interno o del proveedor del modelo de lenguaje.

Las respuestas de error de consulta incluyen { error, prompt }, de modo que siempre puede verse qué se envió al modelo aunque la consulta no llegue a ejecutarse.

Interfaz

Una sola aplicación, sin build ni framework de front, con tema claro y oscuro (sigue al sistema y se puede cambiar; se recuerda la elección).

  • Inicio de sesión — pantalla de acceso; si la contraseña es temporal, pide cambiarla antes de continuar. La primera vez que una persona entra (nunca de nuevo después, se recuerda por persona) ve una bienvenida breve: qué hace la herramienta, los dominios disponibles (si hay más de uno) y preguntas de ejemplo para arrancar.
  • Barra lateral — Nueva conversación; las conversaciones con sus consultas anidadas (clic para abrir o saltar a una consulta; la papelera elimina); el usuario conectado y su rol; el cliente y el modelo activos; Configuración, Mis reportes (solo con el permiso de reportes), Administración (según permisos), Cambiar contraseña, tema y manual. En pantallas chicas se convierte en un menú desplegable.
  • Área de conversación — al empezar, saludo y ejemplos clicables (definidos en la configuración). Cada respuesta muestra: los criterios aplicados, la tabla de resultados con encabezados legibles y cada valor según su tipo (montos con moneda, fechas dd/mm/aaaa, mes mm/aaaa, estados resaltados), con una grilla con líneas verticales y horizontales y filas alternadas (como una planilla de cálculo) — esta grilla tiene siempre fondo claro, sin importar si el resto de la interfaz está en tema claro u oscuro; los botones de exportación; y un bloque desplegable "Cómo se obtuvo esta respuesta" con el prompt enviado, el plan JSON y el SQL ejecutado, cada uno con botón de copiar. Cuando el resultado compara pocos períodos entre sí (ej. "ventas por marca, Q1 vs Q2"), debajo de la tabla (cruzada, un período por columna) se dibuja además un gráfico — barras agrupadas por defecto, con pestañas para cambiar a líneas — con leyenda y un tooltip al pasar el mouse (o con el teclado) que muestra el valor de todos los períodos de esa categoría a la vez. Una comparación más simple, sin ninguna categoría de por medio (ej. "ventas totales 2025 vs 2026"), también dibuja un gráfico debajo de la tabla de siempre — ahí de una sola serie, sin pestañas ni leyenda (no hacen falta con un solo valor por período).
  • Configuración — vista de solo lectura con el cliente, el modelo, la tabla, la cantidad de registros, el rango de fechas y los campos (con su nombre visible) que el rol puede consultar.
  • Administración — panel descripto en la sección 9.

Mostrar el prompt, el plan y el SQL de cada respuesta convierte cada consulta en algo revisable: se puede demostrar exactamente qué recibió el modelo y qué se ejecutó.

Configuración y ejecución

Requisitos: Node.js 18 o superior (probado con 22 y 24). Una clave de API del proveedor del modelo de lenguaje.

Variables de entorno (.env)

VariableRolPor defecto
Clave de APICredencial del proveedor del modelo. Solo en el servidor.— (obligatoria)
Identificador de modeloQué modelo usar para traducir la pregunta al plan.valor económico por defecto
PuertoPuerto HTTP del servidor.3000
Cliente (CLIENT)Qué configuración de cliente usar.demo
Carpeta de datos (DATA_DIR)Dónde se guarda la base de la aplicación.data
Carpeta de bases (DB_ROOT)Fuera de ella el panel no puede apuntar una base.db
Administrador inicial (ADMIN_USERNAME, ADMIN_PASSWORD)Usuario y clave del primer arranque.admin / se genera una temporal
COOKIE_SECURE, TRUST_PROXYCookies solo por HTTPS y confianza en el proxy.desactivados
MAX_ROWSFilas máximas por consulta en pantalla.1000
EXPORT_MAX_ROWS, PDF_MAX_ROWSFilas máximas al exportar.50000 / 2000
BACKUP_KEEP, BACKUP_EVERY_HOURS, BACKUP_DIRCopias de seguridad automáticas.14 / 24 / data/backups
AUDIT_RETENTION_DAYSDías de auditoría que se conservan (0 = para siempre).0
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROMServidor de correo para el envío de reportes por e-mail. Sin cargarlas, ese envío queda deshabilitado.— (opcional)

Comandos

ComandoEfecto
npm run seedCrea o reinicia la base SQLite desde el dataset sintético.
npm startLevanta el servidor.
npm run devIgual, con recarga automática del servidor al guardar cambios.
npm run backupCopia de seguridad consistente de la base de la aplicación.
npm testCorre todas las pruebas automáticas, sin llamar al modelo.

Operación

La base de la aplicación (data/app.db) guarda usuarios, roles, sesiones, versiones de la configuración y auditoría; los datos del negocio viven aparte y se abren en solo lectura.

  • Es adecuada para una sola instancia con disco local y persistente (en contenedores, data debe ser un volumen).
  • Se crea con permisos restrictivos. Conviene cifrar el disco del servidor y servir la aplicación detrás de HTTPS.
  • El servidor hace una copia de seguridad consistente al arrancar y cada 24 horas (14 copias, verificadas); npm run backup hace una a mano. Las copias quedan en el mismo disco: hay que llevarlas fuera de la máquina.
  • GET /api/health sirve para monitoreo.
  • Límites a tener en cuenta: sin alta disponibilidad, el límite de intentos vive en memoria y la auditoría no es inmutable. Para escalar a varias instancias habría que pasar la base de la aplicación a un motor cliente-servidor.

El modelo por defecto se eligió tras comparar la calidad de la traducción entre un modelo más capaz y uno más económico sobre un lote de preguntas representativas: para esta tarea —acotada y validada por whitelist— el modelo económico rinde a la par a aproximadamente un tercio del costo por consulta.

---

Notas de la versión 2.0.0

2.0.0

acceso y roles · panel de administración · exportación · repreguntas

Acceso y seguridad

  • Inicio de sesión con usuarios locales, contraseñas con hash, sesiones por cookie httpOnly, cambio obligatorio de contraseñas temporales, límite de intentos y protección contra solicitudes de otros orígenes. Toda la API exige sesión.
  • Roles y permisos configurables: permisos por función y campos consultables por rol. El modelo no conoce los campos que el rol no puede ver y el validador los rechaza.
  • Auditoría persistente de consultas, exportaciones, accesos y cambios administrativos (sin filas de datos).
  • Corrección: una consulta sin columnas explícitas ya no puede devolver campos fuera de los permitidos al rol (el SELECT es siempre una lista explícita de campos).

Panel de Administración

  • Gestión de usuarios y roles, edición del esquema, los textos y la conexión, historial de versiones con restauración, y visor de auditoría.
  • La configuración del cliente ahora vive versionada en la base de la aplicación, se valida antes de aplicarse y se aplica sin reiniciar. Un cliente nuevo se configura sin tocar código.

Consultas

  • Repreguntas: la conversación aporta contexto al modelo; se puede refinar la consulta anterior o preguntar sobre ella (por ejemplo, el período).
  • Criterios aplicados visibles en cada respuesta.
  • Nombres visibles de campo y de columnas calculadas ("Total facturado", "Suma de…"), y formatos numéricos (entero, decimal, moneda, código).

Exportación

  • Descarga a Excel, CSV (estándar y para Excel en español) y PDF respetando los tipos de dato: montos como números con formato, fechas como fechas reales.
  • Permiso propio, tope de filas configurable y registro en la auditoría.

Interfaz

  • Rediseño tipo chat: barra lateral con conversaciones y consultas anidadas, historial por usuario, tema claro/oscuro y adaptación a pantallas chicas.
  • La auditoría de cada respuesta (prompt, plan, SQL) pasa a un bloque desplegable con botones de copiar.

Operación

  • Copias de seguridad automáticas y manuales con rotación, retención opcional de la auditoría, permisos restrictivos de la carpeta de datos y chequeo de salud.
  • Nuevas variables de entorno y pruebas automáticas para acceso, exportación, repreguntas y operación.

1.1.0

rango de consulta ampliado · interfaz auditable · modelo configurable

Nuevas capacidades de consulta

  • Operador between para rangos numéricos y de fechas, en filtros y en having.
  • Agregaciones globales sin agrupación: "total facturado en Paraguay" devuelve un solo número en lugar de fallar.
  • Filtro having sobre el resultado de una agregación ("clientes cuyo total supera X").
  • groupBy por varias dimensiones a la vez, y buckets de fecha (mes, trimestre, semestre, anio, dia) para "por vendedor y por mes", "total facturado por mes", etc.
  • Rangos de fecha relativos: "el último mes", "en julio" se resuelven contra la fecha de referencia.
  • Regla de interpretación: "el que más compró / vendió" se entiende por monto, no por unidades, salvo mención explícita.
  • Las consultas sin ningún criterio ("mostrame toda la base") se rechazan en lugar de volcar la tabla entera.

Interfaz

  • Panel nuevo: "Prompt enviado al modelo", junto a los de plan y SQL. Hace verificable que el modelo solo recibió el esquema.
  • El modelo activo se muestra en el encabezado; enlace al manual.
  • Panel colapsable "Sobre la base de datos": descripción, cantidad de registros, rango de fechas y cada campo con sus valores válidos.

Operación

  • Modelo de lenguaje configurable por variable de entorno, con un valor por defecto más económico validado por comparación.
  • Endpoints nuevos: GET /api/config, GET /api/schema, GET /manual.html.
  • Modo desarrollo con recarga automática del servidor (npm run dev).
  • Conexión a la base de datos en modo solo lectura.

Robustez

  • Más casos de prueba de inyección: rangos, having y groupBy mal formados, agregación escalar con campos u operadores falsos, buckets inventados.
  • El backend devuelve el prompt también en las respuestas de error, y el modelo usado en las respuestas correctas.

1.0.0

base
  • Traducción pregunta → plan → SQL parametrizado con validación por whitelist de campos y operadores (eq, neq, contains, gt, gte, lt, lte, in).
  • Agregaciones count / sum / avg con agrupación; orden y límite.
  • Interfaz con paneles de plan y SQL; dataset sintético de 90 registros.