# Capacidad: Operación, seguridad y conservación

Despliegue en el host, planificación, logs, certificado, backup y conservación legal. Decisiones de
referencia: **D5** (pool FPM), **D6** (systemd timers), **D27** (certificado), **D28** (logs),
**D29** (conservación), **D30** (reloj).

## ADDED Requirements

### Requirement: Despliegue PHP sin afectar a los demás sitios del host

El SIF DEBE servirse con **PHP 8.3** mediante un **pool FPM dedicado**, sin alterar la versión de PHP
de ningún otro vhost del servidor.

1. Pool en `/etc/php/8.3/fpm/pool.d/verifactu.conf`, con `listen` a un socket propio
   (`/run/php/php8.3-fpm-verifactu.sock`), usuario y grupo dedicados `verifactu`.
2. En el vhost, un `<FilesMatch \.php$>` apuntando a ese socket **dentro del `<Directory>`** del sitio.
3. **NUNCA** ejecutar `a2enconf php8.3-fpm` ni habilitar la conf global de una versión nueva.
4. Antes de instalar cualquier paquete `phpX.Y-fpm` nuevo, crear el fichero vacío
   `/var/lib/apache2/conf/disabled_by_admin/phpX.Y-fpm`.

NO DEBE modificarse `www.conf` del paquete.

#### Scenario: El resto de sitios no cambia de versión

- **WHEN** se despliega el pool y se recarga Apache
- **THEN** los demás vhosts siguen sirviéndose con PHP 8.1 por la regla global
- **AND** solo `verifactu.xtrf.abroadlink.com` usa PHP 8.3

#### Scenario: Instalación de una versión nueva de PHP

- **WHEN** se instale en el futuro un paquete `phpX.Y-fpm`
- **THEN** el marcador en `disabled_by_admin` impide el auto-enable del postinst
- **AND** `a2query -c phpX.Y-fpm` devuelve 32 y el log dice "Not enabling PHP X.Y FPM by default"

#### Scenario: Dimensionado del pool

- **WHEN** se configura el pool
- **THEN** `pm.max_children` es como mínimo **10** y `memory_limit` como mínimo **256M**
- **AND** no se deja el valor por defecto de Debian (5), que es el problema ya detectado en el pool de
  `certlink`

---

### Requirement: Planificación con systemd, con ruta explícita al binario

Las tareas periódicas DEBEN ejecutarse mediante **unidades systemd**, con `ExecStart` apuntando
explícitamente a **`/usr/bin/php8.3`**.

NO DEBEN añadirse al crontab de `root` ni invocar `/usr/bin/php`.

| Unidad | Frecuencia | Cometido |
|---|---|---|
| `verifactu-worker.timer` | 1 min | Procesa la cola: PDF, envío AEAT, notificación XTRF |
| `verifactu-integridad.timer` | diaria 03:15 | Verificación de la cadena de huellas |
| `verifactu-vigilancia.timer` | diaria 08:00 | Certificado, reloj, montaje, incidencias |

Todas DEBEN ser `Type=oneshot` y NO DEBEN solaparse.

#### Scenario: No se toca el crontab existente

- **WHEN** se despliega la planificación
- **THEN** las 7 tareas de producción del crontab de `root` quedan intactas
- **AND** ninguna nueva tarea invoca `/usr/bin/php`, que está pinneado a PHP 8.1 a propósito

#### Scenario: No hay ejecuciones solapadas

- **WHEN** una ejecución del worker se alarga más de un minuto
- **THEN** systemd no inicia una segunda instancia
- **AND** además el worker toma un cerrojo de aplicación como segunda barrera

---

### Requirement: Worker de cola

El worker DEBE, en cada ejecución y en este orden:

1. Comprobar si la cola está pausada; si lo está, terminar.
2. Desplegar PDFs pendientes cuyo momento de reintento haya llegado.
3. Si el control de flujo lo permite y no hay bloqueo de cabecera, enviar el siguiente registro.
4. Notificar a XTRF las facturas con desenlace completo.
5. Liberar *leases* caducados de registros que quedaron en `ENVIANDO`.

#### Scenario: Registro atascado en ENVIANDO

- **WHEN** un registro lleva más de 10 minutos en `ENVIANDO` con el lease caducado
- **THEN** el worker lo pasa a `ESTADO_INDETERMINADO`
- **AND** **no** lo reenvía: exige consulta previa, como cualquier otro indeterminado

#### Scenario: Cola pausada

- **WHEN** `cola_pausada = 1`
- **THEN** el worker no envía nada a la AEAT
- **AND** tampoco genera PDFs ni notifica a XTRF, para no avanzar el circuito con la cola detenida

