16. Validación del Chatbot
Validación del Chatbot
1. El Problema de Autorización (Error 403)
1.1. ¿Por qué ocurre el error 403?
Cuando creamos el modelo con php artisan make:model Chatbot --all, Laravel genera automáticamente un Form Request con el método authorize() que retorna false por defecto.
En app/Http/Requests/UpdateChatbotRequest.php:
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateChatbotRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false; // ❌ Esto causa el error 403
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}
1.2. Solución: Autorizar la Acción
Modificar UpdateChatbotRequest.php:
php
public function authorize(): bool
{
// Verificar que el chatbot pertenece al usuario autenticado
$chatbot = $this->route('chatbot');
return $this->user()->id === $chatbot->user_id;
}
O simplemente retornar true (si usas políticas):
php
public function authorize(): bool
{
return true;
}
2. Reglas de Validación
2.1. Configurar Reglas en UpdateChatbotRequest
En app/Http/Requests/UpdateChatbotRequest.php:
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateChatbotRequest extends FormRequest
{
public function authorize(): bool
{
$chatbot = $this->route('chatbot');
return $this->user()->id === $chatbot->user_id;
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'system_prompt' => 'required|string|min:10|max:5000',
'model' => 'required|string|in:gpt-3.5-turbo,gpt-4,gpt-4-turbo,gpt-4o',
'temperature' => 'required|numeric|min:0|max:1',
];
}
/**
* Mensajes de error personalizados (opcional)
*/
public function messages(): array
{
return [
'name.required' => 'El nombre del chatbot es obligatorio.',
'name.max' => 'El nombre no puede exceder los 255 caracteres.',
'system_prompt.required' => 'El system prompt es obligatorio.',
'system_prompt.min' => 'El system prompt debe tener al menos 10 caracteres.',
'system_prompt.max' => 'El system prompt no puede exceder los 5000 caracteres.',
'model.required' => 'Debes seleccionar un modelo de IA.',
'model.in' => 'El modelo seleccionado no es válido.',
'temperature.required' => 'La temperatura es obligatoria.',
'temperature.min' => 'La temperatura debe ser al menos 0.',
'temperature.max' => 'La temperatura no puede ser mayor a 1.',
];
}
}
2.2. StoreChatbotRequest Similar
En app/Http/Requests/StoreChatbotRequest.php:
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreChatbotRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Cualquier usuario autenticado puede crear
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'system_prompt' => 'required|string|min:10|max:5000',
'model' => 'required|string|in:gpt-3.5-turbo,gpt-4,gpt-4-turbo,gpt-4o',
'temperature' => 'required|numeric|min:0|max:1',
];
}
}
3. Mostrar Errores de Validación en Vue
3.1. Componente InputError
Crear resources/js/Components/InputError.vue:
vue
<script setup>
defineProps({
message: {
type: String,
default: ''
}
})
</script>
<template>
<p v-if="message" class="mt-1 text-sm text-red-600 dark:text-red-400">
{{ message }}
</p>
</template>
3.2. Agregar Errores al ChatbotForm
En resources/js/Components/Chatbots/ChatbotForm.vue:
vue
<script setup>
import { defineProps } from 'vue'
import InputError from '@/Components/InputError.vue'
defineProps({
form: {
type: Object,
required: true
},
isEditing: {
type: Boolean,
default: false
}
})
const models = [
{ value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' },
{ value: 'gpt-4', label: 'GPT-4' },
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo' },
{ value: 'gpt-4o', label: 'GPT-4o' },
]
</script>
<template>
<div class="grid grid-cols-6 gap-6">
<!-- Nombre -->
<div class="col-span-6 sm:col-span-4">
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Nombre
</label>
<input
id="name"
v-model="form.name"
type="text"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
placeholder="Ej: Chef Ecuador, Asistente de Ventas..."
/>
<!-- 👇 Mostrar error -->
<InputError :message="form.errors?.name" />
</div>
<!-- System Prompt -->
<div class="col-span-6 sm:col-span-4">
<label for="system_prompt" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
System Prompt
</label>
<textarea
id="system_prompt"
v-model="form.system_prompt"
rows="6"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
placeholder="Instrucciones que guiarán el comportamiento del chatbot..."
/>
<!-- 👇 Mostrar error -->
<InputError :message="form.errors?.system_prompt" />
</div>
<!-- Modelo -->
<div class="col-span-6 sm:col-span-4">
<label for="model" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Modelo de IA
</label>
<select
id="model"
v-model="form.model"
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-blue-500 focus:ring-blue-500"
>
<option
v-for="model in models"
:key="model.value"
:value="model.value"
>
{{ model.label }}
</option>
</select>
<!-- 👇 Mostrar error -->
<InputError :message="form.errors?.model" />
</div>
<!-- Temperatura -->
<div class="col-span-6 sm:col-span-4">
<label for="temperature" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Temperatura: {{ form.temperature }}
</label>
<input
id="temperature"
v-model="form.temperature"
type="range"
min="0"
max="1"
step="0.1"
class="mt-1 block w-full"
/>
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>Preciso</span>
<span>Creativo</span>
</div>
<!-- 👇 Mostrar error -->
<InputError :message="form.errors?.temperature" />
</div>
</div>
</template>
4. Reestructuración de Requests
4.1. Opción 1: Requests Separados
Mantener StoreChatbotRequest y UpdateChatbotRequest separados.
Ventajas:
✅ Reglas específicas para cada operación
✅ Mensajes personalizados por operación
✅ Más control sobre la autorización
4.2. Opción 2: Request Unificado (Recomendado)
Renombrar a SaveChatbotRequest y usar en ambos métodos:
bash
# Renombrar archivo
mv app/Http/Requests/UpdateChatbotRequest.php app/Http/Requests/SaveChatbotRequest.php
En app/Http/Requests/SaveChatbotRequest.php:
php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SaveChatbotRequest extends FormRequest
{
public function authorize(): bool
{
// Para creación: cualquier usuario autenticado
if ($this->isMethod('post')) {
return true;
}
// Para actualización: verificar propiedad
$chatbot = $this->route('chatbot');
return $this->user()->id === $chatbot->user_id;
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'system_prompt' => 'required|string|min:10|max:5000',
'model' => 'required|string|in:gpt-3.5-turbo,gpt-4,gpt-4-turbo,gpt-4o',
'temperature' => 'required|numeric|min:0|max:1',
];
}
public function messages(): array
{
return [
'name.required' => 'El nombre del chatbot es obligatorio.',
'name.max' => 'El nombre no puede exceder los 255 caracteres.',
'system_prompt.required' => 'El system prompt es obligatorio.',
'system_prompt.min' => 'El system prompt debe tener al menos 10 caracteres.',
'system_prompt.max' => 'El system prompt no puede exceder los 5000 caracteres.',
'model.required' => 'Debes seleccionar un modelo de IA.',
'model.in' => 'El modelo seleccionado no es válido.',
'temperature.required' => 'La temperatura es obligatoria.',
'temperature.min' => 'La temperatura debe ser al menos 0.',
'temperature.max' => 'La temperatura no puede ser mayor a 1.',
];
}
}
Actualizar el controlador:
php
<?php
namespace App\Http\Controllers;
use App\Models\Chatbot;
use App\Http\Requests\SaveChatbotRequest;
use Illuminate\Http\Request;
use Inertia\Inertia;
class ChatbotController extends Controller
{
public function store(SaveChatbotRequest $request)
{
$validated = $request->validated();
$chatbot = $request->user()->chatbots()->create($validated);
return redirect()->route('chatbots.show', $chatbot)
->with('success', 'Chatbot creado correctamente.');
}
public function update(SaveChatbotRequest $request, Chatbot $chatbot)
{
$validated = $request->validated();
$chatbot->update($validated);
return redirect()->route('chatbots.show', $chatbot)
->with('success', 'Chatbot actualizado correctamente.');
}
}
5. Validación en el Frontend (Mejora)
5.1. Validación con VueUse
bash
npm install @vueuse/core
En ChatbotForm.vue:
vue
<script setup>
import { computed } from 'vue'
import { useVuelidate } from '@vuelidate/core'
import { required, minLength, maxLength, helpers } from '@vuelidate/validators'
const props = defineProps({
form: {
type: Object,
required: true
}
})
const rules = computed(() => ({
name: {
required: helpers.withMessage('El nombre es obligatorio', required),
maxLength: helpers.withMessage('El nombre no puede exceder 255 caracteres', maxLength(255))
},
systemPrompt: {
required: helpers.withMessage('El system prompt es obligatorio', required),
minLength: helpers.withMessage('El system prompt debe tener al menos 10 caracteres', minLength(10)),
maxLength: helpers.withMessage('El system prompt no puede exceder 5000 caracteres', maxLength(5000))
},
model: {
required: helpers.withMessage('Debes seleccionar un modelo', required)
},
temperature: {
required: helpers.withMessage('La temperatura es obligatoria', required)
}
}))
const v$ = useVuelidate(rules, {
name: props.form.name,
systemPrompt: props.form.system_prompt,
model: props.form.model,
temperature: props.form.temperature
})
</script>
6. Resumen
6.1. Archivos Modificados
6.2. Errores Comunes y Soluciones
6.3. Validaciones Implementadas
¡La validación está completamente funcional! ✅
Comentarios
Publicar un comentario