3. Manejo de errores
Temas del programa: 3.1.3
Errores y situaciones no esperadas
No todo error es igual
Section titled “No todo error es igual”| Situación | Ejemplo | Quién la detecta | Respuesta |
|---|---|---|---|
| Datos inválidos | Falta fecha_fin | Form Request | 422 |
| Regla de negocio | Equipo ya reservado | Service | 409 |
| Sin identidad | Token vencido | auth:sanctum | 401 |
| Sin permiso | Préstamo ajeno | Policy | 403 |
| No existe | Préstamo 999 | Laravel | 404 |
| No esperada | PostgreSQL no responde | Nadie la planeó | 500 genérico |
Las cinco primeras son esperadas se diseñan · la última se controla
Esperadas e inesperadas
Section titled “Esperadas e inesperadas”Parte del dominio · tiene nombre propio · el usuario puede corregirla · se registra como info
Falla técnica · el usuario no puede hacer nada · se registra como error con todo el detalle
Paso 1 — dale nombre al problema
Section titled “Paso 1 — dale nombre al problema”class ConflictoDeReservaException extends RuntimeException{ public function __construct(public readonly ?int $equipoId = null) { parent::__construct('El equipo ya está reservado en ese horario.'); }}Una excepción de dominio dice qué pasó y lleva los datos para diagnosticarlo
Paso 2 — lanzar donde se detecta
Section titled “Paso 2 — lanzar donde se detecta”// PrestamoServicepublic function crear(array $datos): Prestamo{ if ($this->repositorio->existeConflicto($datos)) { throw new ConflictoDeReservaException((int) $datos['equipo_id']); }
return $this->repositorio->crear($datos);}El Service no sabe si lo llamó la web o la API · no devuelve JSON ni códigos HTTP
Paso 3 — traducir en un solo lugar
Section titled “Paso 3 — traducir en un solo lugar”$exceptions->render(function (ConflictoDeReservaException $e, Request $request) use ($error) { if ($request->is('api/*')) { Log::info('Reserva rechazada por conflicto de horario', [ 'equipo_id' => $e->equipoId, 'user_id' => $request->user()?->id, ]);
return $error('CONFLICTO_RESERVA', $e->getMessage(), [], 409); }});STD-BE-E02 · un render por excepción conocida · lo demás lo maneja Laravel
Un formato de error para toda la API
Section titled “Un formato de error para toda la API”{ "error": { "code": "CONFLICTO_RESERVA", "message": "El equipo ya está reservado en ese horario.", "details": {} }}Lo compara un programa
Lo lee una persona
Qué campo falló
Paso 4 — atrapar solo si puedes hacer algo
Section titled “Paso 4 — atrapar solo si puedes hacer algo”Revertir lo que quedó a medias
Convertir un error técnico en uno de dominio
Un servicio externo que falló una vez
catch (\Exception $e) { } // BLOCKER — STD-BE-E03 el error desapareceSi no puedes hacer ninguna de las tres no lo atrapes deja que suba
Deshacer — todo o nada
Section titled “Deshacer — todo o nada”DB::transaction(function () use ($datos) { $prestamo = Prestamo::create($datos); $prestamo->equipo->update(['disponible' => false]);});Si falla la segunda línea queda un préstamo con el equipo marcado disponible
Si algo lanza una excepción PostgreSQL deshace las dos escrituras
Ejemplo ilustrativo · ReservaFIC no actualiza disponible al prestar
Traducir — error técnico a error de dominio
Section titled “Traducir — error técnico a error de dominio”try { $prestamo = $this->repositorio->crear($datos);} catch (UniqueConstraintViolationException $e) { throw new ConflictoDeReservaException((int) $datos['equipo_id']);}Si el motor rechaza un duplicado el usuario recibe el mismo 409 de siempre y no un 500
Ejemplo ilustrativo · solo aplica si existe una restricción única que lo provoque
Lo que ve cada quien
Section titled “Lo que ve cada quien”Código de estado · code · un mensaje genérico en el 500
Clase de la excepción · archivo · línea · IDs del caso
[2026-11-10 10:03:12] local.ERROR: SQLSTATE[08006] connection to server failed {"userId":7,"exception":"[object] (Illuminate\\Database\\QueryException ...)"}Registro ilustrativo
Registrar con contexto y sin secretos
Section titled “Registrar con contexto y sin secretos”Log::info('Reserva rechazada por conflicto de horario', [ 'equipo_id' => $e->equipoId, 'user_id' => $request->user()?->id,]);Identificadores que permiten reconstruir el caso
Contraseñas tokens o el cuerpo completo de la petición
| Nivel | Cuándo |
|---|---|
| info | Evento normal del negocio |
| warning | Algo raro que no impidió la operación |
| error | La operación falló |
Relación con el laboratorio
Section titled “Relación con el laboratorio”Este tema se practica en el Laboratorio 2 — API REST del proyecto.
Qué sigue
Section titled “Qué sigue”Continúa con 4. Pruebas automatizadas.

