# Plan de implementación — Fase 1 (registros de alta)

Diez bloques ordenados, pensados para **delegarse a distintos implementadores**. Cada bloque declara
sus dependencias, sus entregables y sus **criterios de aceptación verificables**.

**Regla de oro del plan:** F3 (huella) y F4 (cadena) son el corazón del sistema y no dependen de
ninguna dependencia externa pendiente. Se pueden hacer **ya**, en paralelo con la espera del
certificado y del JSON real.

| Bloque | Depende de | Dependencia externa | ¿Puede empezar ya? |
|---|---|---|---|
| F1 Infraestructura | — | — | **Sí** |
| F2 Endpoint y modelo | F1 | DEP-1 (cierre) | **Sí**, con mapeo provisional |
| F3 Huella | F1 | — | **Sí** |
| F4 Cadena y registro | F2, F3 | — | **Sí** |
| F5 Cliente AEAT | F4 | DEP-3, DEP-5, DEP-6 | Parcial (sin certificado no hay llamada real) |
| F6 PDF y QR | F4 | DEP-2, DEP-7 | Parcial |
| F7 Integración XTRF | F5, F6 | DEP-4 | No |
| F8 Interfaz interna | F4, F5 | — | Sí, tras F4 |
| F9 Operación | F5, F6 | DEP-8, DEP-9 | Parcial |
| F10 Validación extremo a extremo | Todos | Todas | No |

---

## F1 — Infraestructura y esqueleto

**Depende de:** nada. **Desbloquea:** todo lo demás.

- [ ] F1.1 Crear el pool FPM `/etc/php/8.3/fpm/pool.d/verifactu.conf` con socket propio, usuario y
      grupo `verifactu`, `pm.max_children >= 10`, `memory_limit = 256M`, `date.timezone = Europe/Madrid`.
- [ ] F1.2 Añadir el `<FilesMatch \.php$>` con ese socket **dentro del `<Directory>`** del vhost 8089.
      Verificar que ningún otro sitio cambia de versión.
- [ ] F1.3 Crear el marcador `/var/lib/apache2/conf/disabled_by_admin/` para versiones futuras de PHP.
- [ ] F1.4 Crear el esquema `verifactu` en MariaDB con las 13 tablas de `design.md` D25, todas InnoDB
      y `utf8mb4_unicode_ci`.
- [ ] F1.5 Implementar los triggers de inmutabilidad y de bloqueo de `DELETE` sobre
      `registros_facturacion` (D24).
- [ ] F1.6 Inicializar los singletons `cadena_estado` (id=1) y `control_flujo` (id=1,
      `tiempo_espera_actual_s = 60`).
- [ ] F1.7 Composer con `mpdf/mpdf`, `endroid/qr-code`, `twig/twig`, `monolog/monolog`. Autoload PSR-4.
- [ ] F1.8 Front controller, enrutador mínimo, contenedor de dependencias, capa PDO, cuatro canales de
      log (D28) y `logrotate`.
- [ ] F1.9 Cargar `catalogo_errores_aeat` desde `errores.properties` **convirtiendo de latin-1 a
      UTF-8**.
- [ ] F1.10 Bind mount de `invoices` en `/etc/fstab` con `x-systemd.requires-mounts-for`, más
      `Require all denied` en el vhost.

**Criterios de aceptación**

1. `curl http://192.168.123.250:8089/` responde y `phpinfo()` muestra **PHP 8.3**.
2. Los demás vhosts del host siguen sirviéndose con **PHP 8.1**.
3. `UPDATE registros_facturacion SET huella=...` falla con el mensaje del trigger.
4. `DELETE FROM registros_facturacion` falla.
5. El catálogo tiene **247** códigos, con 44 / 193 / 10 por categoría, y los acentos se ven bien.
6. `https://verifactu.xtrf.abroadlink.com/invoices/…` devuelve **403**.

---

## F2 — Modelo de facturas y endpoint de entrada

**Depende de:** F1. **Cierra con:** DEP-1.

