# Guía de Adaptadores USEE

## Especificación de Adaptadores JSON y HTTP

**Versión 1.0**

---

## Propósito

Los adaptadores son traductores. Permiten que las piezas USEE, diseñadas para comunicarse mediante texto plano y stdin/stdout, puedan integrarse en contextos que esperan otros formatos o protocolos.

### La Jerarquía de Comunicación

```
Nivel 1 (OBLIGATORIO): stdin/stdout con Formato de Texto USEE
         ↓
Nivel 2 (RECOMENDADO): Separación stdout/stderr
         ↓
Nivel 3 (OPCIONAL): Adaptador JSON
         ↓
Nivel 4 (OPCIONAL): Adaptador HTTP
```

### Principio Fundamental

> Los adaptadores no agregan funcionalidad. Solo traducen.

Una pieza con adaptador JSON hace exactamente lo mismo que sin él. La única diferencia es el formato de entrada y salida.

---

## Adaptador JSON (Nivel 3)

### Propósito

Permite que aplicaciones que trabajan con JSON puedan comunicarse con piezas USEE sin necesidad de implementar un parser FTU.

### Archivo

```
ejecutar-json
```

### Comportamiento

```
                    ┌─────────────────┐
  JSON de entrada → │ ejecutar-json   │ → JSON de salida
                    │                 │
                    │  ┌───────────┐  │
                    │  │ Traduce a │  │
                    │  │    FTU    │  │
                    │  └─────┬─────┘  │
                    │        ↓        │
                    │  ┌───────────┐  │
                    │  │ ejecutar  │  │
                    │  │ (pieza)   │  │
                    │  └─────┬─────┘  │
                    │        ↓        │
                    │  ┌───────────┐  │
                    │  │ Traduce a │  │
                    │  │   JSON    │  │
                    │  └───────────┘  │
                    └─────────────────┘
```

El adaptador JSON es un envoltorio que:

1. Recibe JSON por stdin
2. Lo traduce a FTU
3. Ejecuta la pieza con el FTU
4. Traduce la salida FTU a JSON
5. Emite JSON por stdout

---

### Reglas de Traducción JSON → FTU

#### Valores Simples

| JSON | FTU |
|------|-----|
| `"texto"` | `texto` |
| `123` | `123` |
| `45.67` | `45.67` |
| `true` | `si` |
| `false` | `no` |
| `null` | (valor vacío) |

#### Objetos

```json
{
  "nombre": "Juan",
  "edad": 30
}
```

```
nombre: Juan
edad: 30
```

#### Objetos Anidados

```json
{
  "usuario": {
    "nombre": "Juan",
    "direccion": {
      "ciudad": "México",
      "pais": "MX"
    }
  }
}
```

```
usuario.nombre: Juan
usuario.direccion.ciudad: México
usuario.direccion.pais: MX
```

#### Arrays de Primitivos

```json
{
  "roles": ["admin", "editor", "lector"]
}
```

```
roles: admin, editor, lector
```

#### Arrays de Objetos

```json
{
  "usuarios": [
    {"nombre": "Juan", "rol": "admin"},
    {"nombre": "María", "rol": "editor"}
  ]
}
```

**Opción A: Registros separados (preferida para listas homogéneas)**

```
nombre: Juan
rol: admin
---
nombre: María
rol: editor
```

**Opción B: Índices numéricos (para listas dentro de otros datos)**

```
usuarios.0.nombre: Juan
usuarios.0.rol: admin
usuarios.1.nombre: María
usuarios.1.rol: editor
```

#### Arrays Mixtos

```json
{
  "datos": [1, "texto", true]
}
```

```
datos: 1, texto, si
```

---

### Reglas de Traducción FTU → JSON

#### Detección de Tipos

El adaptador debe inferir tipos de los valores FTU:

| Valor FTU | Tipo JSON | Ejemplo |
|-----------|-----------|---------|
| Solo dígitos | número entero | `30` → `30` |
| Dígitos con punto | número decimal | `3.14` → `3.14` |
| `si` | booleano true | `si` → `true` |
| `no` | booleano false | `no` → `false` |
| Vacío | null | `` → `null` |
| Contiene `, ` | array | `a, b` → `["a", "b"]` |
| Contiene `.` en clave | objeto anidado | `a.b: x` → `{"a":{"b":"x"}}` |
| Otro | string | `Juan` → `"Juan"` |

#### Registros Múltiples

