# 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. ## Conversión de datos históricos a UTC (homo / producción) El comando no requiere migraciones adicionales ni crea una tabla de registro. Para convertir todos los registros de tipo `fixed_window` desde hora argentina: ```bash # Vista previa: no escribe en la base php artisan tickets:convert-validity-times-to-utc --all # Aplicar a todos los fixed_window php artisan tickets:convert-validity-times-to-utc --all --apply ``` También permite seleccionar IDs específicos (los siguientes son ejemplos): ```bash php artisan tickets:convert-validity-times-to-utc --ids=41,42 php artisan tickets:convert-validity-times-to-utc --ids=41,42 --apply ``` `--all` y `--ids` son excluyentes. El origen por defecto es `America/Argentina/Buenos_Aires`; se puede indicar otra zona IANA con `--source-timezone`. Cada extremo conserva su fecha original y se convierte por separado, incluso si la ventana abarca varios días. Los límites nulos y los `time_window` no se modifican. La aplicación es transaccional. No hay registro ni detección automática de conversiones anteriores: repetir `--apply` vuelve a convertir los valores actuales. La migración original `2026_09_21_040000_add_timezone_to_tenants.php` ya convierte las ventanas asociadas a eventos; no ejecutar este comando sobre esas ventanas si ya fueron convertidas. `--all` incluye absolutamente todos los `fixed_window`, sin esa distinción. ### Revertir una conversión `--reverse` convierte los valores actuales desde UTC hacia la zona local, con el mismo alcance `--all` o `--ids`. Sin `--apply` sólo muestra la vista previa. ```bash php artisan tickets:convert-validity-times-to-utc --all --reverse php artisan tickets:convert-validity-times-to-utc --all --reverse --apply php artisan tickets:convert-validity-times-to-utc --ids=41,42 --reverse --apply ``` Con `--reverse`, `--source-timezone` indica la zona local de destino (por defecto `America/Argentina/Buenos_Aires`). Las fechas de cada extremo y los límites nulos se respetan; los `time_window` no se modifican. Revierte una conversión si se seleccionan los mismos registros sin ediciones intermedias. No recupera una copia histórica ni detecta conversiones previas.