Hub

electrosystems

amadeus-auditoria-viajes

in-progress high work
Creado
2026-05-25
Actualizado
2026-05-29
Parent
amadeus

Amadeus — Auditoría forense de viaje por usuario

Contexto

Sergio detectó que el técnico Manuel Pérez Morales (usuarios.id=9) en el viaje 191 (folio BJZ20260519, 19-22 mayo 2026, sitio “Benito Juárez”) estuvo regresando a Cd. Juárez todas las noches sin avisar, cuando la indicación era pernoctar cerca del sitio. Quiere validar todos sus consumos: que sean en horario laboral (8-18hrs según el ticket, no según el EXIF de la foto), que sean conceptos válidos, que no esté inflando.

El feature #239 (validación IA de fotos) ya cubre el lado individual: cada consumo se analiza al subirse. Pero falta la vista agregada por viaje × usuario que detecte patrones cross-consumo: ráfagas de tiempo, geografías inconsistentes, conceptos sintéticos repetitivos.

Investigación read-only (2026-05-25)

SSH + tinker directos sobre prod amadeus. Hallazgos sin tocar IA, solo con metadatos:

Setup

  • Viaje 191: folio BJZ20260519, 3 días (19-22 mayo).
  • Sitio: Benito Juárez (id 45). Sin GPS en sitios.latitud/longitud (null).
  • Manuel = usuarios.id=9, email mperez@e-electrosystems.com.

Consumos de Manuel en viaje 191

  • 31 consumos, total $6,306.60 MXN.
  • 31/31 con foto.
  • 31/31 con fecha_ticket (EXIF de la foto).
  • 0/31 con coordenadas_ticket (el dispositivo no captura GPS en EXIF).
  • 31/31 con coordenadas (GPS del browser al registrar).

Patrones forenses detectados sin IA

  1. Geolocalización de registro homogénea: los 31 consumos se registraron desde 31.6139, -106.355 (Cd. Juárez, no el sitio del viaje). Variación ≤3m entre los 31. Es decir: Manuel registró todos sus consumos desde el mismo edificio físico en Cd. Juárez.

  2. Backloading nocturno masivo: 24 de los 31 tickets fueron fotografiados entre 22:30 y 23:02 del 21 mayo — ventana de 32 minutos, intervalos seriales de ~1 minuto entre fotos consecutivas. Todos los registros en la app caen entre 04:30 y 05:02 del 22 mayo (la madrugada antes de regresar del viaje).

  3. Rotación sintética de conceptos: los 24 nocturnos siguen secuencia mecánica Comida → Víveres → Sueros → Comida → Víveres → Sueros → Cena → .... Patrón no-natural; sugiere conceptos escritos a la hora de la foto, no a la hora del gasto.

  4. Volumen atípico: 31 consumos en 3 días = ~10/día. Una persona en viaje real genera ~3 comidas/día y 1 ronda de víveres por viaje.

  5. Conceptos rechazables triviales: 2 Cargo por retiro de $34.80 cada uno = comisiones bancarias, no son gastos. El feature #239 los marcaría parece_viatico_legitimo=false.

  6. Contraste con los del 25 mayo: 7 consumos restantes tienen fecha_ticket EXIF en horario laboral (09:56-10:01) y registro el mismo día por la tarde (15:56-16:01). Comportamiento natural — sirve de comparación.

Lo que la IA aún tiene que dar

Los metadatos no dicen:

  • Hora real impresa en el ticket (el EXIF es cuándo se tomó la foto, no cuándo fue el gasto).
  • Ciudad del comercio según el ticket (no tenemos GPS de la foto).
  • Detección de tickets duplicados/serializados por contenido.

Eso lo da #239 con la ampliación pre-merge (hora_ticket_ocr, fecha_ticket_ocr, comercio_nombre, comercio_ciudad), commit 166e055 en branch feature/validacion-fotos.

Diseño del feature (decisiones tomadas)

DecisiónElegida
¿Ampliar #239 antes de mergear? — campos OCR temporales y de comercio agregados (commit ya hecho)
Fuente de ubicación esperadaGPS de sitios + fallback bbox por ciudad (bbox cubre el caso actual, donde Benito Juárez no tiene GPS)
TriggerBotón “Auditar” bajo demanda por viaje × usuario (no automático)
Resumen ejecutivo IA, llamada extra a Antigravity con flags determinísticos como contexto (~$0.03/reporte)

Plan de implementación

Schema nuevo

auditorias_viaje
- id
- viaje_id (FK)
- usuario_id (FK)
- generado_por_id (FK usuarios, admin que pidió la auditoría)
- estado: pendiente | analizando | listo | error
- resumen_ejecutivo (text, narrativa generada por IA)
- flags_agregados (JSON con counts y arrays):
    {
      consumos_total: 31,
      consumos_fuera_horario_laboral: 24,
      consumos_lejos_del_sitio: 0 o N si calculamos por bbox,
      conceptos_rechazados_por_ia: 2,
      ratio_diario_consumos: 10.3,
      rafagas_temporales: [
        {ventana: "2026-05-21 22:30-23:02", N: 24, intervalo_medio_seg: 45}
      ],
      ciudades_inferidas: { "Cd. Juárez": 24, "Villa Ahumada": 5, "desconocido": 2 }
    }