---

### Requirement: Custodia del certificado

El certificado DEBE vivir **fuera del docroot**, en `/etc/verifactu/certs/`.

- El `.p12` original se archiva con permisos `0400`, propietario `root:root`, y **no** se usa en
  caliente.
- Se derivan `cliente.crt.pem` y `cliente.key.pem`, con permisos `0400` y propietario
  `verifactu:verifactu`.
- La contraseña vive en `/etc/verifactu/verifactu.env` (`0640`, `root:verifactu`).

La contraseña NO DEBE almacenarse en la base de datos, ni en el docroot, ni en el repositorio, ni
aparecer en logs.

#### Scenario: El certificado no es accesible por HTTP

- **WHEN** se intenta acceder a cualquier ruta bajo el docroot que pudiera contener el certificado
- **THEN** no existe: los ficheros están fuera de `/var/www/html/verifactu`

#### Scenario: Conversión desde P12

- **WHEN** la conversión del `.p12` falla por algoritmo heredado en OpenSSL 3
- **THEN** el procedimiento documentado indica reintentarla con el proveedor `-legacy`
- **AND** la conversión se hace **una sola vez** en la instalación, no en cada envío

#### Scenario: Permisos incorrectos

- **WHEN** la comprobación de salud detecta que los PEM son legibles por otros usuarios
- **THEN** se abre una incidencia de severidad alta

---

### Requirement: Vigilancia de la caducidad del certificado

El SIF DEBE comprobar diariamente la fecha de caducidad y abrir incidencia a **45, 30, 15, 7 y 1**
días. Al llegar a 0, DEBE pausar la cola.

#### Scenario: Aviso anticipado

- **WHEN** faltan 45 días
- **THEN** se abre una incidencia informativa
- **AND** se repite el aviso en los siguientes umbrales

#### Scenario: Certificado caducado

- **WHEN** la fecha ya pasó
- **THEN** `cola_pausada = 1` con `motivo_pausa = "CERTIFICADO_CADUCADO"`
- **AND** no se intentan más envíos, que solo producirían fallos TLS

---

### Requirement: Logs

DEBEN existir cuatro canales en `/var/log/verifactu/`, rotados por `logrotate` (diario, 90 días,
comprimidos): `app`, `aeat`, `http` y `error`.

Los logs NO DEBEN contener NIF de clientes, importes, nombres de clientes ni el contenido de los XML.

El XML íntegro enviado y recibido DEBE guardarse **en la base de datos**, no solo en logs.

#### Scenario: Sin datos personales en los logs

- **WHEN** se procesa una factura
- **THEN** el log `aeat` registra endpoint, desenlace, estado, código y duración
- **AND** no registra el NIF del cliente ni el importe

#### Scenario: El XML sobrevive a la rotación

- **WHEN** han pasado más de 90 días desde un envío
- **THEN** los logs de esa fecha ya han rotado
- **AND** el XML enviado y la respuesta siguen íntegros en la base de datos

---

### Requirement: Conservación y copia de seguridad

El SIF NO DEBE purgar automáticamente registros de facturación, envíos ni auditoría.

DEBE existir un `mysqldump --single-transaction` diario del esquema `verifactu` en
`/var/backups/verifactu/`, con retención local de 90 días, **y copia fuera del host** (**DEP-8**).

#### Scenario: No hay purga automática

- **WHEN** el sistema lleva años en funcionamiento
- **THEN** todos los registros siguen consultables
- **AND** no existe ninguna tarea programada que los borre

#### Scenario: Copia consistente

- **WHEN** se ejecuta el volcado diario
- **THEN** usa `--single-transaction` para no bloquear la operación
- **AND** el volcado incluye triggers y rutinas

#### Scenario: El backup local no basta

- **WHEN** se revisa el plan de conservación
- **THEN** consta explícitamente que un backup que vive solo en el mismo host no protege frente a la
  pérdida del host
- **AND** queda como dependencia pendiente la copia externa

> El art. 8.c del RD 1007/2023 exige conservación *"durante el plazo previsto en la Ley 58/2003"*, y
> que el sistema cuente con un procedimiento de descarga y archivo seguro exportable. El plazo general
> de prescripción tributaria es de 4 años **[A VERIFICAR: el RD remite a la Ley 58/2003 sin fijar la
> cifra; los 4 años no constan literalmente en los documentos locales]**. Dado el volumen real
> (~240 registros/año), conservar indefinidamente cuesta menos que razonar sobre el plazo.

---

### Requirement: Sincronización horaria

El host DEBE tener NTP activo y la zona `Europe/Madrid`. La comprobación diaria DEBE verificarlo.

