Modificación de Mayusculas en clase documneto tipo
This commit is contained in:
@@ -37,12 +37,21 @@ class WhatsAppAnalyticsController extends Controller
|
||||
$query->where('created_at', '<=', $request->fecha_hasta . ' 23:59:59');
|
||||
}
|
||||
|
||||
$enviados = (clone $query)->where('tipo', 'enviado')->count();
|
||||
$fallidos = (clone $query)->where('tipo', 'error')->count();
|
||||
$optOuts = (clone $query)->where('tipo', 'opt_out')->count();
|
||||
$recibidos = (clone $query)->where('tipo', 'recibido')->count();
|
||||
// Métricas siempre sobre todos los tipos (sin filtro de tipo)
|
||||
$enviados = (clone $query)->where('tipo', 'enviado')->count();
|
||||
$fallidos = (clone $query)->where('tipo', 'error')->count();
|
||||
$optOuts = (clone $query)->where('tipo', 'opt_out')->count();
|
||||
$recibidos = (clone $query)->where('tipo', 'recibido')->count();
|
||||
$entregados = (clone $query)->where('tipo', 'status_delivered')->count();
|
||||
$leidos = (clone $query)->where('tipo', 'status_read')->count();
|
||||
|
||||
$logsRecientes = (clone $query)
|
||||
// El filtro de tipo solo aplica al listado de logs
|
||||
$logsQuery = clone $query;
|
||||
if ($request->filled('tipo')) {
|
||||
$logsQuery->where('tipo', $request->tipo);
|
||||
}
|
||||
|
||||
$logsRecientes = $logsQuery
|
||||
->with('alumno.alumnos')
|
||||
->latest('created_at')
|
||||
->paginate(20);
|
||||
@@ -52,7 +61,7 @@ class WhatsAppAnalyticsController extends Controller
|
||||
})->latest()->get();
|
||||
|
||||
return view('promocion.analytics', compact(
|
||||
'enviados', 'fallidos', 'optOuts', 'recibidos',
|
||||
'enviados', 'fallidos', 'optOuts', 'recibidos', 'entregados', 'leidos',
|
||||
'logsRecientes', 'planteles', 'promociones'
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,115 +2,60 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\NuevoMensajeWhatsApp;
|
||||
use App\Models\Alumno;
|
||||
use App\Models\WhatsappLog;
|
||||
use App\Services\WhatsAppService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class WhatsAppWebhookController extends Controller
|
||||
{
|
||||
private array $optOutKeywords = ['stop', 'no', 'cancelar', 'detener', 'baja', 'salir'];
|
||||
|
||||
public function verify(Request $request)
|
||||
public function verify(Request $request): Response
|
||||
{
|
||||
$verifyToken = config('services.whatsapp.verify_token');
|
||||
|
||||
if (
|
||||
$request->get('hub_mode') === 'subscribe' &&
|
||||
$request->get('hub_verify_token') === $verifyToken
|
||||
$request->query('hub_mode') === 'subscribe' &&
|
||||
$request->query('hub_verify_token') === $verifyToken
|
||||
) {
|
||||
return response($request->get('hub_challenge'), 200);
|
||||
return response($request->query('hub_challenge'), 200);
|
||||
}
|
||||
|
||||
return response('Forbidden', 403);
|
||||
}
|
||||
|
||||
public function handle(Request $request, WhatsAppService $whatsapp)
|
||||
public function handle(Request $request, WhatsAppService $whatsapp): JsonResponse
|
||||
{
|
||||
$payload = $request->all();
|
||||
$payload = $request->all();
|
||||
$value = data_get($payload, 'entry.0.changes.0.value', []);
|
||||
$messages = data_get($value, 'messages', []);
|
||||
$statuses = data_get($value, 'statuses', []);
|
||||
|
||||
try {
|
||||
$entry = $payload['entry'][0] ?? null;
|
||||
$changes = $entry['changes'][0] ?? null;
|
||||
$value = $changes['value'] ?? null;
|
||||
$message = $value['messages'][0] ?? null;
|
||||
foreach ($messages as $message) {
|
||||
$telefono = data_get($message, 'from');
|
||||
$texto = data_get($message, 'text.body', '');
|
||||
|
||||
if (!$message) {
|
||||
return response()->json(['status' => 'ok']);
|
||||
WhatsappLog::create([
|
||||
'telefono' => $telefono,
|
||||
'tipo' => 'recibido',
|
||||
'mensaje' => $texto,
|
||||
'whatsapp_mensaje_id' => data_get($message, 'id'),
|
||||
'payload' => $payload,
|
||||
]);
|
||||
|
||||
if (preg_match('/\b(STOP|cancelar)\b/i', $texto)) {
|
||||
$whatsapp->procesarOptOut($telefono);
|
||||
}
|
||||
}
|
||||
|
||||
$telefono = $message['from'] ?? null;
|
||||
$tipo = $message['type'] ?? 'text';
|
||||
$alumno = $this->alumnoPorTelefono($telefono);
|
||||
$alumnoId = $alumno?->id;
|
||||
foreach ($statuses as $status) {
|
||||
$mensajeId = data_get($status, 'id');
|
||||
$statusEnvio = data_get($status, 'status');
|
||||
|
||||
$logData = [
|
||||
'alumno_id' => $alumnoId,
|
||||
'telefono' => $telefono,
|
||||
'tipo' => 'recibido',
|
||||
'payload' => $payload,
|
||||
];
|
||||
|
||||
if ($tipo === 'text') {
|
||||
$texto = $message['text']['body'] ?? '';
|
||||
$logData['mensaje'] = $texto;
|
||||
$logData['media_type'] = null;
|
||||
|
||||
if ($this->esOptOut($texto)) {
|
||||
$whatsapp->procesarOptOut($telefono);
|
||||
$logData['tipo'] = 'opt_out';
|
||||
}
|
||||
} elseif (in_array($tipo, ['image', 'video', 'audio', 'document'])) {
|
||||
$mediaId = $message[$tipo]['id'] ?? null;
|
||||
|
||||
if ($mediaId) {
|
||||
$ruta = $whatsapp->descargarMedia($mediaId);
|
||||
$logData['mensaje'] = $ruta;
|
||||
$logData['media_type'] = $tipo;
|
||||
}
|
||||
}
|
||||
|
||||
$log = WhatsappLog::create($logData);
|
||||
|
||||
if ($alumnoId) {
|
||||
broadcast(new NuevoMensajeWhatsApp($log));
|
||||
|
||||
// Marcar como no leído para el badge
|
||||
WhatsappLog::where('id', $log->id)->update(['leido' => false]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('WhatsApp webhook error', ['error' => $e->getMessage(), 'payload' => $payload]);
|
||||
WhatsappLog::where('whatsapp_mensaje_id', $mensajeId)
|
||||
->update(['status_envio' => $statusEnvio]);
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
}
|
||||
|
||||
private function alumnoPorTelefono(?string $telefono): ?Alumno
|
||||
{
|
||||
if (!$telefono) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$limpio = preg_replace('/\D/', '', $telefono);
|
||||
$sinCodigo = strlen($limpio) === 12 ? substr($limpio, 2) : $limpio;
|
||||
|
||||
return Alumno::whereHas('alumnos', function ($q) use ($limpio, $sinCodigo) {
|
||||
$q->where('telefono', $limpio)->orWhere('telefono', $sinCodigo);
|
||||
})->first();
|
||||
}
|
||||
|
||||
private function esOptOut(string $texto): bool
|
||||
{
|
||||
$normalizado = strtolower(trim($texto));
|
||||
|
||||
foreach ($this->optOutKeywords as $keyword) {
|
||||
if ($normalizado === $keyword || str_contains($normalizado, $keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user