# Front — Cargos y tarifas GUIA (paridad Jasper)

## Contexto

El template **`GUIA_DOCUMENT`** del Report Builder expone dos secciones financieras alineadas con el PDF legacy Jasper:

| Sección | `type` | Orden | Descripción |
|---------|--------|-------|-------------|
| `rate_details_section` | `rate_details` | 4 | Tarifa principal + servicios adicionales |
| `charges_summary_section` | `charges_summary` | 5 | Datos básicos (izq) + resumen de totales (der) |

**Filtro preview/export:** `guideId` (required, entero positivo).

Los valores vienen **calculados del backend** (entidad `Guide`, `servtoguide`, totales de paquetes). El front **no calcula** montos; solo renderiza y permite mostrar/ocultar columnas o campos vía config guardada.

---

## Orden de secciones GUIA

1. `header_section`
2. `party_section`
3. `packages_section`
4. **`rate_details_section`**
5. **`charges_summary_section`**
6. `terms_section` (ver `pageBreakBefore` en términos)

---

## Preview / export — estructura de respuesta

Cada sección del array `sections[]` incluye `key`, `type`, `title`, `headers`/`rows` (si aplica) y `data`.

### 1. `rate_details_section`

Cuando hay paquetes y tarifa, el backend emite layout Jasper en `data` (no uses solo `rows` + `subtotals`).

```json
{
  "key": "rate_details_section",
  "type": "rate_details",
  "title": "Shipment Rate Details",
  "headers": ["Name", "Unit", "Quantity", "Price ($)", "Total ($)"],
  "rows": [
    ["Caracas tarifa", "Lb", "1.00", "10.00", "10.00"]
  ],
  "data": {
    "guideNumber": "GUI1320132125",
    "tariffTable": {
      "title": "Shipment Rate Details GUI1320132125",
      "headers": ["Name", "Unit", "Quantity", "Price ($)", "Total ($)"],
      "row": ["Caracas tarifa", "Lb", "1.00", "10.00", "10.00"],
      "footerLabel": "Total",
      "footerTotal": "10.00",
      "priceHeader": "Price ($)",
      "totalHeader": "Total ($)"
    },
    "servicesTable": {
      "title": "Shipment Service GUI1320132125",
      "headers": ["Name", "Unit", "Quantity", "Price ($)", "Total ($)"],
      "rows": [
        ["Handling fee", "Unit", "2.00", "5.00", "10.00"]
      ],
      "footerLabel": "Total Services",
      "footerTotal": "10.00",
      "priceHeader": "Price ($)",
      "totalHeader": "Total ($)"
    }
  }
}
```

| Campo | Notas |
|-------|-------|
| `data.tariffTable` | Siempre presente si hay paquetes + tarifa |
| `data.servicesTable` | Solo si hay filas de servicios; si no hay servicios, **omitir** la tabla |
| `rows` | Compatibilidad: misma data aplanada; preferir `tariffTable` / `servicesTable` para UI |
| `data.subtotals` | **No se usa** en GUIA con layout Jasper (totales van en `footerTotal`) |

**Significado de la fila tarifa (backend):**

| Columna | Origen |
|---------|--------|
| Name | Nombre de la tarifa |
| Unit | Unidad de medida de la tarifa (Lb, Kg, etc.) |
| Quantity | `paidweight` |
| Price | `measurevalue` (0 si no es positivo) |
| Total | `max(freight, volfreight)` |

**Filas de servicios:** una por `servtoguide`; mismas columnas. El monto por fila sigue reglas Jasper (`%`, `N/A`, unidad × precio).

---

### 2. `charges_summary_section`

Layout **split** (dos paneles). El título de sección (`title`) es **"Shipment's Basic Details"** y va en el panel izquierdo.

