Hub

electrosystems

amadeus-politicas-viaticos

ready-to-build high design-doc
Creado
2026-05-27
Actualizado
2026-05-29
Parent
amadeus

Amadeus — Inyección de políticas de viáticos en IA (BD + 2 prompts)

Diseño aprobado por Sergio el 2026-05-27 a alto nivel (tabla en BD, ambos prompts, contexto BD para AnalizadorTicket). D1-D5 cerradas 2026-05-28 (sección 5) → ready-to-build, sin decisiones pendientes. Parte del milestone de consumos (amadeus-consumos-milestone.md), Bloque 0b, arranque tentativo 2026-06-04.

Contexto

Hoy hay 3 servicios IA en app/Services/Vision/:

  1. AnalizadorTicketService (línea 23) — visión per-ticket aislada. Prompt embebido en SYSTEM_PROMPT (línea 109). NO conoce el viaje ni los otros consumos.
  2. ResumidorAuditoriaService (línea 21) — texto+JSON con flags_agregados. Prompt en línea 26.
  3. AuditorViajeService (línea 24) — orquestador: dispara AnalizarFotoConsumoJob por consumo (línea 126), arma flags, llama Resumidor, persiste en auditorias_viaje.

Tabla auditorias_viaje existe (migration 2026_05_26_000508).

Texto-política (6 párrafos de Gustavo, $150/comida, $100 libres/día, agua/sueros separados, facturación obligatoria, 2 personas, fotos al teléfono de facturación, compras con previo aviso) ya capturado en chat de sesión 2026-05-27.

1. Tabla politicas_viaticos

Decisión: una sola fila vigente + tabla histórico (NO flag activa ni rangos vigente_desde/hasta). Razón: políticas escasas (1-3/año), edición simple en Nova, politica_hash en auditorias_viaje da traceability sin complejidad de rangos.

politicas_viaticos
├─ id
├─ texto (TEXT)
├─ resumen_estructurado (JSON nullable) — opcional, ver decisión 2 abajo
├─ vigente (boolean default true)
├─ hash_sha1 (char(40), boot() lo deriva del texto)
├─ notas_edicion (text nullable)
├─ editado_por_id (FK usuarios nullable)
├─ timestamps

politicas_viaticos_historial (append-only)
├─ id, politica_id, texto, hash_sha1, resumen_estructurado
├─ editado_por_id, notas_edicion, snapshot_at

Workflow: observer updating en PoliticaViaticos → INSERT en _historial con el row anterior antes del save.

Migration auditorias_viaje: ADD politica_hash CHAR(40) NULL + index.

Nova resource:

  • Editables: texto (Textarea grande), notas_edicion (Text).
  • Read-only: hash_sha1, editado_por, created_at, HasMany al historial.
  • Permisos: gate politicas-viaticos.edit (solo Gustavo/Sergio).

2. Contexto BD para el AnalizadorTicket

Decisión: prompt enriquecido con contexto pre-cargado, NO tool-calling agentic (tool-calling sumaría 2-4 round-trips → ~$0.05 extra/ticket vs $0.008 hoy = inaceptable).

JSON de contexto (~1.5-2K tokens extra/ticket):

{
  "politica_vigente": { "texto": "...", "hash": "..." },
  "viaje": {
    "id": 123, "folio": "V-2026-045",
    "sitio_principal": "Cd. Juárez",
    "fechas": "2026-05-20 → 2026-05-23",
    "dias_servicio": 4, "tipo_viaje": "instalación",
    "integrantes": 2
  },
  "consumos_previos_mismo_usuario_este_viaje": [
    { "id": 88, "fecha_ticket": "2026-05-20 13:45", "categoria": "comida",
      "monto": 145.0, "comercio": "La Cabaña", "ciudad": "Cd. Juárez",
      "estado_ia": "ok", "concepto": "comida mediodía" }
  ],
  "totales_dia_actual_usuario": {
    "fecha": "2026-05-21",
    "comidas_count": 2, "comidas_total": 243.0,
    "libres_total": 0.0,
    "limite_dia_segun_politica_estimado": 350.0
  }
}

NO incluir: histórico cross-viaje (privacy + saturación), GPS raw, tickets de otros usuarios del equipo.

