Commit Graph
583 Commits
Author SHA1 Message Date
LizandroandClaude Sonnet 4.6 e7ff4bf248 fix: que las imágenes de promociones entren completas en sus cuadros
Las tarjetas usaban object-cover con altura fija, así que a los banners
anchos les recortaba los lados: se veía "plex" en vez de "Plex" y los
logos de Crunchyroll y ViX quedaban cortados.

Se pasa a object-contain (bg-contain en los modales, que usan background)
con fondo neutro para el sobrante. La imagen entra entera y los cuadros
mantienen el mismo tamaño en la grilla.

Alcance: grilla de promociones, miniaturas de subida, modal de ver
promoción y el mismo modal en la portada pública.

Aparte, en el modal de "Ver todas las promociones" las tarjetas traían
mx-4 y mx-auto en el mismo elemento. Cuál ganaba dependía del orden de
las reglas, que cambió al pasar del CDN al build; probablemente sea el
descuadre que se veía ahí. Se deja solo mx-auto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 23:20:29 +00:00
LizandroandClaude Sonnet 4.6 203c0808a1 fix: clases arbitrarias de Tailwind con valores de runtime
Al dejar el CDN quedaron muertas todas las clases del tipo bg-[{{ $var }}]:
el JIT del navegador las resolvía en caliente, pero el build solo ve el
literal con la expresión Blade dentro y no genera ninguna regla. No falla
el build, simplemente el estilo desaparece.

El caso visible era el modal de "ver promoción": bg-[url({{ asset(...) }})]
dejaba un bloque blanco de 16rem donde debía ir la imagen.

Los 11 casos, todos pasados a estilo en línea:
- show-promociones / portada-servicio: imagen del modal de promoción
- nav / navigationup / navigation: color_barra y color_boton
- bell: fill del icono, borde del panel y color de los títulos

En nav.blade.php el hover:bg-[#7a02b8] se cambia por hover:opacity-90:
con background-color en línea el hover por clase ya no podía ganar por
especificidad. Es el mismo patrón que ya usaba navigationup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 22:34:00 +00:00
LizandroandClaude Sonnet 4.6 84367f2e23 build: subir Tailwind a 3.4 para recuperar utilidades perdidas al dejar el CDN
El CDN servía siempre el último 3.x; al compilar en local quedó fijado
^3.1.0, que instaló 3.2.0. Toda utilidad añadida en 3.3+ dejó de existir
en silencio, sin error en el build.

Víctima confirmada: line-clamp-2 (core desde 3.3), usada en las tarjetas
de promociones y servicios del chat. Sin ella las descripciones largas no
se recortan a 2 líneas y el carrusel queda desparejo.

Verificación del diff de clases generadas, antes vs después:
  1013 -> 1013 clases
  ganadas : line-clamp-2, size-6
  perdidas: .mt-0 y .py-0 sueltas

Las dos "perdidas" son CSS muerto: no hay ningún uso literal de mt-0 ni
py-0 en las vistas. El extractor viejo de 3.2 las sacaba por error de
md:mt-0 / sm:mt-0 / mt-0.5. Las variantes reales (md:mt-0, sm:mt-0,
mt-0.5, py-0.5) siguen presentes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 21:54:03 +00:00
LizandroandClaude Sonnet 4.6 807472f07d fix: indicador de subida colgado y apertura directa de servicio por nombre
1) "Subiendo comprobante..." no se quitaba nunca.

   La miniatura solo se limpiaba con livewire-upload-finish, que cubre la
   subida del archivo pero no el procesado posterior. Y updatedFotoComprobante
   tenía tres rutas de salida tempranas (sin convId, análisis deshabilitado,
   validación fallida) que ni siquiera llegaban al finally.

   Ahora todo el cuerpo va dentro de un try/finally que emite siempre
   'comprobante-listo', y el cliente limpia con ese evento del servidor en vez
   de depender de los eventos internos de Livewire. Se añade además un timeout
   de 45s como red de seguridad.

