Despliegue

Imagen Docker

La imagen base es node:20-slim. Instala LibreOffice + fuentes DejaVu + Liberation + fontconfig (sin esto los PDFs pueden mostrar cuadros por glifos faltantes). Corre como usuario no-root.

Build local

docker build -t converter-api:local .
docker run --rm -e API_KEY=secret -p 3000:3000 converter-api:local

docker-compose

docker-compose.yml
services:
  converter-api:
    build: .
    image: converter-api:local
    container_name: converter-api
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file:
      - .env
    volumes:
      - ./templates:/app/templates
      - ./images:/app/images
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
Volúmenes

./templates persiste las plantillas registradas. Si lo tiras, pierdes el catálogo entero (no hay backup automático). Considera montar una ruta en disco persistente o respaldar periódicamente.

./images es el contenido referenciable como path local en los renders (logos corporativos, etc.). Se gestiona fuera del API.

Variables de entorno en producción

  • API_KEY — genera una con node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" (64 chars hex) y guárdala en tu secret manager.
  • NODE_ENV=production — desactiva pino-pretty, salida JSON pura.
  • LOG_LEVEL=info en producción; warn si tu agregador de logs cobra por volumen.
  • LO_CONCURRENCY=1 es el default seguro. Subirlo requiere validar que LibreOffice usa profile dirs separados (la librería actual sí, pero ojo con futuras versiones).

Healthcheck

El Dockerfile ya incluye HEALTHCHECK basado en curl /health. Para Kubernetes:

livenessProbe:
  httpGet:
    path: /health
    port: 3000
  periodSeconds: 30
  timeoutSeconds: 5
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /health
    port: 3000
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3
  initialDelaySeconds: 10
readinessProbe vs liveness
Considera lo que devuelve /health: 200 ok, 200 degraded (sin LibreOffice; el .docx sigue funcionando), 503 down (templates dir inaccesible). Si tu producto requiere PDF, configura readinessProbe que falle en degraded también.

Logs

Cada request emite una línea JSON con campos requestId, method, url, statusCode, responseTime. Errores 5xx incluyen stack trace. Datos sensibles redactados:

  • req.headers["x-api-key"]
  • req.headers.authorization
  • req.body.data (payload del render — puede contener PII)

Persistencia

Sin DB. El estado vive 100% en filesystem en TEMPLATES_DIR (<slug>.docx y <slug>.schema.json opcional). Para restore, simplemente coloca los archivos en el directorio y reinicia el servicio (schemaService tiene cache que se invalida en eventos de cambio, pero un reinicio fresco también limpia).

Métricas / observabilidad

v1 no expone métricas Prometheus. Si lo necesitas, los hooks naturales son:

  • Wrappear renderService.render y pdfService.convert con un counter + histogram de duración.
  • Counter por error.code en el errorHandler middleware.
  • libreoffice_available gauge desde el pdfService.isAvailable cache.

Tests

npm test                          # unit + integration (PDF gated off)
RUN_PDF_TESTS=1 npm test          # incluye renders reales con LibreOffice
npm run test:coverage             # con thresholds (services/utils ≥ 80%)

En CI, recomendado correr npm test sin LibreOffice (más rápido) y un job aparte con LibreOffice para los PDF tests.