```
nombre: Juan
edad: 30
---
nombre: María
edad: 25
```

```json
[
  {"nombre": "Juan", "edad": 30},
  {"nombre": "María", "edad": 25}
]
```

#### Registro Único

```
nombre: Juan
edad: 30
```

```json
{
  "nombre": "Juan",
  "edad": 30
}
```

**Nota:** Un solo registro produce un objeto, no un array de un elemento.

---

### Implementación del Adaptador JSON

#### Pseudocódigo

```
ejecutar_json():
    # Leer JSON de stdin
    json_entrada = leer_stdin()
    
    # Validar JSON
    si no es_json_valido(json_entrada):
        error_json = {
            "estado": "error",
            "codigo": "json_invalido",
            "mensaje": "La entrada no es JSON válido"
        }
        escribir_stderr(json_a_texto(error_json))
        salir(2)
    
    # Traducir a FTU
    objeto = parsear_json(json_entrada)
    ftu_entrada = objeto_a_ftu(objeto)
    
    # Ejecutar pieza
    resultado = ejecutar_proceso("./ejecutar", stdin=ftu_entrada)
    
    # Traducir salida a JSON
    si resultado.codigo == 0:
        ftu_salida = resultado.stdout
    sino:
        ftu_salida = resultado.stderr
    
    registros = parsear_ftu(ftu_salida)
    
    si longitud(registros) == 1:
        json_salida = registro_a_json(registros[0])
    sino:
        json_salida = [registro_a_json(r) para r en registros]
    
    # Emitir resultado
    si resultado.codigo == 0:
        escribir_stdout(json_a_texto(json_salida))
    sino:
        escribir_stderr(json_a_texto(json_salida))
    
    salir(resultado.codigo)
```

#### Funciones de Traducción

```
objeto_a_ftu(objeto, prefijo=""):
    lineas = []
    
    para cada (clave, valor) en objeto:
        clave_completa = prefijo + clave si prefijo sino clave
        
        si valor es objeto:
            lineas.agregar(objeto_a_ftu(valor, clave_completa + "."))
        sino si valor es array:
            si todos_son_primitivos(valor):
                lineas.agregar(clave_completa + ": " + valor.unir(", "))
            sino:
                para i, elemento en enumerar(valor):
                    lineas.agregar(objeto_a_ftu(elemento, clave_completa + "." + i + "."))
        sino si valor es booleano:
            lineas.agregar(clave_completa + ": " + ("si" si valor sino "no"))
        sino si valor es null:
            lineas.agregar(clave_completa + ":")
        sino:
            lineas.agregar(clave_completa + ": " + texto(valor))
    
    retornar lineas.unir("\n")

registro_a_json(registro):
    resultado = {}
    
    para cada (clave, valor) en registro:
        partes = clave.dividir(".")
        actual = resultado
        
        para i, parte en enumerar(partes[:-1]):
            si parte no está en actual:
                actual[parte] = {}
            actual = actual[parte]
        
        clave_final = partes[-1]
        actual[clave_final] = inferir_tipo(valor)
    
    retornar resultado

inferir_tipo(valor):
    si valor está vacío:
        retornar null
    si valor == "si":
        retornar true
    si valor == "no":
        retornar false
    si es_entero(valor):
        retornar entero(valor)
    si es_decimal(valor):
        retornar decimal(valor)
    si valor.contiene(", "):
        retornar [inferir_tipo(v.trim()) para v en valor.dividir(", ")]
    retornar valor
```

---

### Uso del Adaptador JSON

#### Línea de Comandos

```bash
# Entrada JSON directa
echo '{"usuario": "juan@ejemplo.com", "clave": "secreto123"}' | ./ejecutar-json

# Desde archivo
./ejecutar-json < entrada.json > salida.json

# Con jq para formatear
echo '{"usuario": "juan@ejemplo.com", "clave": "secreto123"}' | ./ejecutar-json | jq .
```

#### Salida Exitosa

```json
{
  "estado": "ok",
  "sesion_id": "ses_abc123",
  "expira": "2025-01-16T10:30:00Z",
  "usuario": {
    "id": "usr_001",
    "nombre": "Juan Pérez"
  }
}
```

#### Salida con Error

```json
{
  "estado": "error",
  "codigo": "credenciales_invalidas",
  "mensaje": "El correo o la contraseña son incorrectos",
  "sugerencia": "Verifique sus credenciales e intente nuevamente"
}
```

