Curso de Laravel
CRUD en Laravel: actualizar registros
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Continuando con el CRUD, hoy veremos la U de actualizar.
Es muy parecido a crear, pero con tres diferencias que conviene tener claras porque son las que hacen fallar a la gente.
¡Empecemos!
Los dos métodos
edit()responde a GET y muestra el formulario con los datos actuales.update()responde a PUT o PATCH y guarda los cambios.
El método edit
public function edit(Paciente $paciente)
{
return view('pacientes.edit', compact('paciente'));
}
Gracias a la vinculación de modelos que vimos en la lección de rutas, el paciente llega ya resuelto. Si no existe, Laravel devuelve un 404 sin que hagas nada.
Y si el formulario necesita datos adicionales:
public function edit(Cita $cita)
{
$doctores = Doctor::where('activo', true)->pluck('nombre', 'id');
return view('citas.edit', compact('cita', 'doctores'));
}
El formulario: la primera diferencia
<form method="POST" action="{{ route('pacientes.update', $paciente) }}">
@csrf
@method('PUT')
<x-campo nombre="cedula" etiqueta="Cédula" :valor="$paciente->cedula" requerido />
<x-campo nombre="nombre" etiqueta="Nombre" :valor="$paciente->nombre" requerido />
<x-campo nombre="apellido" etiqueta="Apellido" :valor="$paciente->apellido" requerido />
<x-campo nombre="telefono" etiqueta="Teléfono" :valor="$paciente->telefono" />
<button type="submit">Actualizar</button>
</form>
@method('PUT') es lo primero que conviene retener. Los navegadores solo saben enviar GET y POST, así que Laravel usa un campo oculto para simular PUT. Sin esa línea, la petición llega como POST y no encuentra la ruta: error 405.
Y fíjate en los dos puntos: :valor="$paciente->cedula" pasa el valor de la propiedad; sin ellos pasaría el texto literal.
El orden de old() y el valor actual
Dentro del componente de campo tenemos:
value="{{ old($nombre, $valor) }}"
Ese orden es importante y explica un comportamiento que confunde:
- La primera vez que se carga el formulario no hay
old(), así que muestra el valor actual del registro. - Si la validación falla,
old()tiene lo que el usuario escribió y eso es lo que se muestra.
Al revés, el usuario perdería sus correcciones cada vez que fallara la validación.
El método update
public function update(ActualizarPacienteRequest $request, Paciente $paciente)
{
$paciente->update($request->validated());
return redirect()
->route('pacientes.show', $paciente)
->with('exito', 'Datos actualizados correctamente');
}
Igual que al crear: valida, guarda y redirige.
La regla unique: la segunda diferencia
Esta es la que más problemas da.
'cedula' => ['required', 'unique:pacientes'],
Al editar, esa regla siempre falla: el propio registro ya tiene esa cédula, así que la validación la considera duplicada aunque el usuario no la haya tocado.
Hay que excluir el registro actual:
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'cedula' => [
'required',
'max:20',
Rule::unique('pacientes')->ignore($this->paciente),
],
'nombre' => ['required', 'max:100'],
'apellido' => ['required', 'max:100'],
];
}
Ese $this->paciente es el modelo que llega por la ruta.
Por eso lo habitual son dos Form Requests: GuardarPacienteRequest y ActualizarPacienteRequest. Las reglas no son idénticas.
Compartir el formulario: la tercera diferencia bien resuelta
Crear y editar comparten casi todos los campos. Repetirlos significa que cualquier cambio hay que hacerlo dos veces, y tarde o temprano se olvida uno.
La solución es un fragmento compartido:
{{-- pacientes/partials/campos.blade.php --}}
<x-campo nombre="cedula" etiqueta="Cédula"
:valor="$paciente->cedula ?? null" requerido />
<x-campo nombre="nombre" etiqueta="Nombre"
:valor="$paciente->nombre ?? null" requerido />
<x-campo nombre="apellido" etiqueta="Apellido"
:valor="$paciente->apellido ?? null" requerido />
<x-campo nombre="telefono" etiqueta="Teléfono"
:valor="$paciente->telefono ?? null" />
Ese ?? null permite que funcione también al crear, donde $paciente no existe.
Crear:
<form method="POST" action="{{ route('pacientes.store') }}">
@csrf
@include('pacientes.partials.campos')
<button type="submit">Guardar</button>
</form>
Editar:
<form method="POST" action="{{ route('pacientes.update', $paciente) }}">
@csrf
@method('PUT')
@include('pacientes.partials.campos')
<button type="submit">Actualizar</button>
</form>
Un solo archivo con los campos. Añadir un campo nuevo es tocar un archivo, no dos.
PUT o PATCH
Route::resource registra ambos para la misma acción, así que funcionan igual. La diferencia es semántica:
- PUT significa reemplazar el recurso completo.
- PATCH significa modificar solo algunos campos.
En una aplicación web con formularios completos, usa PUT. PATCH tiene más sentido en APIs donde se envían campos sueltos.
Actualizar relaciones muchos a muchos
Cuando el formulario incluye casillas de verificación:
public function update(ActualizarCitaRequest $request, Cita $cita)
{
$cita->update($request->validated());
$cita->estudios()->sync($request->estudios ?? []);
return redirect()->route('citas.show', $cita)
->with('exito', 'Cita actualizada');
}
sync() y no attach(). Como vimos en la lección de relaciones: sync() deja exactamente los que le pasas, añadiendo los nuevos y quitando los que se desmarcaron. attach() solo añade, y al editar dos veces terminarías con duplicados.
Y el ?? [] es necesario: si el usuario desmarca todas las casillas, el campo no llega en la petición, y sin ese valor por defecto obtendrías un error.
Saber qué cambió
A veces hay que reaccionar solo cuando un campo concreto cambió:
public function update(ActualizarCitaRequest $request, Cita $cita)
{
$estadoAnterior = $cita->estado;
$cita->update($request->validated());
if ($cita->wasChanged('estado')) {
// registrar en auditoría, notificar al paciente...
logger("Cita {$cita->id}: {$estadoAnterior} → {$cita->estado}");
}
return redirect()->route('citas.show', $cita)->with('exito', 'Cita actualizada');
}
Los métodos disponibles:
$cita->isDirty(); // ¿tiene cambios sin guardar?
$cita->isDirty('estado');
$cita->wasChanged(); // ¿cambió al guardar?
$cita->getOriginal('estado'); // el valor anterior
$cita->getChanges(); // qué cambió exactamente
Actualizaciones parciales
Para acciones puntuales que no requieren el formulario completo, conviene una ruta propia:
Route::patch('/citas/{cita}/atender', [CitaController::class, 'atender'])
->name('citas.atender');
public function atender(Cita $cita)
{
$cita->update([
'estado' => 'atendida',
'atendida_en' => now(),
]);
return back()->with('exito', 'Cita marcada como atendida');
}
Es más claro que forzar esa acción por el update() genérico. Y back() devuelve al usuario a donde estaba, que en un listado es justo lo que espera.
Edición concurrente
Un problema real en sistemas con varios usuarios: dos personas abren el mismo registro, y quien guarda último pisa los cambios del otro sin que nadie se entere.
La solución habitual es comparar la marca de tiempo:
<input type="hidden" name="updated_at" value="{{ $paciente->updated_at }}">
if ($request->updated_at != $paciente->updated_at) {
return back()
->withInput()
->with('error', 'Otro usuario modificó este registro. Revisa los cambios antes de guardar.');
}
No hace falta en todos los proyectos, pero en sistemas donde varias personas editan lo mismo evita pérdidas de datos silenciosas.
Errores comunes
- Olvidar
@method('PUT')y recibir un error 405. uniquesinignore(), y no poder guardar sin cambiar la cédula.old()sin valor por defecto, dejando el formulario vacío al editar.attach()en lugar desync(), duplicando relaciones.- Repetir el formulario en crear y editar.
- Olvidar
?? []en elsync()cuando se desmarcan todas las opciones.
Para cerrar
Actualizar es crear con tres diferencias: el método PUT, la regla unique con ignore() y sync() para las relaciones. Lo demás es idéntico, y por eso conviene compartir el formulario.
En la siguiente lección cerramos el CRUD con el detalle y la eliminación.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp