# Sprint 1.5a · Template PDF NI

> Replica fiel del PDF de Infinia (contratos 1149/1150) con marca de agua del logo.

## Stack

- **Library:** DomPDF
- **Formato:** Carta (216×330 mm) — formato legacy chileno (NO A4)
- **Orientación:** Portrait
- **Marca de agua:** logo del tenant a tamaño grande, color original, opacidad ~15%, centrado en página

## Setup

```bash
composer require dompdf/dompdf
```

## Estructura visual del PDF

```
┌────────────────────────────────────────────────────┐
│ [CONTRATO Nº 1149]                  [LOGO INFINIA] │ ← Header
│ Fecha: 03/05/2026 Hora: 01:31:09                   │
├────────────────────────────────────────────────────┤
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Card 1
│ │ 1. DATOS DEL CONTRATANTE                      │   │
│ ├──────────────────────────────────────────────┤   │
│ │ Nombre:    Juan Claudio Amador  RUT: 10.231...│   │
│ │ Tel 1:     ...     Tel 2:    ...              │   │
│ │ Correo:    jccarrasco71@gmail.com             │   │
│ │ Dirección: ZAPALLAR 2341 METROPOLITANA...     │   │
│ │ Parentesco: HIJO(A)                           │   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Card 2
│ │ 2. DATOS DEL FALLECIDO                        │   │
│ ├──────────────────────────────────────────────┤   │
│ │ Nombre Completo: Francisco del Rosario Garrido│   │
│ │ Falleció en: Hospital   Edo. Civil: CASADO(A) │   │
│ │ Nacionalidad: CHILE                           │   │
│ │ Fecha Nacim.: 03/12/1936  Defunción: 02/05/26 │   │
│ │ Entidad Previsional: HABITAT                  │   │
│ │ Entidad Previsional 2: NO   Previsional 3: NO │   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Card 3
│ │ 3. DETALLES DEL SERVICIO FUNERARIO            │   │
│ ├──────────────────────────────────────────────┤   │
│ │ Urna: SOBREMEDIDA TERCIADO LISO  $ 1.200.000  │   │
│ │ Capilla: CIRIOS  Carroza: BLANCA  Auto: 0     │   │
│ │ Van: 0   Cruz: SI   Tarjetero: NO             │   │
│ │ Libro Condolencias: SI  Tarjeta: SI           │   │
│ │ Arreglo: SI  Cert. Médica: NO                 │   │
│ │ Tramit. Reg. Civil: SI  Cafetería: SI         │   │
│ │ MEDIDAS DEL COFRE: 48 / 61 / 193 cm           │   │
│ │ OTROS SERVICIOS:                              │   │
│ │ TRASLADO = $ 180.000                          │   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Card 4
│ │ 4. COMPLEMENTO AL SERVICIO FUNERARIO          │   │
│ ├──────────────────────────────────────────────┤   │
│ │ Lugar de Velación: -                          │   │
│ │ Lugar de Sepultación: -                       │   │
│ │ Fecha Funeral: POR CONFIRMAR                  │   │
│ │ Hora Funeral: POR CONFIRMAR                   │   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Card 5
│ │ 5. RESUMEN DE VALORES                         │   │
│ ├──────────────────────────────────────────────┤   │
│ │ Valor lista = $ 1.380.000  │ APORTE PREVIS:   │   │
│ │ Total Servicio = $ 1.380K  │ HABITAT $595K    │   │
│ │ Aporte Prev. = $ 595.000   │                  │   │
│ │ Abono Cliente = $ 0        │                  │   │
│ │ Pendiente pago = $ 785.000 │                  │   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│ ┌──────────────────────────────────────────────┐   │ ← Notas
│ │ NOTAS:                                        │   │
│ │ 1. La factura de los gastos se realiza...     │   │
│ │ 2. Para dejar o retirar documentos...         │   │
│ │ 3. Para licitaciones Municipales...           │   │
│ │ 4. Si el cliente no hiciere uso del...        │   │
│ │ 5. Todos los valores indicados NO incluyen IVA│   │
│ └──────────────────────────────────────────────┘   │
│                                                    │
│      EJECUTIVO:              CONTRATANTE:          │
│      _________               _________             │
│      Andrea Núñez Toro       Juan Carrasco         │
│      +56 9 9763 4534                               │
│                                                    │
│                  Page 1/1                          │
└────────────────────────────────────────────────────┘
```

