# Dominio Ticket ## Propósito Genera, valida, consulta y exporta entradas asociadas a compras pagadas de productos o variantes ticketables. ## Modelo - `Ticket`: pertenece a tenant y usuario, y conserva referencias al ítem de compra que lo generó, producto, variante y usuario escáner. La compra se obtiene a través de su ítem. - El nombre y la descripción se calculan dinámicamente desde el producto y la variante; los tickets no persisten una copia de esos textos. - `ValidityTime`: define ventanas absolutas o relativas de vigencia para fechas de evento y opciones de atributos. - `ValidityTimeType`: enum de estrategias de vigencia. `TicketValidityResolver` deriva la vigencia desde la variante asociada. Las alternativas de un mismo atributo se combinan con OR y las dimensiones diferentes se combinan con AND. El modelo calcula si un ticket está vigente, vencido o usado, y resuelve sus fechas efectivas de inicio y fin sin persistir vigencias en el ticket. ## Zona horaria y vigencias La zona se configura por tenant mediante `timezone` (identificador IANA, por ejemplo `America/Argentina/Buenos_Aires`), configurado mediante seeders o base de datos y expuesto en la respuesta de tenants. Ese es el valor por defecto. La aplicación mantiene su zona global en UTC. Las fechas y horas de `EventDate` y los horarios de `time_window` son locales al tenant. Las ventanas `fixed_window` se guardan en UTC. El resolver pasa la zona del tenant del producto al grupo de vigencia: antes de combinar fecha y hora, convierte el ancla a esa zona. Sin fecha de evento, utiliza el día local del instante consultado; contempla ventanas nocturnas. El inicio es inclusivo y el vencimiento exclusivo. Para validar una ventana horaria directamente, pasar la zona explícitamente: `$validityTime->isValid(now(), $tenant->timezone)`. Un `ValidityTime` aislado no tiene tenant; los métodos mantienen UTC como valor por defecto para usos sin contexto. La migración `2026_09_21_040000_add_timezone_to_tenants.php` asigna la zona inicial y reconstruye una sola vez las ventanas asociadas a fechas existentes, usando las fechas y horas locales originales. No modifica ventanas independientes. Los cambios posteriores de zona son responsabilidad del tenant: reinterpretan los horarios locales, pero no reescriben los instantes UTC guardados. Editar la fecha u horas de un evento sí vuelve a calcular su ventana. El rollback elimina la columna, sin deshacer la corrección de los instantes. Ya no se utiliza `EVENT_TIMEZONE`. ## Flujo de generación 1. `Purchase` emite `PurchasePaid` al confirmarse el pago. 2. `GenerateTicketsForPaidPurchase` atiende el evento. 3. `TicketGeneratorService` crea los tickets requeridos según ítems, cantidades y vigencia. 4. `Notification` envía la confirmación de compra después de la generación y adjunta los tickets cuando existen. ## Datos descartables para pruebas de carga En ambientes `local`, `testing`, `staging`, `homo` u `homologation`, el comando siguiente crea tickets válidos, identidades scanner con tokens Sanctum y un dataset JSON importable por Postman: ```bash php artisan load-test:tickets:prepare loadtest-evento \ --tickets=40000 \ --scanners=100 \ --owners=1000 \ --catalog-item=123 \ --run=evento-001 ``` El tenant debe existir y se recomienda que sea exclusivo para carga. Si no se indica `--catalog-item`, se usa el primer producto estándar del tenant con tickets habilitados. `--variant` es opcional; al indicarlo, su configuración de vigencia debe estar activa y ser resoluble. Sin variante, los tickets tienen vigencia irrestricta. El archivo se escribe por defecto en `storage/app/private/load-tests/` y contiene tokens secretos, por lo que no debe versionarse. Para limpiar el tenant después de la ejecución: ```bash php artisan tenants:reset-transactions loadtest-evento --dry-run php artisan tenants:reset-transactions loadtest-evento ``` ## Endpoints Bajo `/tenants/{tenant:codigo}`, protegidos por `auth:sanctum`: - `GET /tickets`. - `POST /tickets/pdf`. Bajo `/v1/adminapp/tenant`, protegido por `auth:sanctum`, `adminapp.tenant` y el menú `adminapp.tickets`: - `GET /tickets`, paginado y con búsqueda opcional mediante `q`. La respuesta incluye `scanned_tickets` y `total_tickets` para el tenant autenticado. `TicketPdfService` genera la descarga y `TicketResource`/`ValidityTimeResource` definen las respuestas. ## Dependencias y reglas Depende de `Purchase`, `Catalog`, `Tenant` y `Auth`. La generación debe ser idempotente ante reintentos del evento. `TicketNotAvailableException` y `TicketGenerationException` separan indisponibilidad de errores de generación.