---

### Argumentos del Adaptador JSON

| Argumento | Descripción |
|-----------|-------------|
| `--ayuda` | Muestra instrucciones de uso |
| `--version` | Muestra versión de la pieza |
| `--compacto` | Salida JSON sin formatear (una línea) |
| `--pretty` | Salida JSON formateada con indentación (default) |

---

## Adaptador HTTP (Nivel 4)

### Propósito

Permite que la pieza se exponga como un servicio de red, accesible mediante solicitudes HTTP. Esto habilita el uso desde aplicaciones web, móviles, o cualquier cliente HTTP.

### Archivo

```
ejecutar-http
```

### Comportamiento

```
                         ┌─────────────────────┐
  Solicitud HTTP ──────→ │   ejecutar-http     │ ──────→ Respuesta HTTP
                         │                     │
                         │  ┌───────────────┐  │
                         │  │ Servidor HTTP │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │ Traduce body  │  │
                         │  │    a FTU      │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │   ejecutar    │  │
                         │  │   (pieza)     │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │ Traduce a     │  │
                         │  │   respuesta   │  │
                         │  └───────────────┘  │
                         └─────────────────────┘
```

---

### Endpoints Estándar

| Método | Ruta | Descripción |
|--------|------|-------------|
| `POST` | `/` | Ejecuta la pieza |
| `GET` | `/salud` | Verifica que el servicio está activo |
| `GET` | `/version` | Retorna información de la pieza |
| `GET` | `/ayuda` | Retorna documentación |
| `GET` | `/metricas` | Retorna métricas de uso (opcional) |

---

### POST / (Ejecutar Pieza)

#### Formatos de Entrada Aceptados

El adaptador HTTP acepta múltiples formatos de entrada según el header `Content-Type`:

| Content-Type | Formato |
|--------------|---------|
| `text/plain` | Formato de Texto USEE |
| `application/json` | JSON |
| `application/x-www-form-urlencoded` | Formulario URL-encoded |
| `multipart/form-data` | Formulario multipart |

#### Ejemplo con FTU

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: text/plain" \
  -d "usuario: juan@ejemplo.com
clave: secreto123"
```

#### Ejemplo con JSON

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{"usuario": "juan@ejemplo.com", "clave": "secreto123"}'
```

#### Ejemplo con Formulario

```bash
curl -X POST http://localhost:8080/ \
  -d "usuario=juan@ejemplo.com" \
  -d "clave=secreto123"
```

#### Formato de Respuesta

El formato de respuesta depende del header `Accept`:

| Accept | Formato de Respuesta |
|--------|---------------------|
| `text/plain` | Formato de Texto USEE |
| `application/json` | JSON (default) |
| `*/*` | JSON |

---

### GET /salud

Verifica que el servicio está funcionando.

#### Solicitud

```bash
curl http://localhost:8080/salud
```

#### Respuesta Exitosa

```json
{
  "estado": "ok",
  "timestamp": "2025-01-15T10:30:00Z"
}
```

**Código HTTP:** `200 OK`

#### Respuesta con Problemas

```json
{
  "estado": "degradado",
  "timestamp": "2025-01-15T10:30:00Z",
  "mensaje": "Base de datos con latencia alta"
}
```

**Código HTTP:** `200 OK` (el servicio responde, aunque con problemas)

#### Servicio No Disponible

**Código HTTP:** `503 Service Unavailable`

---

### GET /version

Retorna información sobre la pieza.

#### Solicitud

```bash
curl http://localhost:8080/version
```

#### Respuesta

```json
{
  "nombre": "login",
  "version": "1.0.0",
  "protocolo": "usee-1.0",
  "adaptador": "http-1.0"
}
```

---

### GET /ayuda

Retorna documentación de uso.

#### Solicitud

```bash
curl http://localhost:8080/ayuda
```

#### Respuesta