#### Scenario: Reloj desincronizado

- **WHEN** la comprobación detecta que NTP no está sincronizado
- **THEN** se abre una incidencia
- **AND** el mensaje explica que provoca el código **2004** en los registros generados

#### Scenario: Estado verificado en el host

- **WHEN** se revisó el host el 2026-09-01
- **THEN** NTP estaba activo, el reloj sincronizado y la zona era `Europe/Madrid (CEST, +0200)`
- **AND** no hace falta cambiar nada en este punto

---

### Requirement: Mitigaciones mientras el acceso sea público

Mientras `auth.habilitada = false`, DEBEN estar operativas estas mitigaciones:

1. `Require all denied` sobre `/var/www/html/verifactu/invoices`.
2. Cuota diaria `endpoint.max_facturas_dia` que pausa la cola al superarse.
3. Pausa manual de cola disponible en un clic.
4. Registro en auditoría de todo acceso no autenticado, con IP.
5. Ningún envío síncrono a la AEAT desde el endpoint.

#### Scenario: Abuso del endpoint público

- **WHEN** un tercero envía cientos de facturas falsas
- **THEN** la cuota diaria se agota y la cola se pausa
- **AND** las facturas falsas quedan como registros locales pendientes, **no remitidas** a la AEAT
- **AND** se abre una incidencia de severidad alta

> Esto acota el daño pero **no lo elimina**: los registros espurios ya han consumido eslabones de la
> cadena. La única mitigación completa de RIESGO-1 es restringir el acceso en NPM. Ver `proposal.md`
> §5.

#### Scenario: Restricción por IP en NPM

- **WHEN** se aplique la restricción recomendada en Nginx Proxy Manager
- **THEN** solo la IP de XTRF puede alcanzar el endpoint
- **AND** no hace falta ningún cambio en el SIF

---

### Requirement: Declaración responsable del productor

DEBE producirse y conservarse una **declaración responsable** por escrito, del productor del sistema
(AbroadLink, en autodesarrollo), que certifique que el sistema cumple el RD 1007/2023, y que DEBE
constar **de modo visible en el propio sistema informático**, en cada una de sus versiones.

Es un **entregable documental**, no software (**DEP-9**).

#### Scenario: Declaración visible en el sistema

- **WHEN** un usuario accede a la interfaz
- **THEN** existe una pantalla o sección accesible donde consta la declaración responsable de la
  versión en uso
- **AND** indica la versión del sistema, que coincide con el campo `Version` del bloque
  `SistemaInformatico`

#### Scenario: Cambio de versión

- **WHEN** se despliega una versión nueva del SIF
- **THEN** se actualiza el campo `Version` de la configuración
- **AND** se conserva la declaración responsable de la versión anterior

> El art. 13 del RD 1007/2023 obliga al productor a certificar mediante declaración responsable, a que
> conste por escrito y de modo visible en el propio sistema en cada versión, y a conservar las
> declaraciones de todas las versiones producidas.

---

### Requirement: Identificación del sistema informático

El bloque `SistemaInformatico` DEBE informar sus **9 campos**, con estos valores fijos por decisión
del proyecto:

| Campo | Valor |
|---|---|
| `NombreRazon` | Razón social de AbroadLink (**DEP-6**) |
| `NIF` | NIF de AbroadLink (**DEP-6**) |
| `NombreSistemaInformatico` | Nombre del SIF, máx. **30** caracteres |
| `IdSistemaInformatico` | **2 caracteres**, cada uno letra mayúscula (excepto `Ñ`) o dígito |
| `Version` | Versión del SIF, máx. 50 |
| `NumeroInstalacion` | Identificador de la instalación, máx. 100 |
| `TipoUsoPosibleSoloVerifactu` | **`S`** |
| `TipoUsoPosibleMultiOT` | **`N`** |
| `IndicadorMultiplesOT` | **`N`** |

`IdSistemaInformatico` y `NumeroInstalacion` son **inmutables** una vez remitido el primer registro a
producción.

#### Scenario: Validación del formato de IdSistemaInformatico

- **WHEN** se configura `IdSistemaInformatico`
- **THEN** se exige exactamente 2 posiciones, cada una letra mayúscula distinta de `Ñ` o dígito
- **AND** se rechaza cualquier otro valor

#### Scenario: Intento de cambio tras el primer envío

- **WHEN** se intenta cambiar `NumeroInstalacion` con la cadena ya inicializada
- **THEN** la interfaz advierte de que altera la identidad de la cadena ante la AEAT
- **AND** exige doble confirmación y deja registro de severidad máxima
