Skip to main content

Overview

The ClinicaController handles all clinic management operations for the DentControl SaaS platform. This controller is exclusively for superadmin use and provides functionality to create, read, update, and manage the status of dental clinics. It includes comprehensive validation, logo upload handling, and status toggle capabilities. Location: app/Http/Controllers/Admin/ClinicaController.php Namespace: App\Http\Controllers\Admin

Methods

index()

Displays a list of all clinics ordered by most recent first. Route: GET /clinicas Route Name: clinicas.index Middleware:
  • auth - User must be authenticated
  • can:admin-only - User must have superadmin role
Response: Returns the admin.clinicas.index Blade view with:
clinicas
Collection
Collection of all clinics ordered by created_at DESC
Example:
public function index()
{
    // Show most recent clinics first
    $clinicas = Clinica::orderBy('created_at', 'desc')->get();
    return view('admin.clinicas.index', compact('clinicas'));
}

store()

Creates a new clinic with comprehensive validation and optional logo upload. Route: POST /clinicas Route Name: clinicas.store Middleware:
  • auth
  • can:admin-only
Parameters:
nombre
string
required
Clinic name (max 50 chars, letters, numbers, spaces, &, ’, -)
rfc
string
required
Mexican RFC tax ID (12-13 chars, uppercase, unique)Format: ABCD123456EF7 (3-4 letters + 6 digits + 3 alphanumeric)
calle
string
Street name (max 255 chars)
numero_ext
string
External street number (max 10 chars)
numero_int
string
Internal/apartment number (max 10 chars)
colonia
string
Neighborhood/colony (max 255 chars)
codigo_postal
string
required
5-digit postal code
ciudad
string
required
City name
estado
string
required
State name
telefono
string
required
10-digit phone number (numeric, unique)
logo_ruta
file
Clinic logo image (jpeg, png, jpg, max 2MB)
Validation Rules:
[
    'nombre' => 'required|string|max:50|regex:/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚ\s&\'\-]+$/u',
    'rfc' => 'required|string|uppercase|regex:/^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$/|min:12|max:13|unique:clinica,rfc',
    'calle' => 'nullable|string|max:255',
    'numero_ext' => 'nullable|string|max:10',
    'numero_int' => 'nullable|string|max:10',
    'colonia' => 'nullable|string|max:255',
    'codigo_postal' => 'required|digits:5',
    'ciudad' => 'required|string',
    'estado' => 'required|string',
    'telefono' => 'required|numeric|digits:10|unique:clinica,telefono',
    'logo_ruta' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
]
Custom Error Messages:
  • nombre.max: “El nombre no debe exceder los 50 caracteres.”
  • nombre.regex: “El nombre solo permite letras, números y espacios.”
  • rfc.unique: “Este RFC ya está registrado.”
  • rfc.min: “El RFC debe tener al menos 12 caracteres.”
  • telefono.digits: “El teléfono debe ser de 10 dígitos.”
  • codigo_postal.digits: “El código postal debe ser de 5 dígitos.”
  • rfc.regex: “El formato del RFC es inválido (Ej: ABCD123456EF7).”
Response: Redirects to clinicas.index with success message: “Clínica registrada correctamente.” Logo Upload:
  • Stored in: public/images/logos/
  • Filename format: logo_{timestamp}.{extension}
  • Database stores: images/logos/{filename}
Example:
public function store(Request $request)
{
    $validated = $request->validate([
        'nombre' => 'required|string|max:50|regex:/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚ\s&\'\-]+$/u',
        'rfc' => 'required|string|uppercase|regex:/^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$/|min:12|max:13|unique:clinica,rfc',
        // ... other validation rules
    ]);

    if ($request->hasFile('logo_ruta')) {
        $image = $request->file('logo_ruta');
        $name = 'logo_' . time() . '.' . $image->getClientOriginalExtension();
        $image->move(public_path('images/logos'), $name);
        $validated['logo_ruta'] = 'images/logos/' . $name;
    }

    Clinica::create($validated);

    return redirect()->route('clinicas.index')->with('success', 'Clínica registrada correctamente.');
}

edit()

Retrieves a specific clinic’s data for editing (AJAX endpoint). Route: GET /clinicas/{id}/edit Route Name: clinicas.edit Middleware:
  • auth
  • can:admin-only
Parameters:
id
integer
required
Clinic ID (id_clinica)
Response: Returns JSON representation of the clinic:
{
    "id_clinica": 1,
    "nombre": "Clínica Dental Ejemplo",
    "rfc": "CDE210101ABC",
    "calle": "Av. Principal",
    "numero_ext": "123",
    "numero_int": "A",
    "colonia": "Centro",
    "codigo_postal": "01000",
    "ciudad": "Ciudad de México",
    "estado": "CDMX",
    "telefono": "5512345678",
    "logo_ruta": "images/logos/logo_1234567890.jpg",
    "estatus": "activo",
    "created_at": "2026-03-01T10:30:00.000000Z",
    "updated_at": "2026-03-01T10:30:00.000000Z"
}
Example:
public function edit($id) 
{
    $clinica = Clinica::findOrFail($id);
    return response()->json($clinica);
}
Error Response:
  • 404 Not Found if clinic doesn’t exist