```json
{
  "key": "charges_summary_section",
  "type": "charges_summary",
  "title": "Shipment's Basic Details",
  "data": {
    "layout": "split",
    "fields": [
      { "key": "insuredValue", "label": "Insured Value", "value": "0.00" },
      { "key": "declaredValue", "label": "Declared Value", "value": "150.00" },
      { "key": "cod", "label": "COD", "value": "X" }
    ],
    "panels": {
      "leftPanel": {
        "title": "Shipment's Basic Details",
        "fields": [
          { "label": "Insured Value", "value": "0.00" },
          { "label": "Declared Value", "value": "150.00" },
          { "label": "COD", "value": "X" }
        ]
      },
      "rightPanel": {
        "fields": [
          { "label": "Total shipment", "value": "10.00" },
          { "label": "Total services", "value": "5.00" },
          { "label": "Insurance paid (0.00%)", "value": "1.00" },
          { "label": "Otherfees", "value": "0.00" },
          { "label": "Commissions", "value": "0.00" },
          { "label": "Discount %", "value": "1.00", "inlinePercent": "5.88" },
          { "label": "Tax paid (0.00 %)", "value": "2.00" }
        ],
        "grandTotal": {
          "label": "Total Shipment $",
          "value": "17.00"
        }
      }
    }
  }
}
```

| Campo `key` | Significado |
|-------------|-------------|
| `insuredValue` | Valor asegurado de la guía |
| `declaredValue` | Suma de valores de paquetes (`SUM receipt.value)`), no `guide.declared` |
| `cod` | `"X"` si COD activo, vacío si no |
| `totalShipment` | Total línea tarifa (`max(freight, volfreight)`) |
| `totalServices` | Suma de `servtoguide.total` |
| `insurancePaid` | Monto seguro (el % va en el label del panel derecho) |
| `otherFees` | Otros cargos |
| `commissions` | Comisiones |
| `discountPercent` | Porcentaje de descuento (Jasper) |
| `discountAmount` | Monto descontado |
| `taxPaid` | Impuesto (el % va en el label) |
| `grandTotal` | Total final guía |

**Fórmula `grandTotal` (referencia, calculada en backend):**

```
totalServices + totalShipment + insurancePaid + otherFees + commissions - discountAmount + taxPaid
```

**Visibilidad:** si todos los valores están vacíos, el backend **omite** la sección del preview.

---

## Config guardada (editor)

El usuario configura **visibilidad y labels** de columnas/campos; **no** edita montos.

### `rate_details_section` — columnas configurables

```json
{
  "key": "rate_details_section",
  "type": "rate_details",
  "visible": true,
  "order": 4,
  "title": "Shipment Rate Details",
  "columns": [
    { "key": "tariffName", "visible": true, "order": 1, "label": "Name" },
    { "key": "unit", "visible": true, "order": 2, "label": "Unit" },
    { "key": "quantity", "visible": true, "order": 3, "label": "Quantity" },
    { "key": "unitPrice", "visible": true, "order": 4, "label": "Price ($)" },
    { "key": "lineTotal", "visible": true, "order": 5, "label": "Total ($)" }
  ]
}
```

- `PATCH /api/report-configurations/{id}` — solo `visible`, `order`, `label` por columna.
- Default: `GET /api/report-templates/GUIA_DOCUMENT` → `defaultConfig`.

### `charges_summary_section` — campos configurables

```json
{
  "key": "charges_summary_section",
  "type": "charges_summary",
  "visible": true,
  "order": 5,
  "title": "Shipment's Basic Details",
  "fields": [
    { "key": "insuredValue", "visible": true, "order": 1, "label": "Insured Value" },
    { "key": "declaredValue", "visible": true, "order": 2, "label": "Declared Value" },
    { "key": "cod", "visible": true, "order": 3, "label": "COD" },
    { "key": "totalShipment", "visible": true, "order": 4, "label": "Total shipment" },
    { "key": "totalServices", "visible": true, "order": 5, "label": "Total services" },
    { "key": "insurancePaid", "visible": true, "order": 6, "label": "Insurance paid" },
    { "key": "otherFees", "visible": true, "order": 7, "label": "Otherfees" },
    { "key": "commissions", "visible": true, "order": 8, "label": "Commissions" },
    { "key": "discountPercent", "visible": true, "order": 9, "label": "Discount %" },
    { "key": "discountAmount", "visible": true, "order": 10, "label": "Discount amount" },
    { "key": "taxPaid", "visible": true, "order": 11, "label": "Tax paid" },
    { "key": "grandTotal", "visible": true, "order": 12, "label": "Total Shipment $" }
  ]
}
```

**Panel mapping (solo render):**

