# config_conexion

Servicio Node.js para ejecutar de forma remota y segura el proceso de `Configurando datos de conexion en Saeplus Go` desde otro servidor.

## Arquitectura recomendada

1. El backend principal genera `companyId` y mantiene su proceso actual.
2. Cuando también se requiera replicar la configuración en otro servidor, el backend principal firma una petición HMAC y llama este servicio.
3. `config_conexion` valida IP, headers HMAC, `timestamp`, `nonce` y payload.
4. El servicio escribe únicamente sobre rutas definidas en su `.env`, crea backup, escribe sobre el archivo existente preservando permisos y propietario, verifica el resultado y revierte si algo falla.

## Autenticación recomendada

Para comunicación `server-to-server` recomiendo **HMAC sobre HTTPS**.

Ventajas:

- valida integridad del body;
- evita replay attack con `timestamp + nonce`;
- no depende de sesión ni refresh token;
- es más simple y más controlable que JWT para dos servidores con secreto compartido;
- una API Key sola no protege integridad del payload.

Firma usada:

```text
HMAC_SHA256_HEX(
  secret,
  timestamp + "\n" +
  nonce + "\n" +
  method + "\n" +
  path + "\n" +
  sha256(rawBody)
)
```

## Estructura

```text
config_conexion/
  package.json
  .env.example
  ecosystem.config.js
  README.md
  src/
    app.js
    server.js
    config/
      env.js
    controllers/
      connectionConfigController.js
    middlewares/
      errorMiddleware.js
      hmacAuthMiddleware.js
      requestContextMiddleware.js
    routes/
      connectionConfigRoutes.js
      index.js
    services/
      connectionConfigService.js
    utils/
      appError.js
      fileSafety.js
      hmac.js
      nonceStore.js
    validators/
      connectionConfigValidator.js
```

## Endpoint REST

`POST /api/connection-config/sync`

## Payload realista

```json
{
  "companyId": "acme001",
  "mode": "upsert",
  "requestedBy": "registro_saeplus/backend",
  "traceId": "wizard-12-acme001",
  "dryRun": false
}
```

Reglas:

- `companyId`: obligatorio, inicia con letra, permite letras, números y `_`, máximo 30.
- `mode`: `create`, `update` o `upsert`.
- `dryRun`: valida sin escribir archivos.

## Respuesta exitosa

```json
{
  "success": true,
  "code": "CONFIG_APPLIED",
  "message": "Configuración aplicada correctamente para acme001.",
  "requestId": "8e1d1bdb-bf52-4f72-9b63-a1d2d355ed1e",
  "data": {
    "companyId": "acme001",
    "mode": "upsert",
    "dryRun": false,
    "requestedBy": "registro_saeplus/backend",
    "traceId": "wizard-12-acme001",
    "json": {
      "action": "created",
      "path": "/var/www/saeplus_go/config.json",
      "backupPath": "/var/backups/config_conexion/2026-03-24T12-00-00-000Z-8e1d1bdb/config.json.bak"
    },
    "php": {
      "action": "created",
      "path": "/var/www/api/config/database.php",
      "backupPath": "/var/backups/config_conexion/2026-03-24T12-00-00-000Z-8e1d1bdb/database.php.bak"
    },
    "backupDir": "/var/backups/config_conexion/2026-03-24T12-00-00-000Z-8e1d1bdb"
  }
}
```

## Respuestas de error

- Validación: `400 VALIDATION_ERROR`
- JSON inválido: `400 INVALID_JSON_BODY`
- Autenticación: `401 AUTH_HEADERS_MISSING`, `401 INVALID_SIGNATURE`, `401 REQUEST_EXPIRED`, `401 NONCE_REPLAYED`
- IP no permitida: `403 IP_NOT_ALLOWED`
- Duplicado: `409 DUPLICATE_ENTRY`
- Archivo no encontrado o inválido: `500 FILE_NOT_FOUND`, `500 INVALID_JSON_FILE`, `500 PHP_MARKER_NOT_FOUND`
- Escritura: `500 FILE_WRITE_ERROR`, `500 WRITE_FAILED`