Cómo pasar al modelo:

  • System: SYSTEM_PROMPT actualizado + instrucciones de uso de política (cacheable con cache_control: ephemeral).
  • User message: bloque image + bloque text con política inline + JSON contexto.
  • Header beta anthropic-beta: prompt-caching-2024-07-31. Reduce ~50-70% el input cost del system al re-enviar.

Tool schema (dictaminar_ticket) — campos nuevos:

  • violaciones_politica: array strings.
  • comentario_politica: string corto.
  • requiere_factura_segun_politica: bool.

Cache del contexto del viaje: vale en flujo “re-auditar viaje completo” (asegurarAnalisisIA), NO en flujo normal (un job background por foto). Patrón:

$contextoBase = Cache::remember(
    "ctx_viaje_{$viaje->id}_{$usuario->id}", 300,
    fn() => $this->armarContextoBase($viaje, $usuario)
);

Estimación costo:

  • Hoy: ~$0.008/ticket.
  • Con política + contexto sin cache: ~$0.015/ticket.
  • Con cache del system: ~$0.010/ticket.
  • 30 consumos/viaje × $0.015 = $0.45 (vs $0.24 hoy). Asumible.

Cap diario USD: config('analisis.cap_diario_usd') debe revisarse (decisión 3).

3. Inyección en Resumidor

Trivial. ResumidorAuditoriaService::generar():

  1. $politica = PoliticaViaticos::vigente().
  2. Concatenar al SYSTEM_PROMPT o pasar como bloque text del user message.
  3. Instrucción explícita: “Evalúa flags contra política. Lista violaciones observables en flags. NO inventes violaciones sin soporte de flag.”
  4. Persistir politica_hash en auditorias_viaje.politica_hash.

Costo: ~+1K tokens input = $0.003/auditoría. Insignificante.

4. Migration plan (4 PRs)

PR 1 — Schema + Nova (bajo riesgo) — ✅ DEPLOYADO 2026-05-29 (commit 7e53af6 → prod + seed)

  • Migrations: politicas_viaticos, politicas_viaticos_historial, add_politica_hash_to_auditorias_viaje. ✅ aplicadas en dev.
  • Modelos PoliticaViaticos + PoliticaViaticosHistorial. ✅ — el hash se deriva vía mutator de atributo, NO boot()/saving, porque DatabaseSeeder usa WithoutModelEvents y el evento no dispararía → violación NOT NULL. El observer updating (historial) sí va como evento (en Nova los eventos corren normal). vigente() como helper estático.
  • PoliticasViaticosSeeder ✅ idempotente, con texto BORRADOR (derivado del resumen de 6 puntos). Falta el texto oficial — se edita en Nova, ese es el punto. (sin nombres propios de personas en el seed, por regla del hub)
  • Nova resources ✅ — PoliticaViaticos (editable) + PoliticaViaticosHistorial (read-only, fuera de menú). Autorización vía PoliticaViaticosPolicy (auto-discovery): superadmin + permiso existente gestionar_viaticos (reusado, no se creó gate politicas-viaticos.edit nuevo).
  • Verificado: 7 tests unit verdes + Pint limpio + migrate/seed OK en dev. 97 tests del módulo verdes (3 fallos inventario preexistentes, ajenos).
  • Texto oficial refinado y sembrado en prod 2026-05-29 (id=1 vigente, hash match). Sin nombres propios (regla del hub). Decisión de negocio abierta: prorrateo del consumo libre en salidas parciales (ver bitácora de amadeus.md).
  • Pendiente: que Sergio pruebe edición en /nova (usuario con gestionar_viaticos).
  • Sin cambios IA todavía. ✅

PR 2 — Resumidor (riesgo bajo) — ✅ DEPLOYADO 2026-05-29 (commit 2506bb2)

  • ResumidorAuditoriaService carga PoliticaViaticos::vigente(), la inyecta en el system prompt (instrucción D5: evaluar contra política, no inventar, solo señalar) y devuelve politica_hash en todas las rutas. ✅
  • AuditorViajeService persiste el hash en auditorias_viaje.politica_hash (?? null defensivo). ✅
  • 5 tests con Http::fake (inyección en payload + hash + solo-vigente + persistencia). ✅ No corrí la validación pagada analisis:auditar-viaje (disponible a pedido, ~$0.35).