| Panel | Keys |
|-------|------|
| Izquierdo | `insuredValue`, `declaredValue`, `cod` |
| Derecho | `totalShipment`, `totalServices`, `insurancePaid`, `otherFees`, `commissions`, `taxPaid` |
| Descuento | `discountAmount` con `inlinePercent` = valor de `discountPercent` |
| Footer derecho | `grandTotal` |

Ocultar un `key` en config → no aparece en `data.fields` ni en `panels`.

---

## Tareas front

### 1. Render `rate_details` — layout Jasper

Si `section.data.tariffTable` existe, usar estructura de tablas (no `<h3>` suelto ni lista de subtotales).

**Tabla 1 — Tarifa**

- Fila título: banda gris ancho completo → `tariffTable.title`
- Headers: `tariffTable.headers`
- Una fila datos: `tariffTable.row`
- Footer: `tariffTable.footerLabel` (colspan) + `tariffTable.footerTotal`

**Tabla 2 — Servicios** (solo si `servicesTable.rows.length > 0`)

- Título: `servicesTable.title`
- Filas: `servicesTable.rows`
- Footer: `servicesTable.footerLabel` + `servicesTable.footerTotal`

**Alineación celdas**

| Índice columna | Alineación |
|----------------|------------|
| 0 (Name) | izquierda |
| 1–2 (Unit, Qty) | centro |
| 3–4 (Price, Total) | derecha |

**Fallback:** si no hay `tariffTable`, usar `headers` + `rows` + `data.subtotals` (otros templates).

### 2. Render `charges_summary` — layout split

Si `section.data.layout === 'split'` y existe `section.data.panels`:

- Contenedor 50% / 50% (flex o table)
- **Izquierda:** header `panels.leftPanel.title` + filas label/value
- **Derecha:** filas sin header de sección + fila final `panels.rightPanel.grandTotal` con fondo gris

Fila descuento: mostrar `field.label` + `field.inlinePercent` inline + `field.value` a la derecha.

**Fallback:** si no hay `panels`, lista vertical con `data.fields` (comportamiento legacy).

### 3. CSS sugerido (paridad PDF)

Copiar del PDF backend (`templates/pdf/report_builder/document.html.twig`):

```css
.jasper-table { width: 100%; border-collapse: collapse; }
.jasper-table th, .jasper-table td { border: 0.75px solid #000; padding: 4px 6px; font-size: 8px; }
.jasper-table-header { background: #DEDEDE; font-weight: bold; text-align: center; }
.jasper-table .num { text-align: right; }
.jasper-table .center { text-align: center; }
.jasper-table .left { text-align: left; }
.jasper-total-row td { font-weight: bold; }
.jasper-total-label { text-align: center; }

.charges-split { width: 100%; border-collapse: collapse; }
.charges-split > td { vertical-align: top; width: 50%; padding: 0 4px; }
.charges-panel-header { background: #DEDEDE; font-weight: bold; padding: 4px 6px; border: 0.75px solid #000; font-size: 9px; }
.charges-panel-row td { border: 0.75px solid #000; padding: 4px 6px; font-size: 9px; }
.charges-panel-label { width: 65%; }
.charges-panel-value { text-align: right; width: 35%; }
.charges-grand-total td { background: #DEDEDE; font-size: 11px; font-weight: bold; border: 0.75px solid #000; padding: 4px 6px; }
.charges-grand-value { text-align: right; }
.discount-inline { margin-left: 8px; font-weight: normal; }
```

### 4. Editor de plantilla

- **Rate details:** toggles por columna (`tariffName`, `unit`, `quantity`, `unitPrice`, `lineTotal`); editar labels; no reordenar lógica de filas.
- **Charges:** toggles por field key; labels custom; `grandTotal` y `discountAmount` son solo lectura en preview.
- No exponer `layout: split` al usuario — es fijo para GUIA.

### 5. Impresión desde navegador

Aplicar las mismas clases; `@media print` puede reforzar bordes y fondos grises.

---

## Ejemplo React (rate_details)

```tsx
function RateDetailsSection({ section }: { section: RateDetailsSectionDTO }) {
  const { tariffTable, servicesTable } = section.data;

  if (!tariffTable) {
    return <LegacyTable section={section} />;
  }

  return (
    <div>
      <JasperTable
        title={tariffTable.title}
        headers={tariffTable.headers}
        rows={[tariffTable.row]}
        footerLabel={tariffTable.footerLabel}
        footerTotal={tariffTable.footerTotal}
      />
      {servicesTable?.rows?.length > 0 && (
        <JasperTable
          title={servicesTable.title}
          headers={servicesTable.headers}
          rows={servicesTable.rows}
          footerLabel={servicesTable.footerLabel}
          footerTotal={servicesTable.footerTotal}
        />
      )}
    </div>
  );
}
```