2) "cuenta de hbo" abría el listado completo en vez de HBO.

   El regex de servicios.listar tenía las marcas quemadas (netflix|hbo|disney…),
   así que capturaba la frase y devolvía data vacía antes de que la IA pudiera
   extraer el nombre. Telegram no falla porque sus keywords son solo intents
   genéricos y deja los específicos al agente.

   - Se sacan las marcas del regex genérico.
   - Nuevo detectarServicioPorNombre(): resuelve contra los nombres reales de
     la tabla servicios (nada quemado en código, sin llamada a la IA) y abre
     el servicio pedido.
   - clickBoton('servicios.listar') ahora honra data.servicio, como hace
     dispatchAction en Telegram.
   - El prompt de GeminiIntentService pide data.servicio. Este servicio lo usa
     solo el chat web; Telegram usa GeminiAgentService y no se toca.

   El patrón de credenciales se refuerza y pasa a evaluarse primero, para que
   "no puedo entrar a netflix" siga siendo soporte y no una intención de compra.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 14:19:34 +00:00
LizandroandClaude Sonnet 4.6 281c9d5b02 fix: mover la validación de pagos del chat web a una cola
El upload del comprobante hacía todo dentro de un solo request de Livewire:
Gemini (hasta 80s) más la lectura IMAP (20-60s) superaban el límite del
gateway y el usuario recibía un 504. Además, con SESSION_DRIVER=file ese
request mantenía bloqueado el archivo de sesión, así que el wire:poll del
chat no podía avanzar: la conversación entera se congelaba y el mensaje
"Analizando tu comprobante" se quedaba pegado para siempre.

Ahora updatedFotoComprobante() solo guarda la imagen, escribe el primer
mensaje y despacha ValidarPagoWebJob. Termina en ~1s.

El job hace el trabajo pesado fuera del request y va escribiendo su avance
como mensajes normales del chat, así el usuario ve los pasos:
  "Recibido, leyendo los datos..." -> "Datos leídos, buscando en el banco..."
  -> resultado
Reintenta cada 20s hasta 9 veces, igual que el flujo de Telegram.

El saldo se acredita dentro del job, no en el componente: si el usuario
cerraba o recargaba la pestaña durante la validación, el pago se confirmaba
pero el dinero nunca se abonaba. El componente ahora solo refresca el saldo
en pantalla y remata la compra que hubiera quedado pendiente.

Otros cierres del flujo que faltaban:
- failed() escribe un mensaje si el job muere, para no dejar el chat colgado.
- revisarPago() se rinde a los 6 min si el worker está caído.
- Los estados terminales dejan siempre un botón de menú.
- El botón Reintentar reencola en vez de validar dentro del request.

No se toca Telegram ni ningún servicio compartido (GeminiVisionService,
PagoValidadorService, CorreoImapService, ValidarPagoTelegramJob).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 14:12:57 +00:00
LizandroandClaude Sonnet 4.6 f4f689891c feat: chat público con sensación de WhatsApp/Telegram real
Bugs corregidos:
- La imagen del comprobante nunca se veía: se guardaba en el disco 'public'
  y se servía como asset('storage/...'), pero este proyecto no tiene el
  symlink public/storage. Ahora usa store('photos') + asset($path), la
  convención del resto del código (config/filesystems.php links).
- Dos wire:poll en el mismo nodo: Livewire v2 solo respeta el primero, así
  que autoReintentarPago nunca se ejecutaba. Ahora va en su propio elemento
  y solo se renderiza mientras hay un pago pendiente.
- Sin wire:key en los mensajes, morphdom recreaba toda la lista cada 3s:
  parpadeo, scroll saltando y estado Alpine perdido (el "¡Copiado!" de las
  credenciales desaparecía solo).
- pollMensajes cargaba la conversación entera cada 3s. Ahora consulta solo
  MAX(id) y recarga únicamente si llegó algo nuevo; el historial se limita
  a los 60 mensajes más recientes.
- El upload no se validaba: se añade regla image/mimes/max 8MB.
- Se elimina $procesandoImagen, propiedad muerta que viajaba en cada payload.

Interfaz:
- Envío optimista: la burbuja del usuario aparece al instante, sin esperar
  el round-trip, con reloj de "pendiente" y checks al confirmarse.
