thinkingConfig must be a top-level key in the request, NOT nested inside
generationConfig. This is required for gemini-2.5-flash and newer models.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gemini-2.5-flash and newer require thinkingBudget:0 to disable thinking
mode, otherwise the API rejects the request or returns unexpected format.
Regex matches gemini-2.5-* and gemini-3.x-* automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- gemini_model is now a saved config field (default: gemini-2.0-flash)
- Config panel shows a text input to set the model name
- probarGemini() reads the configured model and on error calls ListModels
to show exactly which model names are available for the account's API key
- GeminiIntentService and GeminiVisionService build the URL from config at runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gemini-1.5-flash was removed from the v1beta API. Updated all three
references (GeminiIntentService, GeminiVisionService, config test).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
probarGemini() sends a minimal prompt to gemini-1.5-flash and reports
latency + trimmed response text, or the error message from the API.
Mirrors the existing Whisper test button pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lizandro.guarnizo (17 chars) was showing 'lizandro.' (9 chars including the dot)
because ceil(17/2)=9. Now always shows exactly min(4, len) chars: 'liza*************@domain'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- STATE_TTL: 7200s → 315360000s (10 years) so sessions survive indefinitely
- Telegram: "salir"/"/salir" now shows inline confirmation keyboard instead of going straight to menu; "Cerrar sesión" button added to main menu; logout_ask/logout_confirm/confirmLogout() handler clears state and returns to email prompt
- Web chat: "Cerrar sesión" button in main menu; clicking it shows a confirmation message with "Sí, cerrar sesión" / "No, quedarme" buttons; only the confirmed action calls cerrarSesion()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration + AiUsageLog model with tokens, duration, time, cost fields
- GeminiIntentService and GeminiVisionService log every API call
with input/output token counts from usageMetadata and response time
- TelegramBotService logs Whisper calls with audio duration (from Telegram)
and calculates cost at $1/second
- Whisper voice duration now passed from TelegramWebhookController
- Free text in Telegram now tries Gemini intent detection before showing menu
- /chat/ia-logs: Livewire component with summary cards + filterable table
- Whisper connection test button in config panel
- "Logs IA" link added to Chat/Bot nav section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- handleVoice() downloads Telegram audio and transcribes via Whisper API
- transcribeAudio() calls Whisper with Basic Auth, handles json response
- TelegramWebhookController now routes message.voice and message.audio
- Whisper URL, token and toggle configurable from admin config panel
- Transcribed text echoed back to user then processed as normal text intent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Any message matching salir/menu/volver/cancelar/inicio resets flow and shows main menu
- Register /menu /salir /cancelar as official bot commands via setMyCommands
- Commands are registered automatically when saving the webhook from admin panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
file_get_contents fails on many hosts due to allow_url_fopen or SSL restrictions.
Http:: (Guzzle) is more reliable and shows a clearer error message on failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add detectarIntencionKeyword() with regex patterns in Colombian Spanish
that runs before Gemini (instant, no API cost)
- Gemini is now only called for ambiguous text
- Improve Gemini prompt with real examples and informal Spanish
- Replace generic "no entendí" with actionable buttons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Filter promos by TarifaPromo.rol_id (same as services)
- Fix visible filter: use string 'true' not boolean
- Get price from Usuario_promo or TarifaPromo instead of Promociones.precio
- Get utilidad from TarifaPromo for correct margin tracking
- Applied same fixes to TelegramBotService promo flows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace tarifa_id+estado='ocupado' query with the same logic used in
ShowServicios: filter by servicio_id, date range, historiales_count
- Full-account plans (pantallas == completa): look for estado='pendiente'
accounts without assigned profiles
- Partial plans (screen sharing): look for estado='activo' accounts
with remaining slots (historiales_count <= pantallas_servicio - pantallas)
- Wrap purchase in DB::transaction to avoid race conditions
- Remove incorrect cuenta->update(['estado' => 'ocupado']) — availability
is now tracked via historial_cuentas count, not a flag
- Use effective price (Preferencial override) for saldo deduction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add pollMensajes() so wire:poll no longer force-scrolls to bottom
- MutationObserver detects new DOM nodes: auto-scrolls only if user
is already at the bottom (within 80px threshold)
- Floating '↓ N mensaje nuevo' badge appears when new bot messages
arrive while user is scrolled up; tap badge to jump to bottom
- scroll-chat event (emitted on button clicks/sends) still forces
scroll since the user just took an action
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Web chat: 'Otro valor' button sets flujo=recarga.monto_custom,
next text message is parsed as the amount (min $1.000)
- Telegram: '✏️ Otro valor' button sets step=await_amount,
next message is parsed and routed to method selection
- Preset amounts in Telegram now show 2 per row for compactness
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New TelegramBotService: full OTP auth, registration, main menu with
inline keyboards, buy plans, buy promos, recharge (MP + Bre-B),
credentials, history, profile, agent transfer, photo receipt analysis
- TelegramWebhookController: now handles text, callback_query and photo
- Role-based pricing: tarifas filtered by user rol_id in both web chat
and Telegram; Preferencial price overrides base tarifa valor
- Services and promos now render as horizontal scroll slider
(cards_slider tipo_ui) instead of stacked individual cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- abrirChat(): mueve cargarMensajes() al final (después de crear mensajes en DB)
- abrirChat(): siempre agrega saludo+menú al autenticarse (no hay duplicación
porque mount() no llama a abrirChat())
- mount(): solo restaura estado desde sesión, sin agregar mensajes nuevos
- public-chat.blade.php: root div recupera flex flex-col (flex-1 en hijos funciona)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mount(): restaura userId y convId desde sesión Laravel al recargar página
- abrirChat(): guarda chat_user_id y chat_conv_id en sesión
- cerrarSesion(): limpia la sesión correctamente
- Desktop: chat centrado como frame (420px, bordes redondeados, sombra)
- Móvil: pantalla completa sin restricción de ancho
- teclado virtual: ajuste de altura solo en móvil
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix: autocomplete="off" en campo OTP (evita que el correo aparezca ahí)
- Recarga: nuevo paso elegir método (MercadoPago o Transferencia bancaria)
- Transferencia: muestra card con banco/clave/titular/monto y aviso de comprobante
- Validación automática al enviar foto del comprobante (flujo existente)
- Config admin: nuevos campos banco/clave/titular para transferencia
- UI: card de banco con estilo diferenciado (azul), aviso destacado en ámbar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reemplaza campo teléfono por correo electrónico en el inicio del chat
- Si el correo existe: envía OTP y abre el chat
- Si no existe: ofrece crear cuenta automáticamente (pide nombre)
- Si rechaza registro: vuelve al formulario inicial
- Cuenta nueva se crea con contraseña aleatoria
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Chat público en /chat con flujos de compra, recarga y credenciales
- Verificación de identidad por código OTP enviado al correo registrado
- Botones enriquecidos: cards, links, credenciales, validación de comprobante
- Gemini IA para detección de intención en texto libre
- Gemini Vision para extraer datos de foto de comprobante
- Validador de pagos cruzando IA con correos IMAP (Bancolombia)
- MercadoPago como pasarela de pago (reemplaza Wompi en el chat)
- Migraciones: payload en chat_messages, user_id en chat_contacts
- Config admin: API key Gemini, toggles IA y validación de foto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trim and lowercase email before storing or attempting login to avoid mismatches from case or surrounding whitespace. Updated RegisteredUserController@store to save 'email' => strtolower(trim($request->email)) and LoginRequest@authenticate to call Auth::attempt with the normalized email input.
Added Device and UserIp models and migrations to store device fingerprints and user IPs with expiration. Updated ShowSavingIp Livewire component and view to capture and persist both IP and device fingerprint. Introduced a scheduled command to clean expired user IPs and registered it in the console kernel. Removed the old migration for storing IPs directly on the users table.
Introduces a new rank management system including the Rank model, migrations for the ranks table and user IP address, Livewire components for managing and displaying ranks, and related Blade views. Updates navigation and routes to include the new 'Gestión Rangos' section, and enhances the ShareCodeComponent with user data. Also adds a reusable input field component for forms.
Introduces ShowSavingIp Livewire component to fetch, display, and save the authenticated user's IP address. Integrates the component into the navigation layout and comments out the unused /ip-test route.