@ericrisco/rsc 3.7.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/manifest.json +49 -40
- package/package.json +1 -1
- package/scripts/lib/domains.js +1 -1
- package/scripts/lib/onboarding-apply.js +1 -1
- package/scripts/lib/onboarding.js +7 -7
- package/scripts/lib/retired-skills.js +4 -0
- package/scripts/lib/review-findings.js +3 -0
- package/scripts/lib/review-ledger.js +185 -0
- package/scripts/lib/risk.js +65 -0
- package/scripts/lib/spec-gate.js +10 -1
- package/scripts/rsc.js +91 -6
- package/skills/analyze/SKILL.md +3 -3
- package/skills/asdd/SKILL.md +159 -0
- package/skills/{sdd → asdd}/evals/README.md +10 -10
- package/skills/{sdd → asdd}/evals/cases.yaml +4 -4
- package/skills/{sdd → asdd}/references/model-routing.md +2 -2
- package/skills/building-agents/SKILL.md +1 -1
- package/skills/clarify/SKILL.md +48 -55
- package/skills/clarify/evals/cases.yaml +6 -7
- package/skills/connect-tool/SKILL.md +119 -21
- package/skills/connect-tool/THIRD_PARTY.md +43 -0
- package/skills/connect-tool/bridge-kit/01-TOOLS-plantilla/test_connection.sh +33 -0
- package/skills/connect-tool/bridge-kit/LICENSE +27 -0
- package/skills/connect-tool/bridge-kit/README.md +48 -0
- package/skills/connect-tool/bridge-kit/UPSTREAM.json +8 -0
- package/skills/connect-tool/bridge-kit/access/access_a_sqlite.py +82 -0
- package/skills/connect-tool/bridge-kit/dbhub/dbhub.toml.ejemplo +65 -0
- package/skills/connect-tool/bridge-kit/red/cloudflared-config.yml.ejemplo +18 -0
- package/skills/connect-tool/bridge-kit/red/tailscale-grants.hujson +22 -0
- package/skills/connect-tool/bridge-kit/sqlserver/01-login-servidor.sql +25 -0
- package/skills/connect-tool/bridge-kit/sqlserver/02-usuario-y-vistas.sql +69 -0
- package/skills/connect-tool/bridge-kit/sqlserver/03-prueba-solo-lectura.sql +65 -0
- package/skills/connect-tool/bridge-kit/sqlserver/copia-nocturna.ps1 +68 -0
- package/skills/connect-tool/evals/cases.yaml +41 -4
- package/skills/connect-tool/references/connect-or-migrate.md +96 -0
- package/skills/connect-tool/references/database-bridge.md +159 -0
- package/skills/connect-tool/references/file-exchange.md +103 -0
- package/skills/connect-tool/references/messages.md +120 -0
- package/skills/connect-tool/references/program-types.md +97 -0
- package/skills/connect-tool/references/spain.md +103 -0
- package/skills/connect-tool/references/what-to-find-out.md +57 -0
- package/skills/constitution/SKILL.md +21 -8
- package/skills/constitution/evals/README.md +1 -1
- package/skills/debug/SKILL.md +1 -1
- package/skills/fastapi/SKILL.md +1 -1
- package/skills/flutter/SKILL.md +1 -1
- package/skills/ftd/SKILL.md +26 -10
- package/skills/ftd/evals/cases.yaml +1 -1
- package/skills/ftd/evals/routing.json +4 -4
- package/skills/go/SKILL.md +1 -1
- package/skills/implement/SKILL.md +126 -113
- package/skills/implement/evals/cases.yaml +4 -2
- package/skills/implement/references/per-task-review.md +19 -26
- package/skills/nextjs/SKILL.md +1 -1
- package/skills/parallel/SKILL.md +1 -1
- package/skills/plan/SKILL.md +2 -2
- package/skills/postgresdb/SKILL.md +1 -1
- package/skills/react/SKILL.md +1 -1
- package/skills/review/SKILL.md +58 -31
- package/skills/sdd-init/SKILL.md +6 -6
- package/skills/ship/SKILL.md +38 -18
- package/skills/specify/SKILL.md +32 -33
- package/skills/specify/evals/cases.yaml +1 -1
- package/skills/specify/references/spec-template.md +27 -5
- package/skills/suggest/SKILL.md +4 -4
- package/skills/tasks/SKILL.md +37 -33
- package/skills/tasks/evals/README.md +1 -1
- package/skills/tasks/evals/cases.yaml +2 -2
- package/skills/verify/SKILL.md +30 -15
- package/skills/verify/evals/README.md +2 -2
- package/skills/verify/evals/cases.yaml +1 -0
- package/skills/worktrees/SKILL.md +1 -1
- package/targets/clone-bootstrap.mjs +1 -1
- package/targets/hook-once.mjs +2 -2
- package/targets/sello.mjs +1 -1
- package/skills/sdd/SKILL.md +0 -250
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Kit puente: leer un programa de escritorio en solo lectura
|
|
2
|
+
|
|
3
|
+
Para que un agente de IA **lea** los datos de un programa instalado en la oficina (Sage 50 o 200, Contasol en local, ICG, Prevengos, CONTPAQi, a3ERP…) **sin poder escribir**, **sin abrir puertos** y **sin perder el soporte** del fabricante.
|
|
4
|
+
|
|
5
|
+
- Explicación para el dueño: [Mi programa no tiene API](https://executive-lab.github.io/conectores-pymes/guias/mi-programa-no-tiene-api.html).
|
|
6
|
+
- Explicación técnica: [Kit puente, parte técnica](https://executive-lab.github.io/conectores-pymes/guias/kit-puente-tecnico.html).
|
|
7
|
+
- Para **escribir** (asientos, facturas) no se usa el puente: se usa el [fichero de importación oficial](https://executive-lab.github.io/conectores-pymes/guias/escribir-en-contabilidad-por-importacion.html), que importa una persona.
|
|
8
|
+
|
|
9
|
+
> Plantillas genéricas. Los nombres de tablas y columnas de cada programa **no** están aquí: dependen de la versión y hay que confirmarlos con el distribuidor. Prueba todo primero en una copia.
|
|
10
|
+
|
|
11
|
+
## Qué hay
|
|
12
|
+
|
|
13
|
+
| Carpeta | Para qué |
|
|
14
|
+
|---------|----------|
|
|
15
|
+
| `sqlserver/01-login-servidor.sql` | Login `ia_lectura` (una vez, nivel servidor) |
|
|
16
|
+
| `sqlserver/02-usuario-y-vistas.sql` | Usuario, esquema `ia`, permisos de solo lectura y vistas de plantilla (idempotente) |
|
|
17
|
+
| `sqlserver/03-prueba-solo-lectura.sql` | Prueba de aceptación: lee las vistas, **no** lee tablas, **no** borra, **no** modifica, **no** crea |
|
|
18
|
+
| `sqlserver/copia-nocturna.ps1` | Backup `COPY_ONLY` → restauración en `ERP_IA` → vuelve a crear usuario y vistas → pasa la prueba |
|
|
19
|
+
| `access/access_a_sqlite.py` | Para programas en Access: copia el fichero y lo convierte a SQLite |
|
|
20
|
+
| `dbhub/dbhub.toml.ejemplo` | MCP de solo lectura con herramientas de SQL predefinido |
|
|
21
|
+
| `red/tailscale-grants.hujson` | Si el agente es Claude Code en el portátil |
|
|
22
|
+
| `red/cloudflared-config.yml.ejemplo` | Si el agente es un conector de claude.ai, Cowork o n8n en la nube |
|
|
23
|
+
| `01-TOOLS-plantilla/` | Carpeta para el arnés (`.env.example` y `test_connection.sh`) |
|
|
24
|
+
|
|
25
|
+
## Pasos
|
|
26
|
+
|
|
27
|
+
1. **Permiso por escrito** del distribuidor para crear un login y vistas, o para leer la copia.
|
|
28
|
+
2. **SQL Server:** ejecuta `01-login-servidor.sql` una vez. Ajusta las vistas de `02-usuario-y-vistas.sql` a tu programa.
|
|
29
|
+
3. **Copia nocturna** (recomendada): programa `copia-nocturna.ps1` en el Programador de tareas de Windows, fuera de horario, con una cuenta administradora de SQL Server:
|
|
30
|
+
```powershell
|
|
31
|
+
$accion = New-ScheduledTaskAction -Execute "powershell.exe" -Argument '-ExecutionPolicy Bypass -File C:\KitPuente\sqlserver\copia-nocturna.ps1 -Origen "<BD_DEL_PROGRAMA>"'
|
|
32
|
+
$cuando = New-ScheduledTaskTrigger -Daily -At 2am
|
|
33
|
+
Register-ScheduledTask -TaskName "KitPuente copia nocturna" -Action $accion -Trigger $cuando -RunLevel Highest
|
|
34
|
+
```
|
|
35
|
+
Sin copia (leyendo producción): ejecuta `02` y `03` directamente sobre la base del programa, si el distribuidor lo autoriza.
|
|
36
|
+
4. **Access:** `python access_a_sqlite.py --origen <fichero> --destino C:\KitPuente\empresa.sqlite`, programado igual.
|
|
37
|
+
5. **Prueba de aceptación:** `03-prueba-solo-lectura.sql` tiene que terminar en `RESULTADO: OK`. Si no, **no se conecta el agente**.
|
|
38
|
+
6. **DBHub** en el servidor, con `dbhub.toml` protegido. Fija la versión de npm. DBHub escucha en `0.0.0.0` por defecto: indica **siempre** `--host`.
|
|
39
|
+
7. **Red:** Tailscale (agente local) o Cloudflare Tunnel + Access (conectores de claude.ai o nube). **Nunca** abras el 1433 ni el 8080 en el router.
|
|
40
|
+
8. **Arnés:** copia `01-TOOLS-plantilla/` a `01-TOOLS/<PROGRAMA>/`, rellena `.env` y ejecuta `./test_connection.sh`. Añade el MCP a `.mcp.json`.
|
|
41
|
+
|
|
42
|
+
## Lo que el kit no hace
|
|
43
|
+
|
|
44
|
+
- No escribe en el programa: para eso está el fichero de importación.
|
|
45
|
+
- No incluye esquemas de tablas de programas comerciales.
|
|
46
|
+
- No sustituye al soporte del fabricante: si el programa se actualiza y cambia sus tablas, hay que revisar las vistas. La prueba 03 y el `test_connection` lo detectan.
|
|
47
|
+
|
|
48
|
+
Alternativa de pago si no quieres mantener el puente: una API unificada como Chift, que cubre Sage 200 ES y a3ERP con agente local, además de Contasol Nube, Ágora y Revo (ver la guía técnica).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Kit puente · Copia una base de Access (.mdb/.accdb) y la convierte a SQLite para leerla en solo lectura.
|
|
3
|
+
|
|
4
|
+
Para programas que guardan los datos en Access (p. ej. Factusol en local). Nunca se conecta
|
|
5
|
+
nada al fichero vivo: primero se COPIA y se trabaja sobre la copia, fuera de horario.
|
|
6
|
+
|
|
7
|
+
Requisitos (Windows): Python 3.11+, `pip install pyodbc` y el controlador
|
|
8
|
+
"Microsoft Access Driver (*.mdb, *.accdb)" (Access Database Engine) de la misma
|
|
9
|
+
arquitectura que Python (64 bits con 64 bits).
|
|
10
|
+
|
|
11
|
+
Después, sirve el .sqlite con DBHub (ver ../dbhub/dbhub.toml.ejemplo, fuente sqlite).
|
|
12
|
+
|
|
13
|
+
Ejemplos:
|
|
14
|
+
python access_a_sqlite.py --origen "D:\\Programa\\Datos\\EMPRESA.accdb" --destino C:\\KitPuente\\empresa.sqlite
|
|
15
|
+
python access_a_sqlite.py --origen ... --destino ... --tablas F_FAC,F_LFA,F_CLI
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
import datetime
|
|
19
|
+
import decimal
|
|
20
|
+
import shutil
|
|
21
|
+
import sqlite3
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def convertir(valor):
|
|
28
|
+
if isinstance(valor, decimal.Decimal):
|
|
29
|
+
return float(valor)
|
|
30
|
+
if isinstance(valor, (datetime.date, datetime.datetime, datetime.time)):
|
|
31
|
+
return valor.isoformat()
|
|
32
|
+
if isinstance(valor, (bytes, bytearray, memoryview)):
|
|
33
|
+
return None # adjuntos o binarios: no se copian
|
|
34
|
+
return valor
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
39
|
+
parser.add_argument("--origen", required=True, type=Path, help="Fichero .mdb o .accdb del programa")
|
|
40
|
+
parser.add_argument("--destino", required=True, type=Path, help="Fichero .sqlite que se genera (se sustituye)")
|
|
41
|
+
parser.add_argument("--tablas", default="", help="Lista de tablas separadas por comas (por defecto, todas)")
|
|
42
|
+
options = parser.parse_args()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
import pyodbc
|
|
46
|
+
except ImportError:
|
|
47
|
+
sys.exit("ERROR: falta pyodbc (pip install pyodbc) y el controlador de Access")
|
|
48
|
+
|
|
49
|
+
if not options.origen.exists():
|
|
50
|
+
sys.exit(f"ERROR: no existe {options.origen}")
|
|
51
|
+
permitidas = {t.strip().lower() for t in options.tablas.split(",") if t.strip()}
|
|
52
|
+
|
|
53
|
+
with tempfile.TemporaryDirectory() as carpeta:
|
|
54
|
+
copia = Path(carpeta) / options.origen.name
|
|
55
|
+
shutil.copy2(options.origen, copia) # trabajamos SIEMPRE sobre una copia
|
|
56
|
+
origen = pyodbc.connect(rf"DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};DBQ={copia};")
|
|
57
|
+
cursor = origen.cursor()
|
|
58
|
+
tablas = [t.table_name for t in cursor.tables(tableType="TABLE")]
|
|
59
|
+
if permitidas:
|
|
60
|
+
tablas = [t for t in tablas if t.lower() in permitidas]
|
|
61
|
+
|
|
62
|
+
temporal = options.destino.with_suffix(".tmp.sqlite")
|
|
63
|
+
temporal.unlink(missing_ok=True)
|
|
64
|
+
destino = sqlite3.connect(temporal)
|
|
65
|
+
for tabla in tablas:
|
|
66
|
+
columnas = [c.column_name for c in cursor.columns(table=tabla)]
|
|
67
|
+
lista = ", ".join(f'"{c}"' for c in columnas)
|
|
68
|
+
destino.execute(f'CREATE TABLE "{tabla}" ({lista})')
|
|
69
|
+
filas = 0
|
|
70
|
+
for fila in cursor.execute(f"SELECT * FROM [{tabla}]"):
|
|
71
|
+
destino.execute(f'INSERT INTO "{tabla}" VALUES ({", ".join("?" * len(columnas))})', [convertir(v) for v in fila])
|
|
72
|
+
filas += 1
|
|
73
|
+
print(f" {tabla}: {filas} filas")
|
|
74
|
+
destino.commit()
|
|
75
|
+
destino.close()
|
|
76
|
+
origen.close()
|
|
77
|
+
temporal.replace(options.destino) # sustitución atómica: el lector nunca ve un fichero a medias
|
|
78
|
+
print(f"OK — {len(tablas)} tablas en {options.destino}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Kit puente · Configuración de DBHub (https://github.com/bytebase/dbhub, MIT).
|
|
2
|
+
#
|
|
3
|
+
# Este fichero lleva la contraseña de ia_lectura: guárdalo en el SERVIDOR DEL ERP, en una
|
|
4
|
+
# carpeta que solo pueda leer la cuenta que ejecuta DBHub. NUNCA en git ni en el portátil.
|
|
5
|
+
#
|
|
6
|
+
# Arranque (DBHub escucha por defecto en 0.0.0.0: indica SIEMPRE --host):
|
|
7
|
+
# set DBHUB_AUTH_TOKEN=<token largo y aleatorio>
|
|
8
|
+
# npx @bytebase/dbhub@<versión fija> --config=C:\KitPuente\dbhub.toml --transport http --host 127.0.0.1 --port 8080
|
|
9
|
+
# Con Tailscale (sin Cloudflare): --host <IP de Tailscale del servidor> y en el firewall de Windows
|
|
10
|
+
# permite el 8080 solo desde la interfaz de Tailscale (ver ../red/).
|
|
11
|
+
#
|
|
12
|
+
# Marcadores de parámetros en SQL Server: @p1, @p2… (en SQLite: ?).
|
|
13
|
+
|
|
14
|
+
# --- Origen: la COPIA nocturna (ver ../sqlserver/copia-nocturna.ps1) --------------------
|
|
15
|
+
[[sources]]
|
|
16
|
+
id = "erp_ia"
|
|
17
|
+
type = "sqlserver"
|
|
18
|
+
host = "127.0.0.1"
|
|
19
|
+
port = 1433
|
|
20
|
+
database = "ERP_IA"
|
|
21
|
+
user = "ia_lectura"
|
|
22
|
+
password = "<<CONTRASEÑA DE ia_lectura>>"
|
|
23
|
+
|
|
24
|
+
# Alternativa para Access convertido a SQLite (ver ../access/access_a_sqlite.py):
|
|
25
|
+
# [[sources]]
|
|
26
|
+
# id = "erp_ia"
|
|
27
|
+
# dsn = "sqlite:///C:/KitPuente/empresa.sqlite"
|
|
28
|
+
|
|
29
|
+
# --- execute_sql: la documentación no explica cómo quitarlo, así que se deja en solo
|
|
30
|
+
# lectura y con tope de filas. La garantía real es el login ia_lectura, que solo puede
|
|
31
|
+
# hacer SELECT sobre el esquema ia (pasa la prueba 03 antes de conectar nada).
|
|
32
|
+
[[tools]]
|
|
33
|
+
name = "execute_sql"
|
|
34
|
+
source = "erp_ia"
|
|
35
|
+
readonly = true
|
|
36
|
+
max_rows = 200
|
|
37
|
+
|
|
38
|
+
# --- Herramientas de SQL predefinido: una por pregunta de negocio ------------------------
|
|
39
|
+
[[tools]]
|
|
40
|
+
name = "ventas_por_dia"
|
|
41
|
+
description = "Base imponible y número de facturas emitidas por día entre dos fechas (AAAA-MM-DD)"
|
|
42
|
+
source = "erp_ia"
|
|
43
|
+
statement = "SELECT fecha, base_imponible, facturas FROM ia.ventas_por_dia WHERE fecha BETWEEN @p1 AND @p2 ORDER BY fecha"
|
|
44
|
+
|
|
45
|
+
[[tools.parameters]]
|
|
46
|
+
name = "desde"
|
|
47
|
+
type = "string"
|
|
48
|
+
description = "Fecha inicial, AAAA-MM-DD"
|
|
49
|
+
|
|
50
|
+
[[tools.parameters]]
|
|
51
|
+
name = "hasta"
|
|
52
|
+
type = "string"
|
|
53
|
+
description = "Fecha final, AAAA-MM-DD"
|
|
54
|
+
|
|
55
|
+
[[tools]]
|
|
56
|
+
name = "cobros_pendientes"
|
|
57
|
+
description = "Vencimientos de clientes con importe pendiente, del más antiguo al más reciente"
|
|
58
|
+
source = "erp_ia"
|
|
59
|
+
statement = "SELECT TOP (200) cliente, nombre, vencimiento, pendiente FROM ia.cobros_pendientes ORDER BY vencimiento"
|
|
60
|
+
|
|
61
|
+
[[tools]]
|
|
62
|
+
name = "stock_bajo_minimos"
|
|
63
|
+
description = "Artículos con stock por debajo del mínimo"
|
|
64
|
+
source = "erp_ia"
|
|
65
|
+
statement = "SELECT TOP (200) articulo, descripcion, stock, minimo FROM ia.stock_bajo_minimos ORDER BY stock - minimo"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Kit puente · Cloudflare Tunnel delante de DBHub (C:\ProgramData\cloudflared\config.yml).
|
|
2
|
+
# Cuándo: el agente es un conector remoto de claude.ai o Cowork, o n8n en la nube. Esos
|
|
3
|
+
# conectores salen de la infraestructura del proveedor y NO llegan a una VPN ni a Tailscale.
|
|
4
|
+
#
|
|
5
|
+
# 1. cloudflared tunnel login → cloudflared tunnel create kit-puente (genera el JSON de credenciales)
|
|
6
|
+
# 2. Crea en Cloudflare Zero Trust una aplicación de Access "self-hosted" para el hostname de abajo:
|
|
7
|
+
# política por email de las personas autorizadas y service token para máquinas (n8n).
|
|
8
|
+
# 3. DBHub escucha SOLO en 127.0.0.1:8080 y con --auth-token (doble barrera: Access + token).
|
|
9
|
+
# 4. cloudflared service install (queda como servicio de Windows, conexión saliente, sin abrir puertos)
|
|
10
|
+
# Requisito: un dominio gestionado en Cloudflare. Verifica el plan y el soporte de OAuth para
|
|
11
|
+
# MCP en Access antes de conectar claude.ai.
|
|
12
|
+
tunnel: <ID_DEL_TUNEL>
|
|
13
|
+
credentials-file: C:\ProgramData\cloudflared\<ID_DEL_TUNEL>.json
|
|
14
|
+
|
|
15
|
+
ingress:
|
|
16
|
+
- hostname: erp-mcp.<tudominio>.com
|
|
17
|
+
service: http://127.0.0.1:8080
|
|
18
|
+
- service: http_status:404
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Kit puente · Política de Tailscale (Admin console → Access controls).
|
|
2
|
+
// Cuándo: el agente es Claude Code en el portátil del dueño (los conectores remotos de
|
|
3
|
+
// claude.ai NO llegan a Tailscale: para eso, ver cloudflared-config.yml.ejemplo).
|
|
4
|
+
//
|
|
5
|
+
// 1. En el servidor del ERP: instala Tailscale e inícialo con la etiqueta tag:erp
|
|
6
|
+
// (tailscale up --advertise-tags=tag:erp).
|
|
7
|
+
// 2. Arranca DBHub con --host <IP de Tailscale del servidor> --port 8080.
|
|
8
|
+
// 3. Firewall de Windows: el 8080 solo desde la interfaz de Tailscale. El 1433 nunca sale del servidor.
|
|
9
|
+
// Plan: Standard (uso comercial). El plan Personal es solo para uso no comercial.
|
|
10
|
+
{
|
|
11
|
+
"tagOwners": {
|
|
12
|
+
"tag:erp": ["autogroup:admin"]
|
|
13
|
+
},
|
|
14
|
+
"grants": [
|
|
15
|
+
{
|
|
16
|
+
// Solo el dueño (o un grupo) llega al servidor del ERP, y solo al puerto del MCP.
|
|
17
|
+
"src": ["<email-del-dueño@empresa.com>"],
|
|
18
|
+
"dst": ["tag:erp"],
|
|
19
|
+
"ip": ["tcp:8080"]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kit puente · 01 · Login de solo lectura (nivel de SERVIDOR). Se ejecuta UNA vez.
|
|
3
|
+
|
|
4
|
+
Quién: el informático, con un usuario administrador de SQL Server.
|
|
5
|
+
Antes: confirma con el distribuidor del programa (Sage, ICG, Prevengos, CONTPAQi,
|
|
6
|
+
Wolters Kluwer…) que crear un login y vistas no afecta al soporte. Pídelo por escrito.
|
|
7
|
+
|
|
8
|
+
Requisito: el servidor debe admitir autenticación de SQL Server (modo mixto).
|
|
9
|
+
Si solo admite Windows, usa una cuenta de Windows dedicada en lugar de este login.
|
|
10
|
+
|
|
11
|
+
La contraseña NO se guarda en ningún repositorio: genérala larga y aleatoria y
|
|
12
|
+
guárdala solo en el dbhub.toml protegido del servidor (ver ../dbhub/).
|
|
13
|
+
*/
|
|
14
|
+
USE [master];
|
|
15
|
+
GO
|
|
16
|
+
|
|
17
|
+
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'ia_lectura')
|
|
18
|
+
CREATE LOGIN [ia_lectura]
|
|
19
|
+
WITH PASSWORD = N'<<PON AQUÍ UNA CONTRASEÑA LARGA Y ALEATORIA>>',
|
|
20
|
+
CHECK_POLICY = ON,
|
|
21
|
+
DEFAULT_DATABASE = [master];
|
|
22
|
+
GO
|
|
23
|
+
|
|
24
|
+
-- Nada de roles de servidor: ia_lectura NO es sysadmin, ni securityadmin, ni nada más.
|
|
25
|
+
-- Los permisos se dan en la base de datos con 02-usuario-y-vistas.sql.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kit puente · 02 · Usuario, esquema "ia" y vistas (nivel de BASE DE DATOS).
|
|
3
|
+
|
|
4
|
+
Se ejecuta en la base que lee el agente. Por defecto es la COPIA "ERP_IA".
|
|
5
|
+
Es IDEMPOTENTE: copia-nocturna.ps1 lo vuelve a lanzar después de cada restauración,
|
|
6
|
+
porque restaurar la copia borra el usuario, el esquema y las vistas.
|
|
7
|
+
|
|
8
|
+
Las vistas son PLANTILLAS: los nombres de tablas y columnas dependen del programa
|
|
9
|
+
y de su versión. Complétalos con el distribuidor o con la documentación del esquema.
|
|
10
|
+
Regla: solo las columnas necesarias. Sin IBAN, sin datos de salud, sin nóminas.
|
|
11
|
+
*/
|
|
12
|
+
-- Cambia el nombre si no usas la copia:
|
|
13
|
+
USE [ERP_IA];
|
|
14
|
+
GO
|
|
15
|
+
|
|
16
|
+
IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = N'ia')
|
|
17
|
+
EXEC (N'CREATE SCHEMA [ia] AUTHORIZATION [dbo]');
|
|
18
|
+
GO
|
|
19
|
+
|
|
20
|
+
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'ia_lectura')
|
|
21
|
+
CREATE USER [ia_lectura] FOR LOGIN [ia_lectura] WITH DEFAULT_SCHEMA = [ia];
|
|
22
|
+
GO
|
|
23
|
+
|
|
24
|
+
-- Solo lectura sobre el esquema de vistas. Las vistas son de dbo (dueño del esquema),
|
|
25
|
+
-- así que la cadena de propiedad deja leer las tablas a través de ellas sin dar
|
|
26
|
+
-- permiso directo sobre las tablas.
|
|
27
|
+
GRANT SELECT ON SCHEMA::[ia] TO [ia_lectura];
|
|
28
|
+
ALTER ROLE [db_denydatawriter] ADD MEMBER [ia_lectura];
|
|
29
|
+
DENY CREATE TABLE TO [ia_lectura];
|
|
30
|
+
DENY CREATE VIEW TO [ia_lectura];
|
|
31
|
+
DENY CREATE PROCEDURE TO [ia_lectura];
|
|
32
|
+
DENY CREATE FUNCTION TO [ia_lectura];
|
|
33
|
+
GO
|
|
34
|
+
|
|
35
|
+
/* ----------------------------------------------------------------------------
|
|
36
|
+
VISTAS (plantillas). Sustituye <…> por las tablas y columnas reales.
|
|
37
|
+
Una vista por "pregunta de negocio". DBHub expone cada una como herramienta.
|
|
38
|
+
---------------------------------------------------------------------------- */
|
|
39
|
+
|
|
40
|
+
CREATE OR ALTER VIEW [ia].[ventas_por_dia] AS
|
|
41
|
+
SELECT
|
|
42
|
+
CAST(f.<columna_fecha> AS date) AS fecha,
|
|
43
|
+
SUM(f.<columna_base_imponible>) AS base_imponible,
|
|
44
|
+
COUNT(*) AS facturas
|
|
45
|
+
FROM [dbo].[<tabla_facturas_emitidas>] AS f
|
|
46
|
+
GROUP BY CAST(f.<columna_fecha> AS date);
|
|
47
|
+
GO
|
|
48
|
+
|
|
49
|
+
CREATE OR ALTER VIEW [ia].[cobros_pendientes] AS
|
|
50
|
+
SELECT
|
|
51
|
+
c.<columna_codigo_cliente> AS cliente,
|
|
52
|
+
c.<columna_nombre_cliente> AS nombre,
|
|
53
|
+
v.<columna_vencimiento> AS vencimiento,
|
|
54
|
+
v.<columna_importe_pendiente> AS pendiente
|
|
55
|
+
FROM [dbo].[<tabla_vencimientos>] AS v
|
|
56
|
+
JOIN [dbo].[<tabla_clientes>] AS c ON c.<columna_codigo_cliente> = v.<columna_codigo_cliente>
|
|
57
|
+
WHERE v.<columna_importe_pendiente> > 0;
|
|
58
|
+
GO
|
|
59
|
+
|
|
60
|
+
CREATE OR ALTER VIEW [ia].[stock_bajo_minimos] AS
|
|
61
|
+
SELECT
|
|
62
|
+
a.<columna_codigo_articulo> AS articulo,
|
|
63
|
+
a.<columna_descripcion> AS descripcion,
|
|
64
|
+
s.<columna_stock> AS stock,
|
|
65
|
+
a.<columna_stock_minimo> AS minimo
|
|
66
|
+
FROM [dbo].[<tabla_articulos>] AS a
|
|
67
|
+
JOIN [dbo].[<tabla_stock>] AS s ON s.<columna_codigo_articulo> = a.<columna_codigo_articulo>
|
|
68
|
+
WHERE s.<columna_stock> < a.<columna_stock_minimo>;
|
|
69
|
+
GO
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kit puente · 03 · Prueba de aceptación: ia_lectura LEE y NO ESCRIBE.
|
|
3
|
+
|
|
4
|
+
Sin esta prueba el puente no se entrega. Se ejecuta como administrador; el script
|
|
5
|
+
se hace pasar por ia_lectura (EXECUTE AS) y todo va dentro de una transacción
|
|
6
|
+
que se deshace al final, así que no cambia nada aunque algo fallara.
|
|
7
|
+
|
|
8
|
+
Sustituye <tabla_facturas_emitidas> por una tabla real del programa.
|
|
9
|
+
DELETE/UPDATE con TOP (0) no tocan ninguna fila, pero SQL Server comprueba igualmente
|
|
10
|
+
el permiso: si ia_lectura lo tuviera, la prueba lo detecta.
|
|
11
|
+
*/
|
|
12
|
+
USE [ERP_IA];
|
|
13
|
+
GO
|
|
14
|
+
|
|
15
|
+
BEGIN TRANSACTION;
|
|
16
|
+
EXECUTE AS USER = N'ia_lectura';
|
|
17
|
+
|
|
18
|
+
DECLARE @fallos int = 0;
|
|
19
|
+
|
|
20
|
+
BEGIN TRY
|
|
21
|
+
SELECT TOP (1) * FROM [ia].[ventas_por_dia];
|
|
22
|
+
PRINT N'OK · puede leer las vistas del esquema ia';
|
|
23
|
+
END TRY
|
|
24
|
+
BEGIN CATCH
|
|
25
|
+
SET @fallos += 1; PRINT N'FALLO · no puede leer ia.ventas_por_dia: ' + ERROR_MESSAGE();
|
|
26
|
+
END CATCH;
|
|
27
|
+
|
|
28
|
+
BEGIN TRY
|
|
29
|
+
SELECT TOP (1) * FROM [dbo].[<tabla_facturas_emitidas>];
|
|
30
|
+
SET @fallos += 1; PRINT N'FALLO · puede leer las tablas directamente (solo debería ver el esquema ia)';
|
|
31
|
+
END TRY
|
|
32
|
+
BEGIN CATCH
|
|
33
|
+
PRINT N'OK · no puede leer las tablas directamente';
|
|
34
|
+
END CATCH;
|
|
35
|
+
|
|
36
|
+
BEGIN TRY
|
|
37
|
+
DELETE TOP (0) FROM [dbo].[<tabla_facturas_emitidas>];
|
|
38
|
+
SET @fallos += 1; PRINT N'FALLO · tiene permiso de DELETE';
|
|
39
|
+
END TRY
|
|
40
|
+
BEGIN CATCH
|
|
41
|
+
PRINT N'OK · no puede borrar';
|
|
42
|
+
END CATCH;
|
|
43
|
+
|
|
44
|
+
BEGIN TRY
|
|
45
|
+
UPDATE TOP (0) [dbo].[<tabla_facturas_emitidas>] SET <columna_fecha> = <columna_fecha>;
|
|
46
|
+
SET @fallos += 1; PRINT N'FALLO · tiene permiso de UPDATE';
|
|
47
|
+
END TRY
|
|
48
|
+
BEGIN CATCH
|
|
49
|
+
PRINT N'OK · no puede modificar';
|
|
50
|
+
END CATCH;
|
|
51
|
+
|
|
52
|
+
BEGIN TRY
|
|
53
|
+
EXEC (N'CREATE TABLE [ia].[prueba_escritura] (x int)');
|
|
54
|
+
SET @fallos += 1; PRINT N'FALLO · puede crear tablas';
|
|
55
|
+
END TRY
|
|
56
|
+
BEGIN CATCH
|
|
57
|
+
PRINT N'OK · no puede crear tablas';
|
|
58
|
+
END CATCH;
|
|
59
|
+
|
|
60
|
+
REVERT;
|
|
61
|
+
ROLLBACK TRANSACTION;
|
|
62
|
+
|
|
63
|
+
IF @fallos = 0 PRINT N'RESULTADO: OK — ia_lectura es de solo lectura.';
|
|
64
|
+
ELSE PRINT N'RESULTADO: REVISAR — hay ' + CAST(@fallos AS nvarchar(10)) + N' fallos. No conectes el agente.';
|
|
65
|
+
GO
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<#
|
|
2
|
+
Kit puente · Copia nocturna de la base del programa a "ERP_IA" (SQL Server, Express incluido).
|
|
3
|
+
|
|
4
|
+
Qué hace, en este orden:
|
|
5
|
+
1. BACKUP ... WITH COPY_ONLY de la base de producción. COPY_ONLY no rompe la cadena
|
|
6
|
+
de copias del programa.
|
|
7
|
+
2. RESTORE en ERP_IA (la sobrescribe), moviendo los ficheros a la carpeta de datos.
|
|
8
|
+
3. Vuelve a crear el usuario, el esquema "ia" y las vistas con 02-usuario-y-vistas.sql,
|
|
9
|
+
porque la restauración los borra.
|
|
10
|
+
4. Pasa la prueba de solo lectura (03-prueba-solo-lectura.sql) y deja un registro.
|
|
11
|
+
|
|
12
|
+
SQL Server Express no tiene SQL Server Agent: se programa con el Programador de tareas
|
|
13
|
+
de Windows (ver el README del kit). Se ejecuta con una cuenta de Windows que sea
|
|
14
|
+
administradora de SQL Server, NUNCA con ia_lectura.
|
|
15
|
+
|
|
16
|
+
Uso:
|
|
17
|
+
powershell -ExecutionPolicy Bypass -File copia-nocturna.ps1 -Origen "<BD_DEL_PROGRAMA>"
|
|
18
|
+
#>
|
|
19
|
+
param(
|
|
20
|
+
[Parameter(Mandatory = $true)][string]$Origen,
|
|
21
|
+
[string]$Servidor = ".\SQLEXPRESS",
|
|
22
|
+
[string]$Copia = "ERP_IA",
|
|
23
|
+
[string]$CarpetaBackup = "C:\KitPuente\backups",
|
|
24
|
+
[string]$CarpetaDatos = "C:\KitPuente\datos",
|
|
25
|
+
[string]$CarpetaScripts = $PSScriptRoot,
|
|
26
|
+
[int]$DiasRetencion = 3
|
|
27
|
+
)
|
|
28
|
+
$ErrorActionPreference = "Stop"
|
|
29
|
+
New-Item -ItemType Directory -Force -Path $CarpetaBackup, $CarpetaDatos | Out-Null
|
|
30
|
+
$registro = Join-Path $CarpetaBackup "copia-nocturna.log"
|
|
31
|
+
function Log($texto) { "$(Get-Date -Format s) $texto" | Tee-Object -FilePath $registro -Append }
|
|
32
|
+
|
|
33
|
+
$bak = Join-Path $CarpetaBackup ("{0}-{1}.bak" -f $Origen, (Get-Date -Format yyyyMMdd))
|
|
34
|
+
Log "Backup de [$Origen] a $bak"
|
|
35
|
+
sqlcmd -S $Servidor -E -b -Q "BACKUP DATABASE [$Origen] TO DISK = N'$bak' WITH COPY_ONLY, INIT, CHECKSUM"
|
|
36
|
+
if ($LASTEXITCODE -ne 0) { Log "ERROR en el backup"; exit 1 }
|
|
37
|
+
|
|
38
|
+
# Nombres lógicos de los ficheros para el MOVE.
|
|
39
|
+
$lista = sqlcmd -S $Servidor -E -b -h -1 -W -s "|" -Q "SET NOCOUNT ON; RESTORE FILELISTONLY FROM DISK = N'$bak'"
|
|
40
|
+
$moves = @()
|
|
41
|
+
foreach ($linea in $lista) {
|
|
42
|
+
$campos = $linea -split "\|"
|
|
43
|
+
if ($campos.Count -lt 3) { continue }
|
|
44
|
+
$logico = $campos[0].Trim(); $tipo = $campos[2].Trim()
|
|
45
|
+
$ext = if ($tipo -eq "L") { "ldf" } else { "mdf" }
|
|
46
|
+
$moves += "MOVE N'$logico' TO N'$(Join-Path $CarpetaDatos "$Copia-$logico.$ext")'"
|
|
47
|
+
}
|
|
48
|
+
if ($moves.Count -eq 0) { Log "ERROR: no se han leído los ficheros del backup"; exit 1 }
|
|
49
|
+
|
|
50
|
+
Log "Restaurando en [$Copia]"
|
|
51
|
+
$restore = "IF DB_ID(N'$Copia') IS NOT NULL ALTER DATABASE [$Copia] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; " +
|
|
52
|
+
"RESTORE DATABASE [$Copia] FROM DISK = N'$bak' WITH REPLACE, RECOVERY, " + ($moves -join ", ") + "; " +
|
|
53
|
+
"ALTER DATABASE [$Copia] SET MULTI_USER;"
|
|
54
|
+
sqlcmd -S $Servidor -E -b -Q $restore
|
|
55
|
+
if ($LASTEXITCODE -ne 0) { Log "ERROR en la restauración"; exit 1 }
|
|
56
|
+
|
|
57
|
+
Log "Usuario, esquema ia y vistas"
|
|
58
|
+
sqlcmd -S $Servidor -E -b -d $Copia -i (Join-Path $CarpetaScripts "02-usuario-y-vistas.sql")
|
|
59
|
+
if ($LASTEXITCODE -ne 0) { Log "ERROR al crear usuario o vistas"; exit 1 }
|
|
60
|
+
|
|
61
|
+
Log "Prueba de solo lectura"
|
|
62
|
+
$prueba = sqlcmd -S $Servidor -E -b -d $Copia -i (Join-Path $CarpetaScripts "03-prueba-solo-lectura.sql")
|
|
63
|
+
$prueba | ForEach-Object { Log $_ }
|
|
64
|
+
if (-not ($prueba -match "RESULTADO: OK")) { Log "ERROR: la prueba de solo lectura no ha dado OK"; exit 1 }
|
|
65
|
+
|
|
66
|
+
Get-ChildItem $CarpetaBackup -Filter "$Origen-*.bak" |
|
|
67
|
+
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$DiasRetencion) } | Remove-Item -Force
|
|
68
|
+
Log "OK — copia lista en [$Copia]"
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
skill: connect-tool
|
|
2
2
|
|
|
3
3
|
# Cases for connect-tool: the user names ONE tool to connect to the agent. The skill researches the
|
|
4
|
-
# vendor's real docs, picks the least dangerous route (MCP, API, integration,
|
|
5
|
-
# of the database
|
|
6
|
-
# fail when read-only), guides the user and records it in
|
|
4
|
+
# vendor's real docs FIRST, picks the least dangerous route (MCP, API, integration, vendor module,
|
|
5
|
+
# file exchange, read-only copy of the database; never RPA or scraping), builds 01-TOOLS/<TOOL>/ with
|
|
6
|
+
# a test that proves it (and proves writes fail when read-only), guides the user and records it in
|
|
7
|
+
# 02-DOCS. Only when nothing is documented does it take option 2: diagnose the program in
|
|
8
|
+
# conversation, find who holds the key, and keep going. It is NOT the typed API client
|
|
7
9
|
# (api-connector-builder), NOT the whole-workspace scan (harness), NOT whether to automate a process
|
|
8
10
|
# (automation-strategy), NOT chaining services (automation-flows).
|
|
9
11
|
|
|
@@ -24,7 +26,10 @@ should_trigger:
|
|
|
24
26
|
why: "A legacy file database. Copy the .mdb/.accdb, read it with mdbtools or convert it, and never write into the program's file."
|
|
25
27
|
|
|
26
28
|
- prompt: "¿Se puede conectar InstalWin? No sé ni por dónde empezar."
|
|
27
|
-
why: "An obscure vertical program nobody has catalogued: the method (search vendor docs, export features, the database it installs) works for it, and if nothing reliable exists the skill says so."
|
|
29
|
+
why: "An obscure vertical program nobody has catalogued: the method (search vendor docs, export features, the database it installs) works for it, and if nothing reliable exists the skill says so and goes to option 2."
|
|
30
|
+
|
|
31
|
+
- prompt: "Quiero que el agente meta los asientos de las facturas de proveedores en nuestro Sage 50."
|
|
32
|
+
why: "Writing into an installed accounting program: the route is the official import file the user's program exports as a sample, validated and imported by a person into the test company first, never the database."
|
|
28
33
|
|
|
29
34
|
should_not_trigger:
|
|
30
35
|
- prompt: "Write a TypeScript client for the Holded API with pagination, retries and token refresh."
|
|
@@ -56,3 +61,35 @@ capability:
|
|
|
56
61
|
- "Proposes reading a nightly copy with a SELECT-only user, reached privately, never the live database exposed to the internet"
|
|
57
62
|
- "Builds 01-TOOLS/<TOOL>/ from the template with a test_connection that proves reading works AND that a write is refused, telling a permission error apart from any other failure"
|
|
58
63
|
- "Explains the chosen route in plain words and records the connection in 02-DOCS"
|
|
64
|
+
|
|
65
|
+
- scenario: "A small-business owner says: 'Quiero conectar Geyce, nuestro programa de gestión de hace veinte años. No sé qué tipo de programa es ni a quién preguntar.' Research finds no public API, MCP or technical documentation. Show what the agent does."
|
|
66
|
+
must_include:
|
|
67
|
+
- "Researches first (vendor docs, MCP registry, exports and imports in the manual) and cites what it searched and the date, saying plainly that nothing reliable was found"
|
|
68
|
+
- "Only then takes option 2 and does not stop: asks the plain diagnosis questions (website login, works offline, several PCs share data, remote desktop, data-folder extensions, who they call when it breaks)"
|
|
69
|
+
- "Says which program type it looks like and how sure it is, what can be tried, and what the user must confirm"
|
|
70
|
+
- "Finds out who holds the key (distributor, IT person or accounting firm) and lists the closed questions for them: database engine, read-only user on a copy, scheduled export to a folder, whether the contract allows reading the database, cost"
|
|
71
|
+
- "Leaves the case open in 02-DOCS with what was asked, to whom and when, so another session can pick it up"
|
|
72
|
+
- "Never proposes screen automation (RPA) or scraping"
|
|
73
|
+
|
|
74
|
+
- scenario: "The user answers the diagnosis: 'Entramos al programa por un escritorio remoto que nos da el distribuidor; en la oficina no hay nada instalado.' Show what the agent concludes and proposes."
|
|
75
|
+
must_include:
|
|
76
|
+
- "Recognises a program hosted by the distributor: there is no database to copy and no folder the agent can reach"
|
|
77
|
+
- "States that the only door is the provider and does not try to reach their servers any other way"
|
|
78
|
+
- "Proposes asking the distributor for a scheduled export to a folder the client can reach, an integration module or API, or a read-only user on their side, with the cost question"
|
|
79
|
+
- "Offers the message for the distributor from the messages section, read-only and with nothing opened to the internet"
|
|
80
|
+
- "If the provider offers nothing, says the honest options are to keep asking the vendor or to weigh migrating, not RPA"
|
|
81
|
+
|
|
82
|
+
- scenario: "The user says: 'Llevamos la contabilidad en ContaSOL instalado. Quiero que el agente registre los asientos de las facturas de compra que llegan al correo.' Show the route from the first answer to the first import."
|
|
83
|
+
must_include:
|
|
84
|
+
- "Chooses file exchange for writing: the agent generates ContaSOL's official import file in 01-TOOLS/<TOOL>/out/ and a person reviews and imports it; never writes into the program's database"
|
|
85
|
+
- "Asks for a sample file exported from the user's own ContaSOL and copies its structure, instead of a remembered format"
|
|
86
|
+
- "Describes the format (columns, encoding, separator, decimal mark, dates) in the tool's README"
|
|
87
|
+
- "Validates before releasing the file: entries balance, accounts exist in the current chart, no duplicates against a batch log"
|
|
88
|
+
- "Makes the first import into the program's test company, not the real books"
|
|
89
|
+
|
|
90
|
+
- scenario: "The user says: 'Mi ERP no tiene API. ¿Podemos poner un robot que haga clic en las pantallas, o que el agente lea la web del programa?'"
|
|
91
|
+
must_include:
|
|
92
|
+
- "Declines screen automation (RPA) and scraping: they act with a person's permissions, break on every update and cannot prove they only read"
|
|
93
|
+
- "Researches the other routes first: vendor or distributor module, cloud edition with an API, scheduled export, official import file, read-only copy of the database"
|
|
94
|
+
- "If one exists, proposes it; if none exists, says so and offers asking the vendor or weighing migration with the connect-or-migrate signals"
|
|
95
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Connect, or migrate?
|
|
2
|
+
|
|
3
|
+
The agent is only as good as the data it reaches. If the main program lets nothing out (no API, no
|
|
4
|
+
export, no readable database), every automation goes through copy and paste. Sometimes the answer
|
|
5
|
+
is to **connect it with a bridge** (a nightly export, a read-only database user, the distributor's
|
|
6
|
+
module). Sometimes it is to **migrate** to something that connects out of the box. Rarely, it is to
|
|
7
|
+
**build** a replacement. Give one verdict, in one line:
|
|
8
|
+
|
|
9
|
+
- **Connect as is**: a route from SKILL.md section 2 exists and is good enough.
|
|
10
|
+
- **Connect with a bridge**: file exchange or a read-only copy ([database bridge](database-bridge.md)).
|
|
11
|
+
- **Consider migrating**: with the sum below.
|
|
12
|
+
- **Consider building**: only under the conditions below.
|
|
13
|
+
|
|
14
|
+
Whether a process deserves automating at all is `automation-strategy`'s question, not this one.
|
|
15
|
+
|
|
16
|
+
## Signals that the program holds you back (the more, the stronger the case to migrate)
|
|
17
|
+
|
|
18
|
+
1. **No way to get data out** without a person: no API, no automatic export, no readable database.
|
|
19
|
+
2. **Unsupported version**: the vendor no longer updates it, or it only runs on one office PC.
|
|
20
|
+
3. **The bridge costs more than the change**: an integrator, a module or consulting hours every year
|
|
21
|
+
for what another program includes.
|
|
22
|
+
4. **Daily copy and paste** between this program and others. More than 3–4 hours a week is over 150
|
|
23
|
+
hours a year.
|
|
24
|
+
5. **Regulation it does not cover**: in Spain, Veri\*factu and B2B e-invoicing ([Spain](spain.md)).
|
|
25
|
+
If the vendor does not adapt it, or charges extra, the business has to move anyway.
|
|
26
|
+
6. **Only one person can use it**, or the data ends up in parallel spreadsheets.
|
|
27
|
+
7. **It does not grow**: more users, another site, mobile access.
|
|
28
|
+
|
|
29
|
+
## Signals not to migrate (yet)
|
|
30
|
+
|
|
31
|
+
1. **Sector software with unique functions**: construction measurement and BC3, legal health and
|
|
32
|
+
safety obligations, installer software, a point of sale integrated with its hardware. Generic
|
|
33
|
+
ERPs do not cover them the same way. **Connect with a bridge.**
|
|
34
|
+
2. **The accounting firm works with this program** and you exchange files. Changing alone breaks
|
|
35
|
+
that flow. Decide together.
|
|
36
|
+
3. **Mid financial year.** Migrating accounting mid-year complicates the close. The good window is
|
|
37
|
+
**1 January**, or the start of a quarter for invoicing only.
|
|
38
|
+
4. **The pain is small.** If a nightly export to Excel solves 80 % of what is wanted, do not migrate
|
|
39
|
+
for the other 20 %.
|
|
40
|
+
5. **Nobody can lead the change** inside the company for one to three months.
|
|
41
|
+
|
|
42
|
+
## The sum, over two years
|
|
43
|
+
|
|
44
|
+
**Cost of staying** (per year): hours of copy and paste × the hourly cost; plus bridges, modules or
|
|
45
|
+
consulting to connect it; plus the regulatory risk (forced adaptation, penalties).
|
|
46
|
+
|
|
47
|
+
**Cost of migrating** (once, plus the difference in fees): first-year licences minus what stops
|
|
48
|
+
being paid; implementation and data migration; training and weeks of lower productivity; the risk
|
|
49
|
+
of data that does not move well or sector modules the target lacks.
|
|
50
|
+
|
|
51
|
+
**Rule of thumb:** if migrating pays for itself in **18–24 months** and there is a **window**
|
|
52
|
+
(start of the financial year, licence renewal, forced adaptation), recommend it, with one to three
|
|
53
|
+
targets that have a documented API and, ideally, an MCP. If not, **connect with a bridge** and redo
|
|
54
|
+
the sum at the next renewal.
|
|
55
|
+
|
|
56
|
+
## If migrating: seven steps not to lose anything
|
|
57
|
+
|
|
58
|
+
1. **Inventory**: which data (customers, items, invoices, accounting) and which processes depend on
|
|
59
|
+
the program.
|
|
60
|
+
2. **Choose the target with the agent in mind**: a documented API and, if possible, an MCP.
|
|
61
|
+
3. **Export the history** and **keep the old system read-only**. Accounting and invoices must be
|
|
62
|
+
kept for years: do not delete it.
|
|
63
|
+
4. **Start on a clean date**: start of the year or quarter. Opening balances rather than every
|
|
64
|
+
movement, unless the target imports them well.
|
|
65
|
+
5. **One month in parallel** with a real process (invoicing, for instance) before switching off.
|
|
66
|
+
6. **Connect the target from day one** (`01-TOOLS/<TARGET>/` and its `test_connection`), so the
|
|
67
|
+
value shows immediately.
|
|
68
|
+
7. **Train the team** and name who owns the new system.
|
|
69
|
+
|
|
70
|
+
## The third way: building it
|
|
71
|
+
|
|
72
|
+
Some businesses replace a program with their own, built with Claude Code: a custom CRM, a simple
|
|
73
|
+
ERP, a bakery's point of sale. Sometimes it is a very good idea; sometimes it is expensive to keep.
|
|
74
|
+
|
|
75
|
+
Worth it only if **all** of these hold:
|
|
76
|
+
|
|
77
|
+
- **Small, stable scope**: one function, not "the whole ERP".
|
|
78
|
+
- **Someone maintains it**: code in a repository, backups, security updates, and someone who
|
|
79
|
+
understands it when the builder is away.
|
|
80
|
+
- **It starts by reading** the old system's data (export or bridge), and both coexist for a while.
|
|
81
|
+
- **It does not invoice.** A program that issues invoices must comply with the invoicing
|
|
82
|
+
regulations itself ([Spain](spain.md)). Building invoicing almost never pays: connect it.
|
|
83
|
+
|
|
84
|
+
Not worth it when the program is sector software with legal obligations (health and safety,
|
|
85
|
+
payroll, official accounting), or when only one person understands it.
|
|
86
|
+
|
|
87
|
+
## Typical targets
|
|
88
|
+
|
|
89
|
+
| If you have… | Targets to consider | Why |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| A closed desktop ERP or accounting program, unsupported | Odoo, Holded, Business Central, Sage Active | Cloud with a documented API |
|
|
92
|
+
| Excel as the "ERP" | First Google Sheets or Excel on OneDrive (already connectable); an ERP when volume demands it | Moving the file to the cloud already makes it reachable, at no cost |
|
|
93
|
+
| The WhatsApp Business app for orders or bookings | WhatsApp Business Platform (Cloud API), directly with Meta or a provider | Not a change of tool: the edition with an API |
|
|
94
|
+
| Notion or Airtable as a CRM | A CRM with API and MCP when there are more than two or three salespeople | While small, Notion and Airtable connect well |
|
|
95
|
+
|
|
96
|
+
Check each target's current API, MCP and price before recommending it ([what to find out](what-to-find-out.md)).
|