- [ ] F2.1 Implementar `POST /api/v1/facturas`: límite de 2 MiB, parseo JSON, respuesta de error
      uniforme con `id_peticion`.
- [ ] F2.2 Validación estructural del payload, acumulando **todos** los problemas.
- [ ] F2.3 `MapeadorXtrf` aislado, con la tabla provisional de `specs/endpoint-entrada/spec.md` §4.
- [ ] F2.4 Idempotencia por `clave_idempotencia` con `UNIQUE`, resuelta por violación de restricción y
      **no** por comprobación previa.
- [ ] F2.5 Rechazo `409` ante misma clave con `json_hash` distinto, con incidencia.
- [ ] F2.6 Cuota diaria `endpoint.max_facturas_dia` con pausa de cola al superarse.
- [ ] F2.7 Middleware `AuthGuard` y lectura de `X-Verifactu-Token` con `hash_equals()`, con
      `auth.habilitada = false`.
- [ ] F2.8 Rechazo explícito de tipos fuera de alcance (R1–R5, F3) y de monedas distintas de EUR.

**Criterios de aceptación**

1. Payload válido → `202` con `id_registro`, `ref_externa` y `huella`.
2. Repetir el mismo payload → `200` con el **mismo** `id_registro`, sin crear nada.
3. Repetir con payload alterado → `409` e incidencia abierta.
4. Dos peticiones simultáneas idénticas → un `202` y un `200`, nunca dos registros.
5. JSON inválido → `400`; regla fiscal incumplida → `422` con lista de `VF-nn`.
6. `GET /api/v1/facturas` → `405` con `Allow: POST`.

---

## F3 — Motor de huella

**Depende de:** F1. **Sin dependencias externas: hacer ya.**

- [ ] F3.1 Objeto de valor `ValoresRegistroAlta` que produce el **mapa ordenado canonizado** (D7).
- [ ] F3.2 Canonicalización: importes a 2 decimales con punto; fechas `DD-MM-YYYY`;
      `FechaHoraHusoGenRegistro` ISO 8601 con huso explícito.
- [ ] F3.3 Construcción de la cadena `nombre=valor&…` con recorte de extremos y campo vacío como
      `nombre=`.
- [ ] F3.4 SHA-256 sobre UTF-8, salida hexadecimal en mayúsculas de 64 caracteres.
- [ ] F3.5 Variante de anulación (nombres `…Anulada`), solo para dejar el motor completo.

**Criterios de aceptación**

1. **Vector oficial 1** reproduce `3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60`.
2. **Vector oficial 2** reproduce `F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97`.
3. **Vector oficial 3** reproduce `177547C0D57AC74748561D054A9CEC14B4C4EA23D1BEFD6F2E69E3A388F90C68`.
4. `" 12345678 / G33 "` produce `12345678 / G33`: se recortan los extremos, se conservan los interiores.
5. `123.1` y `123.10` producen ambos `123.10` en la cadena.
6. **Sin estos seis en verde, el bloque no está terminado.**

---

## F4 — Cadena, registro y validación fiscal

**Depende de:** F2, F3.

- [ ] F4.1 Escritor único con `SELECT ... FOR UPDATE` sobre `cadena_estado` (D8).
- [ ] F4.2 Encadenamiento como choice de dos ramas; `PrimerRegistro=S` solo con la cadena sin
      inicializar; prohibir `PrimerRegistro=N`.
- [ ] F4.3 Persistencia del registro con `cadena_string` y `xml_registro`.
- [ ] F4.4 `RefExterna = AL-{id_registro}`, con `UNIQUE`.
- [ ] F4.5 Validador fiscal con las reglas **VF-01 … VF-31**, cada una con su código AEAT asociado.
- [ ] F4.6 Agregación del desglose fiscal con límite duro de 12 detalles.
- [ ] F4.7 Verificación de integridad de la cadena (5 comprobaciones de
      `specs/registro-huella-cadena/spec.md`).

**Criterios de aceptación**

1. Dos peticiones concurrentes producen `cadena_orden` consecutivos sin huecos, y la `huella_anterior`
   de la segunda es la `huella` de la primera.