PR 3 — Analizador con contexto (riesgo alto)

  • Nuevo ContextoTicketBuilder arma JSON de contexto.
  • AnalizadorTicketService::analizar(string $ruta, ?array $contexto = null) con backward compat.
  • AnalizarFotoConsumoJob::handle() construye contexto antes de llamar.
  • Tool schema gana violaciones_politica, comentario_politica, requiere_factura_segun_politica.
  • Migration ADD a consumos_analisis.
  • Validación crítica: correr en staging con viaje cerrado (Gustavo elige uno). Comparar veredictos pre/post política contra juicio humano. Documentar discrepancias antes de prender en prod.

PR 4 — Persistir hash + UI mínima

  • AuditorViajeService::auditar() lee politica_hash y guarda en auditorias_viaje.politica_hash + flags_agregados.politica_hash.
  • UI en reporte: “Política aplicada: v editada el ” + link Nova read-only.

Tests

  • Unit PoliticaViaticos: boot() hash + observer historial.
  • Unit ContextoTicketBuilder: dado viaje + 3 consumos previos, devuelve estructura esperada.
  • Feature AnalizarFotoConsumoJob con Http::fake() — verificar payload Antigravity incluye política y contexto.
  • Regression AuditorViajeService con fixture viaje completo.
  • NO tests contra Antigravity real (cuesta + flaky).

Estimación total

  • PR 1: 2-3h.
  • PR 2: 1-1.5h.
  • PR 3: 4-6h (mayoría es ContextoTicketBuilder + tool schema + tests).
  • PR 4: 1.5-2h.
  • Validación staging: 1-2h manual.
  • Total: 9.5-14.5h, distribuidas en 3-4 sesiones con Sergio validando entre cambios.

5. Decisiones — CERRADAS 2026-05-28 ✅

  1. Single-row con historial (NO rangos vigente_desde/hasta). Una sola política vigente + tabla _historial. Si en el futuro hace falta “programar” un cambio, se reevalúa.
  2. Solo texto (NO resumen_estructurado JSON). La política se pasa como prosa; el modelo razona sobre ella sin doble fuente de verdad. → La columna resumen_estructurado se omite del schema inicial (o queda nullable sin uso).
  3. Cap diario = $20 USD. Subir config('analisis.cap_diario_usd') de $5 a $20 (cubre el ~50-80% de aumento de costo del contexto enriquecido con margen).
  4. Sí, prompt caching (header beta anthropic-beta: prompt-caching) desde PR 3, para ahorrar 50-70% en re-envíos del system.
  5. Solo bandera, NUNCA bloqueo automático por violación de política. Una violación (ej. $200 en comida con límite $150) genera bandera/observación para el auditor humano, jamás rechazo automático. Coincide con la filosofía “no definitivo / apelable” del milestone de consumos. → En PR 3, las violaciones_politica del tool no disparan consumos.estatus; solo se persisten y se muestran.

Archivos que se tocan

  • database/migrations/2026_05_27_*_create_politicas_viaticos_table.php (nuevo)
  • database/migrations/2026_05_27_*_add_politica_hash_to_auditorias_viaje.php (nuevo)
  • database/migrations/2026_05_27_*_add_politica_fields_to_consumos_analisis.php (nuevo, PR 3)
  • database/seeders/PoliticasViaticosSeeder.php (nuevo)
  • app/Models/PoliticaViaticos.php (nuevo)
  • app/Models/PoliticaViaticosHistorial.php (nuevo)
  • app/Nova/PoliticaViaticos.php (nuevo)
  • app/Nova/PoliticaViaticosHistorial.php (nuevo)
  • app/Services/Vision/ContextoTicketBuilder.php (nuevo, PR 3)
  • app/Services/Vision/AnalizadorTicketService.php:178 (firma analizar(), payload messages, tool schema, cache_control)
  • app/Services/Vision/ResumidorAuditoriaService.php:44 (cargar política, inyectar)
  • app/Services/Vision/AuditorViajeService.php:43 (persistir politica_hash; PR 3 también contexto)
  • app/Jobs/AnalizarFotoConsumoJob.php:80 (construir contexto antes de llamar)
  • tests/Unit/Services/Vision/AnalizadorTicketServiceTest.php (nuevo/ampliar)
  • tests/Feature/AuditarViajeTest.php (nuevo/ampliar)