**Marca de agua:** el logo del tenant aparece a **tamaño grande** (~200mm de alto), centrado verticalmente, con opacidad ~15%, color original (no gris). Se ve detrás de TODO el contenido pero sin entorpecer la lectura.

## CSS para DomPDF

```css
@page {
    margin: 15mm 12mm;
    size: 216mm 330mm; /* Carta chileno */
}

body {
    font-family: Helvetica, sans-serif;
    font-size: 10pt;
    color: #1a1a24;
    line-height: 1.4;
    position: relative;
}

/* MARCA DE AGUA - LOGO GIGANTE CENTRADO */
.watermark {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 180mm;
    height: auto;
    opacity: 0.12;
    z-index: -1;
    pointer-events: none;
}

/* HEADER */
.pdf-header {
    margin-bottom: 8mm;
}

.pdf-header table {
    width: 100%;
    border-collapse: collapse;
}

.pdf-header .numero-box {
    border: 1px solid #ccc;
    border-radius: 3mm;
    padding: 3mm 5mm;
    background-color: #fafafa;
}

.pdf-header .numero {
    font-size: 14pt;
    font-weight: bold;
    color: #1a1a24;
}

.pdf-header .meta {
    font-size: 8pt;
    color: #666;
    margin-top: 1mm;
}

.pdf-header .logo {
    text-align: right;
}

.pdf-header .logo img {
    height: 18mm;
}

/* CARDS DE SECCIONES */
.section-card {
    border: 1px solid #ccc;
    border-radius: 2mm;
    margin-bottom: 5mm;
    background-color: #fff;
    page-break-inside: avoid;
}

.section-header {
    background-color: #f5f5f5;
    padding: 2mm 4mm;
    border-bottom: 1px solid #ccc;
    border-radius: 2mm 2mm 0 0;
    font-weight: bold;
    font-size: 10pt;
}

.section-body {
    padding: 4mm;
    font-size: 9pt;
}

.section-body table {
    width: 100%;
    border-collapse: collapse;
}

.section-body td {
    padding: 2mm 0;
    vertical-align: top;
}

.section-body .label {
    color: #666;
    width: 30mm;
    font-weight: normal;
}

.section-body .value {
    font-weight: bold;
    color: #1a1a24;
    border-bottom: 1px solid #e0e0e0;
}

/* RESUMEN DE VALORES (2 columnas) */
.resumen-valores {
    display: table;
    width: 100%;
}

.resumen-valores .col-izq, .resumen-valores .col-der {
    display: table-cell;
    width: 50%;
    padding: 2mm 4mm;
    vertical-align: top;
}

.resumen-valores .valor-row {
    margin-bottom: 2mm;
    font-weight: bold;
}

.resumen-valores .pendiente {
    border-top: 1px solid #999;
    padding-top: 2mm;
    margin-top: 2mm;
    color: #c00;
    font-size: 11pt;
}

/* APORTE PREVISIONAL */
.aporte-box {
    border: 1px solid #999;
    padding: 3mm;
    background-color: #fafafa;
}

.aporte-row {
    margin-bottom: 1mm;
}

/* NOTAS */
.notas {
    margin-top: 5mm;
    padding: 3mm 4mm;
    font-size: 8pt;
    line-height: 1.4;
    border: 1px solid #ccc;
    border-radius: 2mm;
}

.notas-titulo {
    font-weight: bold;
    margin-bottom: 2mm;
    font-size: 9pt;
}

.notas ol {
    margin: 0;
    padding-left: 5mm;
}

.notas li {
    margin-bottom: 1mm;
}

/* FIRMAS */
.firmas {
    margin-top: 12mm;
    width: 100%;
    border-collapse: collapse;
}

.firmas td {
    width: 50%;
    text-align: center;
    padding: 0 5mm;
    vertical-align: top;
}

.firma-titulo {
    font-weight: bold;
    margin-bottom: 12mm;
    font-size: 10pt;
}

.firma-img {
    width: 40mm;
    height: 15mm;
    margin: 0 auto;
}

.firma-linea {
    border-bottom: 1px solid #000;
    width: 60mm;
    margin: 0 auto;
}

.firma-nombre {
    font-weight: bold;
    margin-top: 1mm;
    font-size: 9pt;
}

.firma-meta {
    font-size: 8pt;
    color: #666;
    margin-top: 1mm;
}

/* PIE DE PÁGINA */
.page-footer {
    position: fixed;
    bottom: -8mm;
    left: 0;
    right: 0;
    text-align: center;
    font-size: 8pt;
    color: #999;
    font-style: italic;
}
```