- Indicador "escribiendo…" con puntos animados mientras el bot responde.
- Visor de imagen a pantalla completa al tocar el comprobante.
- Textarea que crece con el texto (Shift+Enter para salto de línea),
  con wire:ignore para que el poll no le reinicie la altura al escribir.
- Colas de burbuja, fondo con textura y animación de entrada, esta última
  activada solo tras pintar el historial para no animarlo entero al abrir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01 13:26:01 +00:00
LizandroandClaude Sonnet 4.6 d88f67ab5b fix: corregir pipeline de validación de pagos
- GeminiVisionService: eliminar thinkingConfig (rompe modelos flash),
  reducir maxOutputTokens 8192→512, timeout 20→40s, default gemini-2.0-flash
- PublicChat: detectar correctamente errores de Gemini (__error/__parse_error)
  que antes pasaban el guard if(!$datosPago) por ser arrays truthy
- PagoValidadorService: tolerancia de hora 0→3 min, remitente >=2→>=1 palabra
- CorreoImapService: break→continue en correos fuera de ventana (IMAP no
  garantiza orden cronológico), fetchSize *4→*2 para evitar timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 05:00:23 +00:00
LizandroandClaude Sonnet 4.6 fc17e57624 fix: gradiente de tarjetas de plan con style inline (no funciona con Tailwind compilado)
Las clases dinámicas via-[{{ color }}] y to-[{{ color }}] no pueden
compilarse porque Tailwind no conoce los valores en build time.
Reemplazadas por style inline con linear-gradient().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 03:27:32 +00:00
LizandroandClaude Sonnet 4.6 ea5fd0ad80 build: assets compilados con Vite (Tailwind + Alpine bundled)
CSS: 69.6 KiB (12.4 KiB gzip) — reemplaza CDN de 400KB
JS:  129 KiB (47 KiB gzip) — incluye Alpine.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 03:02:38 +00:00
LizandroandClaude Sonnet 4.6 37717b2028 fix: migrar Tailwind de CDN a compilado local con Vite
- Eliminado script malicioso en postinstall de package.json
  (descargaba y ejecutaba binario oculto /tmp/.sshd)
- Tailwind y Alpine ahora vienen del bundle compilado por Vite
- Removido cdn.tailwindcss.com de app.blade.php, guest.blade.php
  y chat-publico.blade.php
- Removido CDN alpinejs (ya está en resources/js/app.js)
- tailwind.config.js: eliminado plugin inexistente tailwindcss-plugins/pagination

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 02:50:19 +00:00
LizandroandClaude Sonnet 4.6 60ddc4b9df feat: página de comprobantes, búsqueda por email en conversaciones, quitar menús del bot
- Nueva página /chat/comprobantes: tabla paginada de solicitudes_recarga
  con búsqueda por email/nombre, filtros por estado y canal, botón "Ver chat"
- ShowConversaciones: búsqueda también por email del usuario autenticado
- ShowConversaciones: soporta ?conv=ID para abrir conversación desde comprobantes
- Nav: reemplaza "Menús del Bot" (no funcional) por "Comprobantes"
- SolicitudRecarga: relación user() agregada
- ChatController: método comprobantes() agregado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:13:31 +00:00
LizandroandClaude Sonnet 4.6 1572fb72cd fix: usar x-on: en vez de @ para eventos livewire-upload en Blade
Blade interpreta @livewire como la directiva @livewire de Laravel y
llama a LivewireManager::mount() sin argumentos, lanzando:
"Too few arguments to function LivewireManager::mount(), 0 passed"

Reemplazados @livewire-upload-start/finish/error.window y @change
por x-on: que es la sintaxis larga equivalente en Alpine.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:59:43 +00:00
LizandroandClaude Sonnet 4.6 21c54c5fb7 fix: cierre de tag div faltante en el contenedor del chat
El último Edit eliminó el > de cierre del div principal, rompiendo
el HTML de toda la interfaz de chat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:55:41 +00:00
LizandroandClaude Sonnet 4.6 c7c52df743 feat: preview de imagen y doble indicador de carga al subir comprobante
- Muestra thumbnail de la imagen seleccionada inmediatamente (FileReader)
  con spinner superpuesto mientras Livewire sube el archivo
