Skip to main content

Overview

BeanQuick implements a secure three-stage business registration process that ensures only legitimate businesses can join the platform. The workflow involves initial signup, admin review, and email-based account activation.

Registration Workflow

Stage 1: Business Signup

Businesses submit a registration request through the public signup form. Endpoint: POST /api/solicitud-empresa Required Fields:
FieldTypeValidationDescription
nombrestringrequired, max:255Business name
correostringrequired, email, uniqueBusiness email (must be unique)
nitstringnullable, max:50Tax identification number
telefonostringnullable, max:50Contact phone number
direccionstringnullable, max:255Physical address
descripciontextnullableBusiness description
logofilenullable, image, max:2MBBusiness logo (jpeg, png, jpg, webp)
foto_localfilenullable, image, max:4MBStore photo (jpeg, png, jpg, webp)
Example Request:
const formData = new FormData();
formData.append('nombre', 'Café del Centro');
formData.append('correo', '[email protected]');
formData.append('nit', '900123456-7');
formData.append('telefono', '+57 312 456 7890');
formData.append('direccion', 'Calle 10 #5-20, Bogotá');
formData.append('descripcion', 'Cafetería especializada en café colombiano');
formData.append('logo', logoFile);
formData.append('foto_local', storePhotoFile);

const response = await fetch('/api/solicitud-empresa', {
  method: 'POST',
  body: formData
});
Success Response:
{
  "status": "success",
  "message": "Tu solicitud fue enviada correctamente. Nuestro equipo la revisará pronto.",
  "solicitud_id": 15
}
Image Storage: Uploaded images are stored temporarily in storage/app/public/solicitudes/ until the business is approved.
  • Logos: solicitudes/logos/
  • Store photos: solicitudes/locales/

Stage 2: Admin Review

Administrators review pending applications from the admin dashboard.

View Pending Applications

Endpoint: GET /api/admin/dashboard
{
  "solicitudes": [
    {
      "id": 15,
      "nombre": "Café del Centro",
      "correo": "[email protected]",
      "nit": "900123456-7",
      "telefono": "+57 312 456 7890",
      "direccion": "Calle 10 #5-20, Bogotá",
      "descripcion": "Cafetería especializada en café colombiano",
      "estado": "pendiente",
      "logo_url": "http://localhost:8000/storage/solicitudes/logos/abc123.jpg",
      "foto_local_url": "http://localhost:8000/storage/solicitudes/locales/def456.jpg"
    }
  ]
}

Approve Application

Endpoint: POST /api/admin/solicitudes/{id}/aprobar When approved, the system:
  1. Generates a secure 60-character activation token
  2. Updates the application status to 'aprobado'
  3. Sends an activation email with a unique link
Activation Link Format:
http://localhost:5173/empresa/activar/{token}
Email Template: ActivacionEmpresaMail Success Response:
{
  "message": "Solicitud aprobada y correo de activación enviado.",
  "solicitud": {
    "id": 15,
    "estado": "aprobado",
    "token": "randomstring60characters..."
  }
}

Reject Application

Endpoint: POST /api/admin/solicitudes/{id}/rechazar
{
  "message": "Solicitud rechazada.",
  "solicitud": {
    "id": 15,
    "estado": "rechazado"
  }
}

Stage 3: Account Activation

The business owner receives the activation email and completes account setup.

Validate Activation Token

Endpoint: GET /api/empresa/activar/{token}
{
  "status": "success",
  "solicitud": {
    "nombre": "Café del Centro",
    "correo": "[email protected]",
    "nit": "900123456-7",
    "logo_url": "..."
  }
}

Complete Activation

Endpoint: POST /api/empresa/activar/{token} Required Fields:
FieldValidationDescription
passwordrequired, confirmed, min:8Account password
password_confirmationrequired, same as passwordPassword confirmation
Process Flow:
1

Create User Account

Creates a new user record with role 'empresa'
User::create([
  'name' => $solicitud->nombre,
  'email' => $solicitud->correo,
  'password' => Hash::make($request->password),
  'rol' => 'empresa'
]);
2

Move Images to Permanent Storage

  • Logo: solicitudes/logos/empresas/logos/
  • Store photo: solicitudes/locales/empresas/locales/
3

Create Business Profile

Creates a record in the empresas table linked to the user
Empresa::create([
  'user_id' => $user->id,
  'nombre' => $solicitud->nombre,
  'nit' => $solicitud->nit,
  'direccion' => $solicitud->direccion,
  'telefono' => $solicitud->telefono,
  'descripcion' => $solicitud->descripcion,
  'logo' => 'empresas/logos/filename.jpg',
  'foto_local' => 'empresas/locales/filename.jpg',
  'is_open' => true
]);
4

