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

Archivo

Cambio

app/Http/Requests/UpdateChatbotRequest.php

✅ Autorización y reglas

app/Http/Requests/StoreChatbotRequest.php

✅ Reglas de validación

app/Http/Requests/SaveChatbotRequest.php

✅ Request unificado (opcional)

resources/js/Components/InputError.vue

✅ Creado

resources/js/Components/Chatbots/ChatbotForm.vue

✅ Errores de validación

6.2. Errores Comunes y Soluciones

Error

Causa

Solución

403 Forbidden

authorize() retorna false

Cambiar a true o verificar permisos

422 Unprocessable

Validación falla

Mostrar errores en el frontend

Sin errores visibles

No se muestran form.errors

Agregar InputError en cada campo

6.3. Validaciones Implementadas

Campo

Reglas

name

Requerido, string, máx 255

system_prompt

Requerido, string, min 10, máx 5000

model

Requerido, in: [gpt-3.5-turbo, gpt-4, ...]

temperature

Requerido, numeric, min 0, max 1

¡La validación está completamente funcional! ✅


Comentarios

Entradas más populares de este blog

1. Introducción al curso de Chatbots personalizados con Laravel & OpenAI

2. Prerequisitos para el Curso de Chatbots con Laravel y OpenAI

4. Herramientas de Calidad