- costo_usd
- generado_at
- timestamps

Service nuevo

app/Services/Vision/AuditorViajeService.php:

public function auditar(Viaje $viaje, Usuario $usuario): AuditoriaViaje

Pasos:

  1. Carga consumos del usuario en el viaje (sin global scopes) + análisis IA + categoría.
  2. Para consumos sin análisis o con datos incompletos: dispatcha AnalizarFotoConsumoJob SYNC y espera (esto es un comando admin, no UI flow — sync OK).
  3. Calcula flags determinísticos en PHP (rápido, sin IA):
    • Fuera de horario: hora_ticket_ocr no entre 08:00-18:00, o si null, fecha_ticket EXIF idem.
    • Lejos del sitio: distancia coordenadas_ticket vs sitio del viaje. Fallback: comercio_ciudad vs ciudad esperada del sitio.
    • Ráfagas temporales: agrupa fechas de fotografía en ventanas de ≤30 min con ≥5 consumos.
    • Conceptos rechazados: count de consumos_analisis.parece_viatico_legitimo=false.
    • Ratio diario: count(consumos) / (fecha_fin - fecha_inicio + 1). Flag si >5/día.
    • Duplicados por OCR: agrupa por comercio_nombre + monto_total; flag si hay duplicados con fecha_ticket_ocr distintas.
  4. Llama Antigravity UNA vez con todo el JSON de flags + resumen de consumos, pide narrativa de 3-5 oraciones describiendo patrones.
  5. Persiste AuditoriaViaje.

Controller + UI

  • Ruta: GET /viajes/{viaje}/auditoria/{usuario} (vista del reporte). POST mismo path para disparar generación.
  • Page Vue Admin/AuditoriaViaje.vue:
    • Header con nombre del usuario, viaje, fechas, sitio.
    • Tarjeta de resumen ejecutivo (texto IA).
    • KPIs: total consumos, monto, % nocturno, % fuera de sitio, conceptos rechazados.
    • Cronología (línea de tiempo) por consumo: fecha del ticket vs fecha de registro, color por flag.
    • Tabla detallada por consumo con flags por columna.
    • Sección “Hallazgos accionables” (lista de flags relevantes con counts).
  • Botón “Auditar este usuario” en /viajes/{id} para admin con gestionar_viaticos.

Comando artisan

php artisan analisis:auditar-viaje {viaje} {usuario} — útil para correr desde la línea de comandos sin pasar por la UI.

Costo

Para el viaje 191 / Manuel (31 consumos):

  • Si re-analizamos todos los consumos del viaje para tener los campos nuevos: 31 × ~$0.01 = ~$0.31.
  • Resumen ejecutivo: ~$0.03.
  • Total: ~$0.35 USD por auditoría.

Cap diario global (ANALISIS_COSTO_MAX_DIARIO_USD) sigue aplicando.

Tareas pendientes

  • 📅 sin-fecha — B.1 Migración auditorias_viaje + modelo + relaciones.
  • 📅 sin-fecha — B.2 AuditorViajeService con flags determinísticos.
  • 📅 sin-fecha — B.3 Llamada extra a Antigravity para resumen ejecutivo (helper en mismo service o ResumidorAuditoriaService).
  • 📅 sin-fecha — B.4 AuditoriaViajeController + ruta + policy.
  • 📅 sin-fecha — B.5 Page Vue Admin/AuditoriaViaje.vue con secciones del diseño.
  • 📅 sin-fecha — B.6 Botón “Auditar” en /viajes/{id} show (visible si admin).
  • 📅 sin-fecha — B.7 Comando artisan analisis:auditar-viaje.
  • 📅 sin-fecha — B.8 Tests Pest end-to-end.
  • 📅 sin-fecha — B.9 Smoke test contra viaje 191 / Manuel para validar que la IA captura los patrones que ya vemos en metadatos.

Bitácora

2026-05-25 — investigación + diseño + ampliación de #239

  • SSH read-only sobre prod amadeus, queries vía tinker.
  • Identificados los 5 patrones forenses del viaje 191 sin necesidad de IA (geo homogénea, ráfaga nocturna, rotación sintética, volumen, cargos bancarios).
  • Sergio confirmó: ampliar #239 antes de mergear con hora_ticket_ocr, fecha_ticket_ocr, comercio_nombre, comercio_ciudad. Commit 166e055 en branch feature/validacion-fotos.
  • Diseño completo del feature de auditoría documentado en este archivo.
  • Próximo: implementación B.1 → B.9 en el mismo branch feature/validacion-fotos. Al mergear, todo va junto.