2. Un fallo a mitad deja `cadena_estado` intacto y no consume `cadena_orden`.
3. Un registro `N2` genera XML **sin** los elementos `TipoImpositivo`, `CuotaRepercutida`,
   `TipoRecargoEquivalencia` ni `CuotaRecargoEquivalencia` (VF-03).
4. Un registro `S2` genera `TipoImpositivo` y `CuotaRepercutida` **a cero explícito** (VF-05).
5. Un descuadre de 0,01 € entre `ImporteTotal` y el desglose bloquea el registro (VF-18).
6. Una factura de 200 líneas todas `N2` produce **un solo** `DetalleDesglose`.
7. La verificación de integridad detecta un eslabón manipulado a mano en base de datos.

---

## F5 — Cliente del servicio web de la AEAT

**Depende de:** F4. **Bloqueado para pruebas reales por:** DEP-3, DEP-5, DEP-6.

- [ ] F5.1 Generador de XML con `XMLWriter`: namespaces sin `V1.0`, `elementFormDefault=qualified`,
      orden posicional de los 31 elementos, `IDVersion` dentro del registro y **no** en la cabecera.
- [ ] F5.2 Validación del XML generado contra los XSD locales, con catálogo que redirige
      `xmldsig-core-schema.xsd` a la copia local.
- [ ] F5.3 Transporte cURL con mTLS, `SOAPAction: ""`, verificación TLS activa, timeouts.
- [ ] F5.4 Conversión del `.p12` a PEM en la instalación (con `-legacy` si hace falta) y custodia según
      D27.
- [ ] F5.5 Parseo de los **cuatro desenlaces** (respuesta, fault `Client`, fault `Server`, sin
      respuesta).
- [ ] F5.6 Parseo de la respuesta: CSV, `DatosPresentacion`, `TiempoEsperaEnvio`, `EstadoEnvio`,
      `RespuestaLinea`, `RegistroDuplicado`; correlación por `RefExterna`; **tres enums de estado
      distintos**.
- [ ] F5.7 Control de flujo con `proximo_envio_permitido_at` y valor inicial 60 s.
- [ ] F5.8 Cola FIFO con bloqueo de cabecera ante `ESTADO_INDETERMINADO`.
- [ ] F5.9 `ConsultaFactuSistemaFacturacion` con `PeriodoImputacion` obligatorio, registrada en
      `consultas_aeat`.
- [ ] F5.10 Las tres operativas de alta, con las dos manuales **sin disparo automático**.
- [ ] F5.11 Indicador `RemisionVoluntaria/Incidencia` tras una interrupción registrada.
- [ ] F5.12 Persistencia probatoria de cada envío en base de datos.

**Criterios de aceptación**

1. El XML generado valida contra los XSD **sin acceso a la red**.
2. La petición HTTP incluye literalmente `SOAPAction: ""`.
3. La `Cabecera` no contiene `IDVersion`; cada `RegistroAlta` empieza por `<IDVersion>1.0</IDVersion>`.
4. Con certificado real, una factura F1 de cliente español llega a `CORRECTO` con CSV en
   preproducción.
5. Una factura `N2` de cliente intracomunitario llega a `CORRECTO` en preproducción.
6. Un rechazo provocado deja `RECHAZADO` con código y descripción traducida, y **no** bloquea la cola.
7. Un timeout provocado deja `ESTADO_INDETERMINADO` y **sí** bloquea la cola.
8. Un reenvío sin consulta previa es rechazado por el sistema.
9. Tras una respuesta con `TiempoEsperaEnvio = 60`, el siguiente envío no ocurre antes de 60 s.

---

## F6 — PDF y QR

**Depende de:** F4. **Cierra con:** DEP-2, DEP-7.

- [ ] F6.1 Constructor de la URL del QR: 4 parámetros en orden, `rawurlencode` por valor, importe con
      2 decimales, sin `formato` ni `idioma`.