Finalize Application

  • Updates application status to 'completada'
  • Nullifies the activation token
  • Deletes temporary image files
Success Response:
{
  "status": "success",
  "message": "Cuenta creada exitosamente. Ya puedes iniciar sesión.",
  "user": {
    "id": 42,
    "name": "Café del Centro",
    "email": "[email protected]",
    "rol": "empresa"
  }
}

Database Models

SolicitudEmpresa Model

Table: solicitudes_empresas Fillable Fields:
'nombre', 'correo', 'nit', 'telefono', 'direccion', 
'descripcion', 'logo', 'foto_local', 'estado', 'token'
Estado Values:
  • 'pendiente' - Awaiting admin review (default)
  • 'aprobado' - Approved, activation email sent
  • 'rechazado' - Rejected by admin
  • 'completada' - Account activated successfully
Appended Attributes:
  • logo_url - Full URL to logo image
  • foto_local_url - Full URL to store photo

Empresa Model

Table: empresas Fillable Fields:
'user_id', 'nombre', 'nit', 'direccion', 'telefono', 
'descripcion', 'logo', 'foto_local', 'is_open'
Relationships:
  • usuario() - belongsTo User
  • productos() - hasMany Producto
  • pedidos() - hasMany Pedido
Appended Attributes:
  • logo_url - Full URL to logo
  • foto_local_url - Full URL to store photo

Security Features

  • 60-character random string generated using Str::random(60)
  • Stored in database, validated on activation
  • Single-use token (nullified after activation)
  • Only valid for approved applications
  • Email must be unique in solicitudes_empresas table
  • Prevents duplicate applications
  • Additional check during activation to prevent race conditions
  • Account creation wrapped in database transaction
  • Rollback on any error during:
    • User creation
    • Image file operations
    • Business profile creation
  • Ensures data consistency
  • Validates file types (jpeg, png, jpg, webp)
  • Logo max size: 2MB
  • Store photo max size: 4MB
  • Stored in public disk with organized folder structure

Error Handling

Common Errors

ErrorHTTP CodeReason
”El enlace de activación no es válido o ya fue usado”404Invalid token or already activated
”Ya existe una cuenta con este correo”422Email already registered
”Laravel no detecta el archivo logo”400Missing logo file in request
”Solicitud aprobada pero hubo un error al enviar el correo”500Email sending failed (approval successful)

User Journey

Best Practices

Form Validation

Always validate file uploads on the frontend before submission to improve UX and reduce server load.

Token Security

Never expose activation tokens in logs or error messages. Use secure HTTPS in production.

Email Delivery

Implement retry logic for failed email deliveries. Log all email sending attempts for debugging.

Image Optimization

Compress and resize images on the frontend before upload to reduce storage costs and improve load times.

Implementation Example

Frontend Registration Form (React)

import { useState } from 'react';

function BusinessSignupForm() {
  const [formData, setFormData] = useState({
    nombre: '',
    correo: '',
    nit: '',
    telefono: '',
    direccion: '',
    descripcion: '',
    logo: null,
    foto_local: null
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const data = new FormData();
    Object.keys(formData).forEach(key => {
      if (formData[key]) data.append(key, formData[key]);
    });

    try {
      const response = await fetch('/api/solicitud-empresa', {
        method: 'POST',
        body: data
      });
      
      const result = await response.json();
      
      if (response.ok) {
        alert('¡Solicitud enviada! Recibirás un correo cuando sea aprobada.');
      } else {
        alert(result.message || 'Error al enviar solicitud');
      }
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Nombre del negocio"
        value={formData.nombre}
        onChange={(e) => setFormData({...formData, nombre: e.target.value})}
        required
      />
      <input
        type="email"
        placeholder="Correo electrónico"
        value={formData.correo}
        onChange={(e) => setFormData({...formData, correo: e.target.value})}
        required
      />
      {/* Add other fields similarly */}
      <input
        type="file"
        accept="image/jpeg,image/png,image/jpg,image/webp"
        onChange={(e) => setFormData({...formData, logo: e.target.files[0]})}
      />
      <button type="submit">Enviar Solicitud</button>
    </form>
  );
}

Product Management

Learn how businesses manage their product catalog after registration

Order Management

Understand how businesses receive and process customer orders

Build docs developers (and LLMs) love