```json
{
  "nombre": "login",
  "descripcion": "Autentica un usuario con correo y contraseña",
  "entrada": {
    "campos_obligatorios": [
      {
        "nombre": "usuario",
        "tipo": "texto",
        "descripcion": "Correo electrónico del usuario"
      },
      {
        "nombre": "clave",
        "tipo": "texto",
        "descripcion": "Contraseña del usuario"
      }
    ],
    "campos_opcionales": [
      {
        "nombre": "recordar",
        "tipo": "booleano",
        "default": "no",
        "descripcion": "Extender duración de la sesión"
      }
    ]
  },
  "salida": {
    "exitosa": [
      {"nombre": "estado", "tipo": "texto", "valor": "ok"},
      {"nombre": "sesion_id", "tipo": "texto"},
      {"nombre": "expira", "tipo": "fecha"}
    ],
    "error": [
      {"nombre": "estado", "tipo": "texto", "valor": "error"},
      {"nombre": "codigo", "tipo": "texto"},
      {"nombre": "mensaje", "tipo": "texto"}
    ]
  },
  "ejemplo": {
    "entrada": {"usuario": "ejemplo@correo.com", "clave": "contraseña"},
    "salida": {"estado": "ok", "sesion_id": "ses_xxx", "expira": "2025-01-16T10:30:00Z"}
  }
}
```

---

### GET /metricas (Opcional)

Retorna métricas de uso del servicio.

#### Solicitud

```bash
curl http://localhost:8080/metricas
```

#### Respuesta

```json
{
  "desde": "2025-01-15T00:00:00Z",
  "solicitudes_totales": 15234,
  "solicitudes_exitosas": 14892,
  "solicitudes_error": 342,
  "tiempo_respuesta_promedio_ms": 45,
  "tiempo_respuesta_p95_ms": 120,
  "tiempo_respuesta_p99_ms": 250
}
```

---

### Códigos HTTP

| Código | Cuándo se Usa |
|--------|---------------|
| `200 OK` | Operación exitosa |
| `400 Bad Request` | Entrada inválida o mal formada |
| `401 Unauthorized` | Autenticación requerida (si aplica) |
| `404 Not Found` | Ruta no encontrada |
| `405 Method Not Allowed` | Método HTTP no soportado |
| `415 Unsupported Media Type` | Content-Type no soportado |
| `422 Unprocessable Entity` | Entrada válida pero con errores de lógica |
| `429 Too Many Requests` | Límite de tasa excedido |
| `500 Internal Server Error` | Error interno de la pieza |
| `503 Service Unavailable` | Servicio no disponible |

### Mapeo de Errores de Pieza a HTTP

| Código de Salida Pieza | Código HTTP |
|------------------------|-------------|
| 0 | 200 OK |
| 1 | 422 Unprocessable Entity |
| 2 | 400 Bad Request |
| 3 | 500 Internal Server Error |
| 4 | 503 Service Unavailable |
| 5 | 503 Service Unavailable |
| 10-99 | 422 Unprocessable Entity |

---

### Headers de Respuesta

| Header | Valor | Descripción |
|--------|-------|-------------|
| `Content-Type` | `application/json` o `text/plain` | Formato de la respuesta |
| `X-USEE-Pieza` | `login` | Nombre de la pieza |
| `X-USEE-Version` | `1.0.0` | Versión de la pieza |
| `X-USEE-Tiempo-Ms` | `45` | Tiempo de procesamiento |
| `X-Request-Id` | `req_abc123` | ID único de la solicitud |

---

### Argumentos del Adaptador HTTP

| Argumento | Descripción | Default |
|-----------|-------------|---------|
| `--puerto=N` | Puerto donde escuchar | 8080 |
| `--host=HOST` | Host donde escuchar | 0.0.0.0 |
| `--timeout=S` | Timeout por solicitud en segundos | 30 |
| `--max-body=BYTES` | Tamaño máximo del cuerpo | 1048576 (1MB) |
| `--cors` | Habilitar CORS para todos los orígenes | deshabilitado |
| `--cors-origen=URL` | Habilitar CORS para origen específico | - |
| `--log=NIVEL` | Nivel de logging (debug, info, error) | info |
| `--metricas` | Habilitar endpoint /metricas | deshabilitado |

### Ejemplos de Inicio

```bash
# Iniciar en puerto por defecto
./ejecutar-http

# Puerto personalizado
./ejecutar-http --puerto=3000

# Con CORS habilitado
./ejecutar-http --cors

# Con logging detallado
./ejecutar-http --log=debug

# Producción típica
./ejecutar-http --puerto=8080 --timeout=10 --metricas
```

---

### Implementación del Adaptador HTTP

#### Pseudocódigo