- [ ] F6.2 Generación del QR con nivel **M**, 40 mm, zona muda 6 mm.
- [ ] F6.3 Plantilla Twig + mPDF replicando la plantilla de XTRF (provisional hasta DEP-2).
- [ ] F6.4 Catálogos de traducción y cascada idioma → `en` → `es`.
- [ ] F6.5 Leyendas `QR tributario:` y `Factura verificable en la sede electrónica de la AEAT`
      **siempre en castellano**, en la primera página y una sola vez.
- [ ] F6.6 Despliegue en el share: verificar montaje, escribir temporal, renombrar, releer y verificar
      hash.
- [ ] F6.7 Archivado del PDF original de XTRF y copia propia inmutable con SHA-256.
- [ ] F6.8 Detección de colisiones con otra factura, sin sobrescribir.
- [ ] F6.9 Reintentos escalonados y acción manual "Regenerar PDF".
- [ ] F6.10 Controlador `GET /registro/{id}/pdf` con streaming y auditoría.

**Criterios de aceptación**

1. La URL del QR de `195/2026` codifica la barra como `%2F`.
2. El importe usa punto, nunca coma.
3. `numserie`, `fecha` e `importe` del QR coinciden carácter a carácter con los del registro.
4. Una factura en alemán tiene el cuerpo en alemán y **las leyendas en castellano**.
5. Una factura con líneas en griego, turco, checo y polaco renderiza todos los caracteres.
6. En una factura de 19 páginas el QR aparece **solo** en la página 1.
7. Con el share desmontado, el PDF se genera y queda pendiente de despliegue, sin perder nada.
8. La regeneración del PDF produce un QR idéntico al original.

---

## F7 — Integración con XTRF

**Depende de:** F5, F6. **Bloqueado por:** DEP-4.

- [ ] F7.1 `XtrfClient` aislado, con el contrato provisional y toda la parametrización en configuración.
- [ ] F7.2 Interruptor `xtrf.habilitado`, por defecto `false`.
- [ ] F7.3 Regla de éxito: AEAT terminal correcto **y** `pdf_estado = DESPLEGADO`.
- [ ] F7.4 Reintentos 1/5/30/180/1440 min ante 5xx y errores de red; **sin reintento** ante 4xx.
- [ ] F7.5 Idempotencia por desenlace y nueva notificación tras una subsanación.
- [ ] F7.6 Enmascarado del token en UI, logs y auditoría.

**Criterios de aceptación**

1. Con `xtrf.habilitado = false`, todo el circuito funciona y las facturas quedan `PENDIENTE` sin
   incidencia.
2. AEAT correcto con PDF pendiente **no** notifica.
3. `ACEPTADO_CON_ERRORES` con subsanación pendiente notifica **fallo**.
4. Un 4xx no se reintenta; un 5xx sí.
5. El token no aparece en ningún log, auditoría ni pantalla.

---

## F8 — Interfaz interna

**Depende de:** F4, F5.

- [ ] F8.1 Área (a): listado con filtros y detalle con las 7 secciones, incluida la `cadena_string` y
      la acción "Recalcular y comparar".
- [ ] F8.2 Área (b): tres bloques (pendientes, bloqueados, fallidos) con errores traducidos.
- [ ] F8.3 Las 7 acciones correctoras, todas manuales, con motivo escrito obligatorio y auditoría.
- [ ] F8.4 Deshabilitar "Reenviar como ALTA" mientras no haya consulta previa.
- [ ] F8.5 Área (c): integridad, certificado con semáforo, último contacto, control de flujo, montaje,
      reloj, incidencias.
- [ ] F8.6 Área (d): configuración por bloques, secretos enmascarados, doble confirmación en
      parámetros críticos.
- [ ] F8.7 Indicador permanente de entorno y de cola pausada.
- [ ] F8.8 Exportación de registros y XML por rango de fechas.
- [ ] F8.9 `AuthGuard` envolviendo **todas** las rutas, cada una con su rol declarado.

**Criterios de aceptación**

