# API de reportes administrativos

## Endpoints

| Método | Ruta | Permiso |
| --- | --- | --- |
| `GET` | `/api/v1/admin/reports/participants` | `reports.view_participants` |
| `GET` | `/api/v1/admin/reports/pharmacies` | `reports.view_pharmacies` |
| `GET` | `/api/v1/admin/reports/invoices` | `reports.view_invoices` |
| `GET` | `/api/v1/admin/reports/tickets` | `reports.view_tickets` |
| `GET` | `/api/v1/admin/reports/tickets/{ticket}/print-history` | `reports.view_tickets` |
| `GET` | `/api/v1/admin/reports/products` | `reports.view_products` |
| `GET` | `/api/v1/admin/reports/{report}/export` | permiso del reporte + `reports.export` |

## Reglas comunes

- Todas las colecciones paginan en backend.
- La metadata vive en `meta.current_page`, `meta.per_page`, `meta.total`, `meta.last_page`.
- Todos los filtros respetan la fecha de factura cuando el reporte se basa en facturas.
- No se exponen IDs internos.
- Las exportaciones CSV salen desde backend.

## Filtros comunes

- `promotion_id`
- `date_from`
- `date_to`
- `province`
- `city`
- `search`
- `page`
- `per_page`
- `sort`
- `direction`

## Extras por reporte

### Participantes

- `pharmacy_id`

Columnas principales:

- `dependent_name`
- `pharmacy_name`
- `pharmacy_branch`
- `first_participation_at`
- `last_participation_at`
- `physical_invoices`
- `valid_tickets`
- `voided_tickets`
- `applications_count`

### Farmacias

Columnas principales:

- `pharmacy_code`
- `pharmacy_name`
- `pharmacy_branch`
- `address`
- `province`
- `city`
- `participants`
- `physical_invoices`
- `valid_tickets`
- `promotions`

Notas:

- `address`, `province` y `city` pueden venir vacíos.
- El CSV exporta celdas vacías en lugar de la cadena `null`.

### Facturas

- `invoice_number`
- `pharmacy_id`

Columnas principales:

- `invoice_number`
- `invoice_date`
- `dependent_name`
- `pharmacy_name`
- `pharmacy_branch`
- `current_revision`
- `corrections_count`
- `promotions`
- `valid_tickets`
- `voided_tickets`

### Boletos

- `status`
- `dependent_id`
- `ticket_number`
- `invoice_number`
- `pharmacy_id`
- `reprinted_only`
- `reprinted_by_user_id`
- `reprinted_from`
- `reprinted_to`

Columnas principales:

- `ticket_number`
- `status`
- `promotion_name`
- `dependent_name`
- `pharmacy_name`
- `pharmacy_branch`
- `invoice_number`
- `revision_number`
- `product_name_snapshot`
- `generated_at`
- `first_printed_at`
- `last_printed_at`
- `print_count`
- `last_reprinted_at`
- `last_reprinted_by_user_id`
- `reprinted_by_name`
- `voided_at`
- `void_reason`

Uso recomendado para auditoría:

- filtrar `reprinted_only=true` para ver solo boletos reimpresos
- combinar `promotion_id` + `reprinted_by_user_id` para saber qué usuario reimprimió boletos de una promoción específica
- usar `reprinted_from` y `reprinted_to` para cortes por fecha de reimpresión

### Historial de impresiones por boleto

`GET /api/v1/admin/reports/tickets/{ticket}/print-history`

- usa el UUID público del boleto
- requiere autenticación y `reports.view_tickets`
- carga el historial real desde `promotion_ticket_print_batches`
- no se incluye dentro del listado principal; React lo consulta al abrir el modal

Respuesta conceptual:

```json
{
  "success": true,
  "message": "Historial de impresiones obtenido correctamente.",
  "data": {
    "ticket": {
      "id": "uuid-publico",
      "ticket_number": "BOLT3",
      "status": "valid",
      "status_label": "Válido",
      "promotion": { "id": "uuid-publico", "name": "Rifa de motor" },
      "dependent": { "id": "uuid-publico", "name": "Albert Prueba" },
      "pharmacy": { "id": "uuid-publico", "code": "00011", "name": "FCIA GRACIELA SRL", "city": "Santiago" },
      "invoice": { "id": "uuid-publico", "invoice_number": "FAC123" }
    },
    "summary": {
      "total_prints": 5,
      "initial_prints": 1,
      "reprints": 4,
      "first_printed_at": "2026-07-28T14:57:00Z",
      "last_printed_at": "2026-07-28T17:58:00Z"
    },
    "events": [
      {
        "id": "uuid-publico",
        "sequence": 1,
        "type": "initial_print",
        "type_label": "Impresión inicial",
        "printed_at": "2026-07-28T14:57:00Z",
        "printed_by": {
          "id": "uuid-publico",
          "name": "Development Administrator"
        },
        "batch": {
          "id": "uuid-publico",
          "type": "print",
          "label": "Lote 7F82A1C4"
        },
        "reprint_reason": null,
        "reprint_reason_label": null,
        "reprint_reason_other": null
      }
    ]
  }
}
```

Notas de integración:

- `events` llega en orden cronológico ascendente
- `type` distingue impresión inicial y reimpresión por boleto, aunque el lote haya sido mixto
- `reprint_reason_label` ya viene listo para mostrar
- `batch.label` es opcional para la UI; no mostrar UUID completos ni IDs internos
- si `events` llega vacío, tratarlo como estado vacío y no como error

### Productos

Columnas principales:

- `product_code`
- `product_name`
- `presentation`
- `applied_quantity`
- `physical_invoices`
- `participants`
- `generated_tickets`
- `promotions`

## Exportación CSV

- UTF-8 con BOM
- encabezados en español
- mismo filtro, orden y alcance del reporte visual
- protección contra CSV Formula Injection prefijando valores peligrosos
- columna `Sucursal` en participantes, farmacias, facturas y boletos
- agrupación por la farmacia física real; códigos iguales en sucursales distintas no se combinan

## Errores relevantes

- `401 UNAUTHENTICATED`
- `403 FORBIDDEN`
- `419` CSRF expirado
- `429 TOO_MANY_REQUESTS`