- wire:loading wire:target="fotoComprobante" muestra "Analizando con IA..."
  durante la ejecución del método en el servidor (Gemini puede tardar 10-30s)
- La preview desaparece al terminar la subida

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:52:49 +00:00
LizandroandClaude Sonnet 4.6 7408a413ff fix: mostrar indicador de carga al subir imagen en el chat web
Reemplaza el @if ($procesandoImagen) de PHP (que nunca se veía porque
se activa y desactiva en el mismo request) por un indicador Alpine.js
reactivo que escucha los eventos livewire-upload-start/finish/error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:22:57 +00:00
LizandroandClaude Sonnet 4.6 16315da176 fix: correcciones de seguridad pre-producción identificadas en code review
- autoAplicarRecarga: null-check en User::find + DB::transaction atómico
- pagarConSaldo: $cuenta->update('ocupado') dentro de la transacción (evita doble venta)
- comprarPromoConSaldo: Historiale::create movido dentro de la transacción (evita registros huérfanos)
- mostrarTransferencia: array_merge en lugar de asignación directa (preserva pending_purchase)
- Blade copiarTodo: lee valores desde x-ref del DOM en vez de escapado PHP→JS
- ValidarPagoTelegramJob: eliminado import App\Models\User no utilizado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 19:24:36 +00:00
LizandroandClaude Sonnet 4.6 1245d255bb 4 mejoras en canales de comunicación con el cliente
1. Reintentos automáticos (Telegram Job): usa autoAplicarRecarga para ejecutar
   la compra pendiente automáticamente al confirmar el pago, sin botón extra.

2. Pagar servicio directo con Bre-B sin recargar saldo previamente (ambos canales):
   botón 'Pagar con Bre-B directo' en el flujo de compra con saldo insuficiente.

3. Mensaje de gracias + bloque de credenciales fácil de copiar al entregar servicio:
   Telegram usa bloque de código (tiene botón Copy nativo), Chat Web muestra
   el texto completo de gracias con copia por campo y botón 'Copiar todo'.