## Template PHP (`app/Views/contratos/pdf_ni.php`)

```php
<?php
$logoPath = '';
$tenant = \App\Core\Container::instance()->get('tenant');
if (!empty($tenant['branding_logo_url'])) {
    $logoPath = \App\Core\Storage::url($tenant['branding_logo_url'], 86400);
}

// Helpers
$formatRut = function ($rut) {
    if (empty($rut)) return '—';
    $clean = preg_replace('/[.-]/', '', strtoupper($rut));
    if (strlen($clean) < 8 || strlen($clean) > 9) return $rut;
    $cuerpo = substr($clean, 0, -1);
    $dv = substr($clean, -1);
    $reversed = strrev($cuerpo);
    $formatted = '';
    for ($i = 0; $i < strlen($reversed); $i++) {
        if ($i > 0 && $i % 3 === 0) $formatted .= '.';
        $formatted .= $reversed[$i];
    }
    return strrev($formatted) . '-' . $dv;
};

$formatCLP = function ($monto) {
    return '$ ' . number_format((int)$monto, 0, ',', '.');
};

$formatFecha = function ($fecha) {
    if (empty($fecha) || $fecha === '0000-00-00') return '—';
    return date('d/m/Y', strtotime($fecha));
};

$si_no = function ($v) { return $v ? 'SI' : 'NO'; };
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style><?php require __DIR__ . '/pdf_ni_styles.css'; ?></style>
</head>
<body>

<!-- MARCA DE AGUA -->
<?php if ($logoPath): ?>
    <img src="<?= htmlspecialchars($logoPath) ?>" class="watermark" alt="">
<?php endif; ?>

<!-- HEADER -->
<div class="pdf-header">
    <table>
        <tr>
            <td style="width: 50%;">
                <div class="numero-box">
                    <div class="numero">CONTRATO Nº <?= htmlspecialchars($contrato['numero']) ?></div>
                    <div class="meta">
                        Fecha: <?= $formatFecha($contrato['fecha_contrato']) ?>
                        Hora: <?= htmlspecialchars($contrato['hora_contrato']) ?>
                    </div>
                </div>
            </td>
            <td class="logo" style="width: 50%; text-align: right;">
                <?php if ($logoPath): ?>
                    <img src="<?= htmlspecialchars($logoPath) ?>" alt="<?= htmlspecialchars($tenant['nombre']) ?>">
                <?php else: ?>
                    <div style="font-family: Times; font-size: 14pt;">
                        <?= htmlspecialchars($tenant['nombre']) ?>
                    </div>
                <?php endif; ?>
            </td>
        </tr>
    </table>
</div>

<!-- 1. DATOS DEL CONTRATANTE -->
<div class="section-card">
    <div class="section-header">1. DATOS DEL CONTRATANTE</div>
    <div class="section-body">
        <table>
            <tr>
                <td class="label">Nombre Completo:</td>
                <td class="value" colspan="2"><?= htmlspecialchars($cliente['nombres'] . ' ' . $cliente['apellido_paterno'] . ' ' . ($cliente['apellido_materno'] ?? '')) ?></td>
                <td class="label">RUT:</td>
                <td class="value"><?= $formatRut($cliente['rut']) ?></td>
            </tr>
            <tr>
                <td class="label">Teléfono 1:</td>
                <td class="value"><?= htmlspecialchars($cliente['telefono_1'] ?? '—') ?></td>
                <td class="label">Teléfono 2:</td>
                <td class="value" colspan="2"><?= htmlspecialchars($cliente['telefono_2'] ?? '—') ?></td>
            </tr>
            <tr>
                <td class="label">Correo:</td>
                <td class="value" colspan="4"><?= htmlspecialchars($cliente['email'] ?? '—') ?></td>
            </tr>
            <tr>
                <td class="label">Dirección:</td>
                <td class="value" colspan="4">
                    <?= strtoupper(htmlspecialchars(
                        ($cliente['direccion'] ?? '—') . ' ' .
                        ($cliente['region_nombre'] ?? '') . ' ' .
                        ($cliente['provincia_nombre'] ?? '') . ' ' .
                        ($cliente['comuna_nombre'] ?? '')
                    )) ?>
                </td>
            </tr>
            <tr>
                <td class="label">Parentesco:</td>
                <td class="value" colspan="4"><?= strtoupper(htmlspecialchars($cliente['parentesco_nombre'] ?? '—')) ?></td>
            </tr>
        </table>
    </div>
</div>

<!-- 2. DATOS DEL FALLECIDO -->
<div class="section-card">
    <div class="section-header">2. DATOS DEL FALLECIDO</div>
    <div class="section-body">
        <table>
            <tr>
                <td class="label">Nombre Completo:</td>
                <td class="value" colspan="2"><?= htmlspecialchars($fallecido['nombres'] . ' ' . $fallecido['apellido_paterno'] . ' ' . ($fallecido['apellido_materno'] ?? '')) ?></td>
                <td class="label">RUT:</td>
                <td class="value"><?= $formatRut($fallecido['rut']) ?></td>
            </tr>
            <tr>
                <td class="label">Falleció en:</td>
                <td class="value"><?= htmlspecialchars($fallecido['lugar_defuncion'] ?? '—') ?></td>
                <td class="label">Edo. Civil:</td>
                <td class="value"><?= strtoupper(htmlspecialchars($fallecido['estado_civil_nombre'] ?? '—')) ?></td>
                <td class="label">Nacionalidad:</td>
                <td class="value"><?= strtoupper(htmlspecialchars($fallecido['pais'] ?? '—')) ?></td>
            </tr>
            <tr>
                <td class="label">Fecha Nacimiento:</td>
                <td class="value"><?= $formatFecha($fallecido['fecha_nacimiento']) ?></td>
                <td class="label">Fecha Defunción:</td>
                <td class="value"><?= $formatFecha($fallecido['fecha_defuncion']) ?></td>
                <td class="label">Entidad Previsional:</td>
                <td class="value">
                    <?= htmlspecialchars($aportes[0]['nombre_snapshot'] ?? 'NO') ?>
                </td>
            </tr>
            <tr>
                <td class="label">Entidad Previsional 2:</td>
                <td class="value" colspan="2"><?= htmlspecialchars($aportes[1]['nombre_snapshot'] ?? 'NO') ?></td>
                <td class="label">Entidad Previsional 3:</td>
                <td class="value"><?= htmlspecialchars($aportes[2]['nombre_snapshot'] ?? 'NO') ?></td>
            </tr>
        </table>
    </div>
</div>

<!-- 3. DETALLES DEL SERVICIO FUNERARIO -->
<div class="section-card">
    <div class="section-header">3. DETALLES DEL SERVICIO FUNERARIO</div>
    <div class="section-body">
        <table>
            <tr>
                <td class="label">Urna:</td>
                <td class="value" style="width: 60%;"><?= strtoupper(htmlspecialchars($contrato['cofre_descripcion'] ?? '—')) ?></td>
                <td class="label">Valor:</td>
                <td class="value"><?= $formatCLP($contrato['cofre_valor_clp']) ?></td>
            </tr>
            <tr>
                <td class="label">Capilla:</td>
                <td class="value"><?= strtoupper(htmlspecialchars($contrato['capilla_nombre'] ?? '—')) ?></td>
                <td class="label">Carroza:</td>
                <td class="value"><?= strtoupper(htmlspecialchars($contrato['carroza_nombre'] ?? '—')) ?></td>
            </tr>
            <tr>
                <td class="label">Auto:</td>
                <td class="value"><?= (int)$contrato['cantidad_auto'] ?></td>
                <td class="label">Van:</td>
                <td class="value"><?= (int)$contrato['cantidad_van'] ?></td>
            </tr>
            <tr>
                <td class="label">Cruz:</td>
                <td class="value"><?= $si_no($contrato['cruz']) ?></td>
                <td class="label">Tarjetero:</td>
                <td class="value"><?= $si_no($contrato['tarjetero']) ?></td>
            </tr>
            <tr>
                <td class="label">Libro Condolencias:</td>
                <td class="value"><?= $si_no($contrato['libro_condolencias']) ?></td>
                <td class="label">Tarjeta Condolencias:</td>
                <td class="value"><?= $si_no($contrato['tarjeta_condolencias']) ?></td>
            </tr>
            <tr>
                <td class="label">Arreglo:</td>
                <td class="value"><?= $si_no($contrato['arreglo_floral']) ?></td>
                <td class="label">Certificación Médica:</td>
                <td class="value"><?= $si_no($contrato['certificacion_medica']) ?></td>
            </tr>
            <tr>
                <td class="label">Tramitación Reg. Civil:</td>
                <td class="value"><?= $si_no($contrato['tramitacion_registro_civil']) ?></td>
                <td class="label">Cafetería:</td>
                <td class="value"><?= $si_no($contrato['cafeteria']) ?></td>
            </tr>
            <tr>
                <td colspan="4" style="padding-top: 3mm;">
                    <strong>MEDIDAS DEL COFRE:</strong>
                    Alto (cm) <strong><?= (int)$contrato['cofre_alto_cm'] ?></strong>
                    Ancho (cm) <strong><?= (int)$contrato['cofre_ancho_cm'] ?></strong>
                    Largo (cm) <strong><?= (int)$contrato['cofre_largo_cm'] ?></strong>
                </td>
            </tr>
        </table>
        
        <?php if (!empty($servicios_extra)): ?>
        <div style="margin-top: 4mm; padding-top: 3mm; border-top: 1px dashed #ccc;">
            <strong>OTROS SERVICIOS:</strong>
            <?php foreach ($servicios_extra as $serv): ?>
                <div style="margin-top: 1mm;">
                    <?= strtoupper(htmlspecialchars($serv['nombre'])) ?>
                    = <?= $formatCLP($serv['monto_clp']) ?>
                </div>
            <?php endforeach; ?>
        </div>
        <?php endif; ?>
    </div>
</div>

<!-- 4. COMPLEMENTO -->
<div class="section-card">
    <div class="section-header">4. COMPLEMENTO AL SERVICIO FUNERARIO</div>
    <div class="section-body">
        <table>
            <tr>
                <td class="label">Lugar de Velación:</td>
                <td class="value"><?= htmlspecialchars($contrato['lugar_velacion'] ?? '—') ?></td>
            </tr>
            <tr>
                <td class="label">Lugar de Sepultación:</td>
                <td class="value"><?= htmlspecialchars($contrato['lugar_sepultacion'] ?? '—') ?></td>
            </tr>
            <tr>
                <td class="label">Fecha Funeral:</td>
                <td class="value">
                    <?= $contrato['fecha_funeral_por_confirmar'] 
                        ? 'POR CONFIRMAR' 
                        : $formatFecha($contrato['fecha_funeral']) ?>
                </td>
                <td class="label">Hora Funeral:</td>
                <td class="value">
                    <?= $contrato['fecha_funeral_por_confirmar'] 
                        ? 'POR CONFIRMAR' 
                        : htmlspecialchars($contrato['hora_funeral']) ?>
                </td>
            </tr>
        </table>
    </div>
</div>

<!-- 5. RESUMEN DE VALORES -->
<div class="section-card">
    <div class="section-header">5. RESUMEN DE VALORES</div>
    <div class="section-body">
        <div class="resumen-valores">
            <div class="col-izq">
                <div class="valor-row">Valor lista = <?= $formatCLP($contrato['valor_lista_clp']) ?></div>
                <div class="valor-row">Total Servicio = <?= $formatCLP($contrato['total_servicios_clp']) ?></div>
                <div class="valor-row">Aporte Previsional = <?= $formatCLP($contrato['aporte_previsional_total_clp']) ?></div>
                <div class="valor-row">Abono Cliente = <?= $formatCLP($contrato['abono_cliente_clp']) ?></div>
                <div class="valor-row pendiente">Pendiente de pago = <?= $formatCLP($contrato['pendiente_pago_clp']) ?></div>
            </div>
            <div class="col-der">
                <?php if (!empty($aportes)): ?>
                <div class="aporte-box">
                    <div style="font-weight: bold; margin-bottom: 2mm;">APORTE PREVISIONAL:</div>
                    <?php foreach ($aportes as $aporte): ?>
                    <div class="aporte-row">
                        <strong><?= htmlspecialchars($aporte['nombre_snapshot']) ?></strong>
                        = <?= $formatCLP($aporte['monto_clp']) ?>
                    </div>
                    <?php endforeach; ?>
                </div>
                <?php endif; ?>
            </div>
        </div>
    </div>
</div>

<!-- NOTAS -->
<div class="notas">
    <div class="notas-titulo">NOTAS:</div>
    <ol>
        <li>La factura de los gastos se realiza a la caja de previsión correspondiente. Si procede.</li>
        <li>Para dejar o retirar documentos, el horario de atención es de 08:30 a 18:00 horas.</li>
        <li>Para licitaciones Municipales es deber del contratante gestionar el subsidio con la Municipalidad, de caso contrario el cliente deberá cancelar en forma particular.</li>
        <li>Si el cliente no hiciere uso del servicio contratado, podrá cambiarlo por otro, cederlo a un tercero, o cambiarlo por otro producto disponible pagando diferencia si existiere. No procede restitución de dinero.</li>
        <li>Todos los valores indicados NO incluyen IVA.</li>
    </ol>
</div>

<!-- FIRMAS -->
<table class="firmas">
    <tr>
        <td>
            <div class="firma-titulo">EJECUTIVO:</div>
            <div class="firma-linea"></div>
            <div class="firma-nombre"><?= htmlspecialchars($vendedor['nombre'] ?? 'Vendedor') ?></div>
            <?php if (!empty($vendedor['telefono'])): ?>
                <div class="firma-meta">+56 <?= htmlspecialchars($vendedor['telefono']) ?></div>
            <?php endif; ?>
        </td>
        <td>
            <div class="firma-titulo">CONTRATANTE:</div>
            <?php if (!empty($firma_url)): ?>
                <img src="<?= htmlspecialchars($firma_url) ?>" class="firma-img" alt="Firma">
            <?php endif; ?>
            <div class="firma-linea"></div>
            <div class="firma-nombre">
                <?= htmlspecialchars($cliente['nombres'] . ' ' . $cliente['apellido_paterno']) ?>
            </div>
        </td>
    </tr>
</table>

<!-- PIE DE PÁGINA -->
<div class="page-footer">Page 1/1</div>

</body>
</html>
```