1. No existe ningún control de "reintentar todos".
2. Toda acción correctora exige motivo y deja entrada en `auditoria` con actor e IP.
3. El error 1245 se muestra con su texto oficial y acentos correctos.
4. Activar `auth.habilitada` hace que las rutas exijan sesión **sin tocar código**.
5. Con la cola bloqueada, la pantalla de errores muestra el registro causante y cuántos esperan detrás.
6. Cambiar el `NumeroInstalacion` con la cadena inicializada exige doble confirmación y avisa del 2007.

---

## F9 — Operación

**Depende de:** F5, F6. **Cierra con:** DEP-8, DEP-9.

- [ ] F9.1 Tres unidades systemd con `ExecStart=/usr/bin/php8.3`, `Type=oneshot`, sin solapamiento.
- [ ] F9.2 Worker con los 5 pasos en orden y liberación de leases caducados.
- [ ] F9.3 Vigilancia de caducidad del certificado con umbrales 45/30/15/7/1 y pausa a 0.
- [ ] F9.4 Verificación diaria de integridad de la cadena.
- [ ] F9.5 Comprobación de reloj, montaje CIFS y permisos de los PEM.
- [ ] F9.6 `mysqldump --single-transaction` diario con retención 90 días y copia externa.
- [ ] F9.7 Pantalla con la declaración responsable de la versión en uso.

**Criterios de aceptación**

1. El crontab de `root` queda **intacto**, con sus 7 tareas.
2. Ninguna unidad nueva invoca `/usr/bin/php`.
3. Un registro atascado en `ENVIANDO` con lease caducado pasa a `ESTADO_INDETERMINADO`, **no** se
   reenvía.
4. Con la cola pausada, el worker no envía, no genera PDFs y no notifica.
5. Un certificado a 30 días abre incidencia; caducado, pausa la cola.
6. El volcado diario existe, es restaurable y se copia fuera del host.

---

## F10 — Validación extremo a extremo en preproducción

**Depende de:** todos los bloques y todas las dependencias externas.

- [ ] F10.1 Factura F1 de cliente español (IVA 21 %): circuito completo hasta categoría de éxito en
      XTRF.
- [ ] F10.2 Factura F1 de cliente intracomunitario (`N2`, `IDOtro/IDType=02`): ídem.
- [ ] F10.3 Rechazo provocado → `RECHAZADO` → **ALTA POR RECHAZO** manual → `CORRECTO`.
- [ ] F10.4 Aceptado con errores provocado → subsanación manual → registro nuevo encadenado.
- [ ] F10.5 Timeout provocado → `ESTADO_INDETERMINADO` → consulta → resolución.
- [ ] F10.6 Share desmontado durante el proceso → PDF pendiente → recuperación automática.
- [ ] F10.7 XTRF caído → reintentos → recuperación.
- [ ] F10.8 Verificación de integridad sobre toda la cadena de pruebas.
- [ ] F10.9 Simulacro de restauración del backup y verificación de la cadena restaurada.

**Criterios de aceptación (los 6 del `proposal.md` §6)**

1. F1 española recorre el circuito completo con CSV.
2. F1 intracomunitaria idem.
3. Los tres vectores de huella en verde.
4. Rechazo → alta por rechazo manual desde la interfaz.
5. `SoapFault`/timeout → indeterminado → cola bloqueada → consulta obligatoria.
6. Verificación de integridad OK sobre la cadena completa.

---

## Orden recomendado de ataque

```
        ┌── F3 (huella) ────────────┐
F1 ─────┤                           ├── F4 ──┬── F5 ──┬── F7 ──┐
        └── F2 (endpoint) ──────────┘        │        │        ├── F10
                                             ├── F6 ──┘        │
                                             └── F8 ── F9 ─────┘
```

**Empezar por F1 + F3 en paralelo.** F3 no depende de ninguna dependencia externa, es el núcleo del
sistema y tiene criterios de aceptación objetivos y cerrados (los tres vectores oficiales). Tener F3
verde desde el principio elimina el riesgo técnico mayor del proyecto antes de que llegue el
certificado.