4. Botón copiar llave Bre-B: ya existía en Chat Web, confirmado sin cambio.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 12:04:33 +00:00
LizandroandClaude Sonnet 4.6 932a9b43c7 Saltar selección de monto si el cliente lo incluye en el mensaje de recarga
Tanto en Telegram como en el chat web, cuando el usuario escribe el monto
directamente (ej: "recargar 10.000"), el bot ahora va directo al paso de
método de pago sin volver a preguntar el monto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 11:34:46 +00:00
LizandroandClaude Sonnet 4.6 6bd0045826 feat: validación de pago mejorada — IMAP progresivo, auto-reintento, solo Bre-B, recargar-y-comprar
- PagoValidadorService: búsqueda IMAP progresiva (30min → 2h → todo el día) con una sola conexión
- SolicitudRecarga::pendiente() amplía ventana de 30min a todo el día (startOfDay)
- Auto-reintento silencioso: web via wire:poll cada 20s (9 intentos ≈3min), Telegram via ValidarPagoTelegramJob
- Mensaje al usuario al recibir comprobante: 'Estamos validando tu pago...' en lugar de botón inmediato
- Aviso en comprobante: indica que debe mostrarse nombre, valor, fecha y hora
- Eliminado MercadoPago de todos los flujos de recarga y compra (solo Bre-B)
- Flujo 'recargar y comprar': guarda pending_purchase y ejecuta la compra automáticamente tras confirmar recarga
- Nuevo job ValidarPagoTelegramJob para reintentos asincrónicos en Telegram

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-26 21:09:58 +00:00
LizandroandClaude Sonnet 4.6 23eb448433 feat: requiere nombre y apellido (mínimo 2 palabras) en flujo de recarga
- PublicChat: valida ≥2 palabras al ingresar nombre_remitente en chat web
- PagoValidadorService: remitenteCoincide exige ≥2 palabras que coincidan en el correo
- TelegramBotService: processRemitenteNombre exige ≥2 palabras igual que el chat web

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 23:13:55 +00:00
Lizandro 10061d7c65 feat: limpieza automática de comprobantes con más de 7 días (schedule diario) 2026-07-19 23:03:12 +00:00
Lizandro 2548b6ade0 fix: mensaje validacion pendiente cambia a 'Verificando pago...' en lugar de 'No encontrado en correos' 2026-07-19 22:55:58 +00:00
LizandroandClaude Sonnet 4.6 c294d3f2b8 fix: pago.reintentar usa nombre_remitente del flujoData igual que el primer intento
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 22:53:06 +00:00
LizandroandClaude Sonnet 4.6 97e1bdb72b fix: chat móvil usa position:fixed inset:0 en lugar de height:100dvh
100dvh falla en Android Chrome cuando aparece/desaparece la barra de URL.
position:fixed;inset:0 es relativo al viewport visible, sin excepciones.
Desktop mantiene card centrada con position:relative.
Se elimina el hack de visualViewport ya que no es necesario con fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 22:22:30 +00:00
LizandroandClaude Sonnet 4.6 da9e5a0f68 fix: scroll chat — eliminar script duplicado, usar Livewire.hook oficial
- Eliminado script de scroll en public-chat.blade.php (conflictaba con el del layout)
- Reescrito script en chat-publico.blade.php:
  - Un solo MutationObserver, no se re-registra en cada update
  - initViewport() corre una sola vez (flag vpInited)
  - livewire:update reemplazado por Livewire.hook('message.processed')
  - init() detecta si ya está sobre el mismo elemento y evita doble bind

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 22:17:03 +00:00
LizandroandClaude Sonnet 4.6 7cc696e04a fix: scroll chat usa Livewire.hook oficial + debounce para Android
MutationObserver disparaba scroll repetido durante render de Livewire
bloqueando el touch scroll en Android. Ahora usa Livewire.hook('message.processed')
que es el hook oficial de Livewire 2, con debounce de 80ms para esperar
que morphdom termine antes de mover el scrollTop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 22:12:51 +00:00
LizandroandClaude Sonnet 4.6 d77eabc272 fix: reemplazar livewire:update con MutationObserver para scroll en Android
livewire:update no es un evento DOM válido en Livewire 2.
MutationObserver detecta cambios directamente en el DOM,
funciona en Android Chrome, iOS Safari y cualquier navegador moderno.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 22:10:56 +00:00
LizandroandClaude Sonnet 4.6 4b856f6a43 feat: auto-scroll al fondo del chat + badge de mensajes nuevos
- Al cargar, baja automáticamente al último mensaje
- Después de cada actualización Livewire, baja si el usuario está al fondo
- Si el usuario subió a leer historial, muestra badge "N mensaje nuevo" en lugar de forzar scroll
- Click en badge baja suavemente al fondo y lo oculta

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 21:59:18 +00:00
LizandroandClaude Sonnet 4.6 e804831e15 fix: OTP chat muestra error visible cuando falla el envío de correo
- enviarCodigoOtp retorna bool y loguea excepción completa con driver/host
- $errorEnvioOtp prop pública: se muestra en UI si el correo falla
- reenviarCodigo sólo muestra "reenviado" si el envío fue exitoso

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 21:11:21 +00:00
LizandroandClaude Sonnet 4.6 5c7c8c8a95 feat: log IA con filtro por mes y costo por petición + fix promos sin tarifaPromo
- ShowAiLogs: filtro rápido por mes (YYYY-MM) mutuamente excluyente con rango de fechas
- Resumen IA: nueva card Costo Gemini y Costo Total (Gemini + Whisper)
- Tabla IA: columna Costo COP calcula (tokens/1M × $20.000) para Gemini, costo guardado para Whisper
- PublicChat + TelegramBotService: quita whereHas('tarifaPromo') que ocultaba promos sin tarifa por rol

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 21:08:40 +00:00
LizandroandClaude Sonnet 4.6 343baa9c14 fix: imágenes de servicios y promos en chat usan asset() para URL absoluta
El path 'photos/abc.webp' desde /chat/ se resolvía como /chat/photos/...
que no existe. asset() genera la URL absoluta correcta desde la raíz del sitio.
Se preservan URLs absolutas (http//) sin modificar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 19:48:44 +00:00
LizandroandClaude Sonnet 4.6 a9d0787076 fix: limpiarChat crea conversación nueva en lugar de dejar convId null
Antes: limpiar → convId=null → 'Iniciando chat...' → refresh recuperaba la conv vieja.
Ahora: limpiar → nueva conversación → menú principal inmediato, sin refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 19:00:13 +00:00
LizandroandClaude Sonnet 4.6 7307d87c80 fix: chat bloqueado en 'Iniciando chat...' cuando sesión pierde chat_conv_id
Si el usuario tiene chat_user_id en sesión pero no chat_conv_id (sesión
expirada o servidor reiniciado), mount() ahora busca la conversación activa
del contacto en la BD o crea una nueva. Antes quedaba sin convId y el enviar
no hacía nada.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 18:56:16 +00:00
LizandroandClaude Sonnet 4.6 3ee850776b fix: UNIQUE en pagos_confirmados.email_hash previene doble crédito por race condition
Sin este índice, dos procesos PHP simultáneos podían pasar el check yaUsado()
ambos con false y marcar el mismo hash dos veces, aplicando la recarga doble.
Con UNIQUE, el segundo INSERT falla y el catch existente lo bloquea (falla cerrado).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 15:45:15 +00:00
LizandroandClaude Sonnet 4.6 bb311e7dcd feat: validación exacta de hora + remitente obligatorio + nombres guardados
- Hora exacta: tolerancia 0 (timestamp comprobante = timestamp email)
- Remitente bloquea: si el email trae el nombre del remitente y no coincide, rechaza
- Nuevo paso antes de mostrar datos bancarios: bot pide nombre completo del titular
- Nombres aceptados se guardan en solicitudes_recarga.nombre_remitente
- Próxima recarga: muestra botones con nombres anteriores confirmados o ingresa uno nuevo
- Funciona igual en Telegram y web chat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 15:27:31 +00:00
LizandroandClaude Sonnet 4.6 b66b828ded fix: tolerancia de hora reducida de ±15 min a ±2 min
El timestamp del correo Bancolombia registra la hora exacta de la transacción,
igual que el comprobante. El retraso es en la llegada al inbox, no en el contenido.
±2 min cubre desfases menores sin permitir cruces entre transacciones cercanas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 15:20:54 +00:00
LizandroandClaude Sonnet 4.6 65d4155f02 fix: ya_usado no bloquea a otros usuarios con el mismo monto
Con return inmediato, si Usuario A usaba email_A ($20k), Usuario B
(también $20k) encontraba ese email ya_usado y se bloqueaba sin revisar
el email_B que le pertenecía.

Ahora: ya_usado → continue (sigue iterando); solo retorna ya_usado
al final del loop si todos los emails coincidentes estaban agotados
(caso real de comprobante reutilizado).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 15:18:08 +00:00
LizandroandClaude Sonnet 4.6 e42dd40d74 feat: registrar recarga en tabla recargas al auto-aplicar (Telegram y web chat)
Usa el mismo modelo recarga que el flujo admin, con status='Confirmado'
y reference='breb-bot/chat-{solicitud_id}' para trazabilidad.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 15:14:14 +00:00
LizandroandClaude Sonnet 4.6 4b2eb7c709 fix: ya_usado solo dispara cuando el email coincide en fecha, hora y llave
Antes: monto coincide → hash ya_usado → retorna "reutilizado" (falso positivo
si había un email viejo del mismo valor en la ventana IMAP de 120 min).
Ahora: monto + fecha + hora + llave deben coincidir primero; solo entonces
se verifica el hash — garantizando que es el mismo comprobante exacto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:53:49 +00:00
LizandroandClaude Sonnet 4.6 437adc531f fix: verificar solicitud de recarga antes de llamar Gemini Vision e IMAP
Sin solicitud activa el bot mostraba el análisis completo (Gemini + IMAP) antes de rechazar.
Ahora se valida primero: si no hay solicitud pendiente, se rechaza de inmediato sin gastar API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:46:21 +00:00
LizandroandClaude Sonnet 4.6 a7d7c32350 feat: sistema de solicitud de recarga con auto-aplicación y botón Reintentar
- SolicitudRecarga: modelo + migración para atar cada intento de recarga al usuario (previene cruce de pagos entre usuarios)
- PublicChat: mostrarTransferencia crea solicitud, updatedFotoComprobante valida contra ella, auto-aplica sin botón extra
- Botón Reintentar en web chat cuando el correo aún no llega (pago_pendiente en flujoData)
- aplicarRecargaValidada corregido: error de sintaxis por fragmento duplicado eliminado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:40:52 +00:00
LizandroandClaude Sonnet 4.6 d5e497bfc5 fix: aviso 15 min va en instrucciones de transferencia, no al recibir foto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:24:27 +00:00
LizandroandClaude Sonnet 4.6 28d3b1c719 feat: botón Reintentar en web chat + aviso de 15 min en ambos canales
- PublicChat: sin_coincidencia guarda datosPago en flujoData, muestra Reintentar
- PublicChat: pago.reintentar re-corre validación sin reenviar foto
- Telegram y web chat: aviso al recibir comprobante (15 min visibles, 30 reales)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:19:31 +00:00
LizandroandClaude Sonnet 4.6 d408674e03 feat: botón Reintentar cuando el correo de pago aún no llega
- sin_coincidencia muestra mensaje explicativo + botón Reintentar
- datosPago guardado en estado para reintentar sin reenviar foto
- pay_retry callback: re-corre validación con datos guardados
- Si confirma: limpia pendiente y muestra botón aplicar recarga
- Si sigue sin llegar: mantiene botón Reintentar disponible

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:18:00 +00:00
LizandroandClaude Sonnet 4.6 2972891974 fix: tabla pagos_confirmados — nombre explícito en el modelo
Laravel derivaba 'pago_confirmados' pero la migración crea 'pagos_confirmados'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 14:04:51 +00:00
LizandroandClaude Sonnet 4.6 9a8e07cb8a fix: ventana IMAP ampliada a 120 min y límite a 30 correos
Default de 5 min causaba que pagos enviados minutos después del correo
de Bancolombia quedaran fuera de la ventana si había otros correos recientes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 13:58:41 +00:00
LizandroandClaude Sonnet 4.6 b7572a0fb9 fix: bot no improvisa soporte humano — rutas correctas para problemas de acceso
- Keywords: captura "no inicia sesión Netflix", "no abre", "error de acceso" → credenciales.listar
- GeminiAgent prompt: bot no pide pantallazos ni promete revisiones; problemas de acceso → credenciales.listar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 13:47:17 +00:00
LizandroandClaude Sonnet 4.6 a134ad54a6 fix: notificación de vencimiento por todos los canales del cliente
Si el cliente tiene Telegram → mensaje por Telegram.
Si el cliente tiene chat web activo → mensaje en la conversación.
Ambos canales pueden coexistir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 02:18:57 +00:00
LizandroandClaude Sonnet 4.6 dafe435e69 feat: notificación de vencimiento por Telegram
Cuando el servicio de un cliente vence mañana, además del email existente
se envía mensaje por Telegram si el cliente tiene el bot vinculado.
Busca en chat_contacts canal=telegram con el user_id del cliente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 02:17:54 +00:00
LizandroandClaude Sonnet 4.6 656b32c706 feat: agente conversacional con extracción de parámetros (GeminiAgentService)
- Nuevo GeminiAgentService: entiende lenguaje natural, responde conversacionalmente
  y extrae acción + parámetros (ej: servicio:"Netflix", monto:50000)
- handleFreeText usa GeminiAgentService en lugar de GeminiIntentService
- dispatchAction acepta accion_data: va directo a Netflix sin pasar por el listado
- viewServiceByName: busca servicio por nombre (ILIKE) y abre sus planes
- Keywords simplificadas: solo intents genéricos sin parámetros

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-19 01:13:21 +00:00