## Ejemplo React (charges split)

```tsx
function ChargesSummarySection({ section }: { section: ChargesSummarySectionDTO }) {
  if (section.data.layout !== 'split' || !section.data.panels) {
    return <ChargesList fields={section.data.fields} />;
  }

  const { leftPanel, rightPanel } = section.data.panels;

  return (
    <div className="charges-split">
      <div className="charges-left">
        <div className="charges-panel-header">{leftPanel.title}</div>
        {leftPanel.fields.map((f) => (
          <div key={f.label} className="charges-panel-row">
            <span>{f.label}</span>
            <span className="charges-panel-value">{f.value}</span>
          </div>
        ))}
      </div>
      <div className="charges-right">
        {rightPanel.fields.map((f) => (
          <div key={f.label} className="charges-panel-row">
            <span>
              {f.label}
              {f.inlinePercent != null && (
                <span className="discount-inline">{f.inlinePercent}</span>
              )}
            </span>
            <span className="charges-panel-value">{f.value}</span>
          </div>
        ))}
        {rightPanel.grandTotal && (
          <div className="charges-grand-total">
            <span>{rightPanel.grandTotal.label}</span>
            <span className="charges-grand-value">{rightPanel.grandTotal.value}</span>
          </div>
        )}
      </div>
    </div>
  );
}
```

---

## Criterios de aceptación

- [ ] Preview con `guideId` muestra `tariffTable` con título que incluye número de guía.
- [ ] Footer "Total" en tarifa = total línea tarifa, no `grandTotal`.
- [ ] `servicesTable` solo aparece si hay servicios.
- [ ] Charges en dos columnas: básicos izq, totales der.
- [ ] Labels dinámicos: `Insurance paid (X%)`, `Tax paid (X %)`.
- [ ] Fila descuento: % inline + monto a la derecha.
- [ ] Fila final gris: `Total Shipment $` = `grandTotal`.
- [ ] COD muestra `X` o vacío, no nombre de entidad COD.
- [ ] Declared Value = suma valores paquetes.
- [ ] Ocultar columna/campo en config → no aparece en preview.
- [ ] Export PDF del backend coincide visualmente con preview web.

---

## Prueba manual

1. Abrir config `GUIA_DOCUMENT` → verificar columnas rate + fields charges en editor.
2. Preview con guía que tenga paquetes, tarifa y al menos un servicio.
3. Validar JSON: `tariffTable`, `servicesTable`, `panels.leftPanel`, `panels.rightPanel.grandTotal`.
4. Preview con guía sin servicios → sin `servicesTable`.
5. Guía con `tcod=true` → COD = `X`.
6. Comparar PDF export vs captura Jasper legacy.

---

## APIs

| Acción | Endpoint |
|--------|----------|
| Default template | `GET /api/report-templates/GUIA_DOCUMENT` |
| Guardar config | `PATCH /api/report-configurations/{id}` |
| Preview | endpoint preview Report Builder con `guideId` |
| Export PDF | endpoint export document con `guideId` |

---

## Fuera de alcance

- Cálculo de tarifas/servicios en front.
- Edición de montos en plantilla.
- Misma estructura en `WHREC_DOCUMENT` o `PACKAGE_DOCUMENT` (solo GUIA usa `layout: split` y `tariffTable`).

---

## Referencias backend

| Archivo | Rol |
|---------|-----|
| `src/Service/ReportBuilder/Provider/GuideDocumentDataProvider.php` | Datos tarifa, servicios, charges |
| `src/Service/ReportBuilder/ReportDocumentFormatter.php` | Helpers cálculo/display |
| `src/Service/ReportBuilder/DocumentReportRenderService.php` | `tariffTable`, `panels` |
| `templates/pdf/report_builder/document.html.twig` | CSS y HTML referencia |
| `src/Service/ReportBuilder/ReportDocumentTemplateDefaults.php` | Defaults GUIA |