## Seguridad aplicada

- El payload no acepta rutas de archivo.
- Las rutas objetivo salen solo del `.env`.
- Las rutas además deben estar dentro de `ALLOWED_BASE_PATHS`.
- Validación estricta de entrada.
- Backups automáticos antes de modificar.
- Escritura directa sobre el archivo existente para no reemplazar inode ni alterar permisos o propietario.
- Verificación posterior a la escritura.
- Rollback local si la escritura o verificación falla.
- Protección contra replay con nonce TTL.
- Allowlist IP opcional.

## Crear, actualizar o upsert

- `create`: falla si la empresa ya existe.
- `update`: falla si la empresa no existe.
- `upsert`: crea o actualiza.

## Variables sensibles en .env

Ejemplo base: [config_conexion/.env.example](/c:/wamp64/www/AI/registro_saeplus/config_conexion/.env.example)

Variables clave:

- `HMAC_SHARED_KEYS_JSON`
- `ALLOWED_IPS`
- `ALLOWED_BASE_PATHS`
- `JSON_CONFIG_PATH`
- `PHP_DATABASE_CONFIG_PATH`
- `BACKUP_DIR`
- `REMOTE_DB_HOST`
- `REMOTE_DB_PORT`
- `REMOTE_DB_USER`
- `REMOTE_DB_PASSWORD`

## Middleware de seguridad

Archivo: [hmacAuthMiddleware.js](/c:/wamp64/www/AI/registro_saeplus/config_conexion/src/middlewares/hmacAuthMiddleware.js)

Valida:

- cliente permitido;
- firma HMAC;
- `timestamp` en ventana válida;
- `nonce` no reutilizado;
- IP en allowlist si está configurada.

## Validación de datos

Archivo: [connectionConfigValidator.js](/c:/wamp64/www/AI/registro_saeplus/config_conexion/src/validators/connectionConfigValidator.js)

El validador rechaza:

- payload que no sea objeto;
- `companyId` inválido;
- `mode` fuera de contrato;
- `requestedBy` y `traceId` con caracteres no permitidos.

## PM2

```bash
cd /var/www/registro_saeplus/config_conexion
npm ci --omit=dev
pm2 start ecosystem.config.js
pm2 save
pm2 startup
```

Recomendaciones:

- usar `cwd` fijo;
- no ejecutar como `root`;
- reinicio automático activado;
- límites de memoria;
- logs con rotación.

## Hardening Linux

- Exponer el servicio solo por VPN o red privada.
- Poner Nginx delante con TLS 1.2+.
- Limitar por firewall a la IP del backend principal.
- Ejecutar con usuario dedicado sin shell interactivo.
- Permisos mínimos sobre `JSON_CONFIG_PATH`, `PHP_DATABASE_CONFIG_PATH` y `BACKUP_DIR`.
- Montar backups en disco con espacio monitoreado.
- Si escalas a varias instancias, mover el store de nonce a Redis.
- Centralizar logs y alertar sobre `401`, `409` y `500`.

## Cómo consumir esta API desde la app actual

Ya quedó creado el cliente HMAC en [remoteConnectionConfigClient.js](/c:/wamp64/www/AI/registro_saeplus/backend/src/utils/remoteConnectionConfigClient.js).

Variables del backend: [backend/.env.example](/c:/wamp64/www/AI/registro_saeplus/backend/.env.example)

Ejemplo de uso:

```js
const { syncRemoteConnectionConfig } = require('../utils/remoteConnectionConfigClient');

await syncRemoteConnectionConfig({
  companyId: idEmpresa,
  mode: 'upsert',
  requestedBy: 'registro_saeplus/backend',
  traceId: `wizard-12-${idEmpresa}`
});
```

Decisión de integración:

- dejé el cliente listo para invocarlo desde el backend actual;
- no lo acoplé automáticamente al flujo local de `configureSaeplusGo`, porque hoy no existe una compensación remota transaccional y encadenarlo de forma ciega dejaría riesgo de inconsistencia entre servidores ante fallos parciales de red;
- con `upsert` la operación es idempotente y segura para reintento.