```
ejecutar_http(argumentos):
    puerto = argumentos.puerto o 8080
    host = argumentos.host o "0.0.0.0"
    
    servidor = crear_servidor_http(host, puerto)
    
    servidor.registrar("POST", "/", manejar_ejecucion)
    servidor.registrar("GET", "/salud", manejar_salud)
    servidor.registrar("GET", "/version", manejar_version)
    servidor.registrar("GET", "/ayuda", manejar_ayuda)
    
    si argumentos.metricas:
        servidor.registrar("GET", "/metricas", manejar_metricas)
    
    log("Servidor iniciado en " + host + ":" + puerto)
    servidor.iniciar()

manejar_ejecucion(solicitud, respuesta):
    inicio = tiempo_actual()
    request_id = generar_id()
    
    # Determinar formato de entrada
    content_type = solicitud.header("Content-Type")
    
    si content_type == "application/json":
        entrada_ftu = json_a_ftu(solicitud.body)
    sino si content_type == "text/plain":
        entrada_ftu = solicitud.body
    sino si content_type == "application/x-www-form-urlencoded":
        entrada_ftu = form_a_ftu(solicitud.body)
    sino:
        respuesta.status(415)
        respuesta.json({"estado": "error", "codigo": "content_type_no_soportado"})
        retornar
    
    # Ejecutar pieza
    resultado = ejecutar_proceso("./ejecutar", stdin=entrada_ftu, timeout=30)
    
    # Determinar formato de salida
    accept = solicitud.header("Accept") o "application/json"
    
    si resultado.codigo == 0:
        salida = resultado.stdout
        http_status = 200
    sino:
        salida = resultado.stderr
        http_status = mapear_codigo_http(resultado.codigo)
    
    # Preparar respuesta
    si accept.contiene("application/json"):
        respuesta.header("Content-Type", "application/json")
        cuerpo = ftu_a_json(salida)
    sino:
        respuesta.header("Content-Type", "text/plain")
        cuerpo = salida
    
    # Headers USEE
    respuesta.header("X-USEE-Pieza", NOMBRE_PIEZA)
    respuesta.header("X-USEE-Version", VERSION_PIEZA)
    respuesta.header("X-USEE-Tiempo-Ms", tiempo_actual() - inicio)
    respuesta.header("X-Request-Id", request_id)
    
    respuesta.status(http_status)
    respuesta.enviar(cuerpo)
    
    # Registrar métricas
    registrar_metrica(request_id, http_status, tiempo_actual() - inicio)

manejar_salud(solicitud, respuesta):
    respuesta.json({
        "estado": "ok",
        "timestamp": timestamp_iso()
    })

manejar_version(solicitud, respuesta):
    respuesta.json({
        "nombre": NOMBRE_PIEZA,
        "version": VERSION_PIEZA,
        "protocolo": "usee-1.0",
        "adaptador": "http-1.0"
    })
```

---

## Consideraciones de Seguridad

### Para Adaptador JSON

| Riesgo | Mitigación |
|--------|------------|
| JSON malformado | Validar antes de procesar |
| Entrada excesivamente grande | Limitar tamaño de stdin |
| Inyección en valores | La pieza debe sanitizar sus entradas |

### Para Adaptador HTTP

| Riesgo | Mitigación |
|--------|------------|
| Ataques DoS | Limitar tasa de solicitudes |
| Cuerpos excesivamente grandes | `--max-body` limita tamaño |
| Timeouts | `--timeout` cancela solicitudes largas |
| CORS inseguro | Usar `--cors-origen` específico en producción |
| Inyección de headers | Validar y sanitizar entrada |

### Recomendaciones de Producción

```bash
# NO hacer en producción
./ejecutar-http --cors  # CORS abierto a todos

# Hacer en producción
./ejecutar-http \
  --puerto=8080 \
  --timeout=10 \
  --max-body=102400 \
  --cors-origen=https://mi-app.com \
  --log=error
```

---

## Configuración de Adaptadores

Los adaptadores pueden configurarse mediante archivo `CONFIG.adaptadores.usee`:

```
# Configuración de adaptadores

json.pretty: si
json.inferir_tipos: si

http.puerto: 8080
http.host: 0.0.0.0
http.timeout: 30
http.max_body: 1048576
http.cors: no
http.cors_origen: 
http.log: info
http.metricas: no
```

Los adaptadores leen este archivo si existe, pero los argumentos de línea de comandos tienen prioridad.

---

## Pruebas de Adaptadores

### Pruebas para Adaptador JSON

Crear directorio `pruebas/json/`:

```
pruebas/json/
├── caso-basico.entrada.json
├── caso-basico.salida.json
├── caso-anidado.entrada.json
├── caso-anidado.salida.json
├── error-json-invalido.entrada.json
└── error-json-invalido.salida.json
```

**caso-basico.entrada.json:**
```json
{
  "usuario": "test@ejemplo.com",
  "clave": "test123"
}
```

**caso-basico.salida.json:**
```json
{
  "estado": "ok",
  "sesion_id": "*",
  "expira": "*",
  "usuario_id": "usr_test_001"
}
```

### Pruebas para Adaptador HTTP

Crear directorio `pruebas/http/`:

```
pruebas/http/
├── caso-basico.solicitud
├── caso-basico.respuesta
├── caso-basico.status
├── salud.solicitud
├── salud.respuesta
└── salud.status
```

**caso-basico.solicitud:**
```
POST / HTTP/1.1
Content-Type: application/json

{"usuario": "test@ejemplo.com", "clave": "test123"}
```

**caso-basico.respuesta:**
```json
{
  "estado": "ok",
  "sesion_id": "*",
  "expira": "*"
}
```

**caso-basico.status:**
```
200
```

---

## Ejemplo Completo: Pieza Login con Adaptadores

### Estructura de Archivos

```
login/
├── PIEZA.usee
├── LEEME.md
├── ENTRADA.ejemplo
├── SALIDA.ejemplo
├── ejecutar              # Nivel 1: stdin/stdout FTU
├── ejecutar-json         # Nivel 3: stdin/stdout JSON
├── ejecutar-http         # Nivel 4: Servidor HTTP
├── CONFIG.adaptadores.usee
├── pruebas/
│   ├── caso-basico.entrada
│   ├── caso-basico.salida
│   └── ...
├── pruebas/json/
│   ├── caso-basico.entrada.json
│   ├── caso-basico.salida.json
│   └── ...
└── pruebas/http/
    ├── caso-basico.solicitud
    ├── caso-basico.respuesta
    └── ...
```

### Uso en Diferentes Contextos

#### Script de Shell

```bash
echo "usuario: juan@ejemplo.com
clave: secreto123" | ./ejecutar
```

#### Aplicación Node.js

```javascript
const { spawn } = require('child_process');

const pieza = spawn('./ejecutar-json');
pieza.stdin.write(JSON.stringify({
  usuario: 'juan@ejemplo.com',
  clave: 'secreto123'
}));
pieza.stdin.end();

pieza.stdout.on('data', (data) => {
  const resultado = JSON.parse(data);
  console.log(resultado);
});
```

#### Aplicación Web (Frontend)

```javascript
const respuesta = await fetch('http://localhost:8080/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    usuario: 'juan@ejemplo.com',
    clave: 'secreto123'
  })
});

const resultado = await respuesta.json();
console.log(resultado);
```

#### cURL desde Terminal

```bash
# Con JSON
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{"usuario": "juan@ejemplo.com", "clave": "secreto123"}'

# Con FTU
curl -X POST http://localhost:8080/ \
  -H "Content-Type: text/plain" \
  -d "usuario: juan@ejemplo.com
clave: secreto123"
```

---

## Verificación de Adaptadores

El verificador USEE incluye verificaciones adicionales para adaptadores:

### Verificaciones Opcionales

Si existe `ejecutar-json`:

- [ ] Procesa JSON válido
- [ ] Rechaza JSON inválido con error apropiado
- [ ] La salida es JSON válido
- [ ] Las pruebas en `pruebas/json/` pasan

Si existe `ejecutar-http`:

- [ ] Inicia sin errores
- [ ] `/salud` responde 200
- [ ] `/version` retorna versión correcta
- [ ] `POST /` procesa solicitudes
- [ ] Las pruebas en `pruebas/http/` pasan
- [ ] Se detiene limpiamente con SIGTERM

---

## Resumen

| Adaptador | Archivo | Propósito | Cuándo Usar |
|-----------|---------|-----------|-------------|
| **JSON** | `ejecutar-json` | Traducir JSON ↔ FTU | Integración con apps que usan JSON |
| **HTTP** | `ejecutar-http` | Exponer como servicio web | Aplicaciones web, móviles, APIs |

### Regla de Oro

> Los adaptadores traducen, no transforman.

Una pieza debe comportarse exactamente igual sin importar qué adaptador se use. La única diferencia es el formato de comunicación.

---

**Adaptadores USEE**: Múltiples puertas, misma habitación.