## Generador (`app/Core/PdfGeneratorNI.php`)

```php
<?php
declare(strict_types=1);

namespace App\Core;

use App\Models\Contrato;
use App\Models\Cliente;
use App\Models\Fallecido;
use App\Models\User;
use Dompdf\Dompdf;
use Dompdf\Options;

final class PdfGeneratorNI
{
    public static function fromContrato(int $contratoId): string
    {
        // Cargar todo
        $contrato = Contrato::findCompleto($contratoId);
        $cliente = Cliente::findCompleto($contrato['cliente_id']);
        $fallecido = Fallecido::findCompleto($contrato['fallecido_id']);
        $aportes = AporteContrato::listByContrato($contratoId);
        $servicios_extra = ServicioContrato::listByContrato($contratoId);
        $vendedor = User::find($contrato['vendedor_id']);
        $firma_url = $contrato['firma_r2_path'] 
            ? Storage::url($contrato['firma_r2_path'], 86400) 
            : null;
        
        // Render template
        ob_start();
        require __DIR__ . '/../Views/contratos/pdf_ni.php';
        $html = ob_get_clean();
        
        // Generar PDF
        $opts = new Options();
        $opts->set('isRemoteEnabled', true);
        $opts->set('isHtml5ParserEnabled', true);
        $opts->set('defaultFont', 'Helvetica');
        
        $dompdf = new Dompdf($opts);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->setPaper([0, 0, 612, 935.43], 'portrait'); // Carta chileno: 216×330mm
        $dompdf->render();
        
        return $dompdf->output();
    }
    
    public static function generateAndUpload(int $contratoId): string
    {
        $pdfBinary = self::fromContrato($contratoId);
        $contrato = Contrato::find($contratoId);
        $path = "contratos/CT-NI-{$contrato['numero']}.pdf";
        return Storage::put($path, $pdfBinary, 'application/pdf');
    }
}
```

## Tests

```php
// tests/feature/PdfGeneratorNITest.php

class PdfGeneratorNITest extends TestCase
{
    public function testGenerarPdfNICompleto(): void
    {
        $contratoId = $this->createContratoNICompleto();
        
        $pdfBinary = PdfGeneratorNI::fromContrato($contratoId);
        
        // Verificar que es PDF válido
        $this->assertSame('%PDF', substr($pdfBinary, 0, 4));
        $this->assertGreaterThan(10000, strlen($pdfBinary)); // PDF tiene contenido real
    }
    
    public function testRutSeFormateaCorrectamente(): void
    {
        // RUT 102319966 → 10.231.996-6
        // Test que el formato chileno se aplica
    }
}
```