update()

Updates an existing clinic with validation and optional logo replacement. Route: PUT /clinicas/{id} Route Name: clinicas.update Middleware:
  • auth
  • can:admin-only
Parameters:
id
integer
required
Clinic ID (id_clinica)
nombre
string
required
Clinic name (max 50 chars)
rfc
string
required
RFC tax ID (unique, excluding current clinic)
calle
string
Street name
numero_ext
string
External number
numero_int
string
Internal number
colonia
string
Neighborhood
codigo_postal
string
required
5-digit postal code
ciudad
string
required
City name
estado
string
State name
telefono
string
required
10-digit phone (unique, excluding current clinic)
logo_ruta
file
New clinic logo (replaces existing)
Validation: Similar to store(), but with unique exceptions:
'rfc' => 'required|string|min:12|max:13|uppercase|regex:/^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$/|unique:clinica,rfc,' . $id . ',id_clinica',
'telefono' => 'required|numeric|digits:10|unique:clinica,telefono,' . $id . ',id_clinica',
Custom Error Messages:
  • rfc.unique: “Este RFC ya pertenece a otra clínica registrada.”
  • telefono.unique: “Este número telefónico ya está asociado a otra clínica.”
Logo Handling:
  • If new logo uploaded:
    • Deletes old logo file from disk (if exists)
    • Uploads new logo with timestamp filename
    • Updates logo_ruta field
  • If no new logo:
    • Keeps existing logo path
Response: Success: Redirects to clinicas.index with message: “Clínica actualizada correctamente.” Validation Failure: Redirects back with:
  • Error messages
  • Input data preserved
  • editing_clinic_id session variable (for modal reopening)
Example:
public function update(Request $request, $id)
{
    $clinica = Clinica::findOrFail($id);

    $validator = Validator::make($request->all(), [
        'nombre' => 'required|string|max:50|regex:/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚ\s&\'\-]+$/u',
        'rfc' => 'required|string|min:12|max:13|uppercase|regex:/^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$/|unique:clinica,rfc,' . $id . ',id_clinica',
        // ... other rules
    ]);

    if ($validator->fails()) {
        return redirect()->back()
            ->withErrors($validator)
            ->withInput()
            ->with('editing_clinic_id', $id);
    }

    $validated = $validator->validated();

    // Handle logo replacement
    if ($request->hasFile('logo_ruta')) {
        if ($clinica->logo_ruta && File::exists(public_path($clinica->logo_ruta))) {
            File::delete(public_path($clinica->logo_ruta));
        }

        $image = $request->file('logo_ruta');
        $name = 'logo_' . time() . '.' . $image->getClientOriginalExtension();
        $image->move(public_path('images/logos'), $name);
        $validated['logo_ruta'] = 'images/logos/' . $name;
    }

    $clinica->update($validated);

    return redirect()->route('clinicas.index')->with('success', 'Clínica actualizada correctamente.');
}

toggleStatus()

Toggles a clinic’s status between active and inactive. Route: PATCH /clinicas/{id}/toggle Route Name: clinicas.toggle Middleware:
  • auth
  • can:admin-only
Parameters:
id
integer
required
Clinic ID (id_clinica)
Status Logic:
  • If estatus = 'activo' → changes to 'baja'
  • If estatus = 'baja' → changes to 'activo'
Response: Redirects back with success message:
  • 'activo': “Clínica reactivada con éxito.”
  • 'baja': “Clínica dada de baja.”
Example:
public function toggleStatus($id)
{
    $clinica = Clinica::findOrFail($id);
    
    // Toggle status
    $nuevoEstado = ($clinica->estatus == 'activo') ? 'baja' : 'activo';
    
    $clinica->update(['estatus' => $nuevoEstado]);

    $mensaje = ($nuevoEstado == 'activo') 
        ? 'Clínica reactivada con éxito.' 
        : 'Clínica dada de baja.';
    
    return redirect()->back()->with('success', $mensaje);
}
Impact:
  • When a clinic is set to 'baja', associated users cannot log in
  • The AuthController checks clinic status during login

Summary

The ClinicaController provides 5 methods for complete clinic management:
  1. index() - List all clinics (ordered by newest)
  2. store() - Create new clinic with logo upload
  3. edit() - Get clinic data (JSON for AJAX)
  4. update() - Update clinic with logo replacement
  5. toggleStatus() - Activate/deactivate clinic
Key Features:
  • Comprehensive RFC and phone validation
  • Logo upload and management
  • Duplicate prevention (RFC, phone)
  • Status toggling with user login impact
  • Custom error messages in Spanish
  • Modal-friendly edit/validation flow
  • File cleanup on logo replacement
Route Summary:
GET    /clinicas              → index()
POST   /clinicas              → store()
GET    /clinicas/{id}/edit    → edit()
PUT    /clinicas/{id}         → update()
PATCH  /clinicas/{id}/toggle  → toggleStatus()

Build docs developers (and LLMs) love