MS CRM | Automata
sections (9)
$ cd .. // back to projects
automata@latam: ~/projects/ms_crm
USE CASE · slug: ms_crm ·2026 · 2 min read · 494 words

> MS CRM

// Bespoke bilingual CRM: contacts, organizations, interaction timeline, dynamic and static audience segments, all on Laravel + Livewire.

Laravel 12Livewire 3Flux UIMySQL 8FortifyTailwind 4
forks last commit
MS CRM
▸ role
Full-stack · Laravel · Livewire
▸ team
Solo
▸ status
online
// section 01 · discovery

$ cat ./discovery.md

▸ overview

A small-team CRM built around three concepts: an Organization (companies, NGOs, government bodies, communities), the Contacts inside it, and the Interactions logged against either. On top of that sit Audience Segments (static lists or dynamic queries) and Campaigns. The UI is server-rendered with Livewire 3 + Flux, runs in Spanish or English, and supports 2FA via Laravel Fortify. All records have soft deletes and created_by/updated_by for audit.

▸ problem

Off-the-shelf CRM tools either lock the org into a per-seat SaaS price (Hubspot, Pipedrive) or are too generic to model the contact ↔ organization ↔ interaction triad the team actually uses. The bespoke tradeoff is paid back by a UI that fits the workflow and bilingual support out of the box.

▸ audience

Non-profits, government bodies, NGOs and community-driven companies that track relationships across organizations rather than individual leads.

// section 03 · architecture

$ cat ./architecture.md

// section 04 · infrastructure

$ cat ./infrastructure.md

▸ services
provider: Laravel + MySQL + Vite
  • Laravel 12 app (Livewire components + Flux UI)
  • MySQL 8 (organizations, contacts, interactions, segments, campaigns, activities)
  • Laravel Fortify (auth + 2FA)
  • Vite dev server (HMR)
  • Laravel Pail (real-time logs)
  • Spanish + English locales (Spanish default)
// section 05 · implementation

$ cat ./implementation.md

▸ frontend
  • · Livewire 3 + Volt 1.7
  • · Flux UI
  • · Tailwind CSS 4
  • · Alpine.js
  • · Vite
▸ backend
  • · Laravel 12
  • · PHP 8.2
  • · Laravel Fortify 1.30
▸ data
  • · MySQL
  • · Eloquent + soft deletes
  • · JSON columns for tags + custom_fields
▸ devops
  • · Local dev with concurrently (artisan + queue + pail + vite)
// section 06 · technical challenges

$ cat ./challenges/*.md

// 1 technical problems solved

01 / 01
challenge-01.md · modeling · audit · soft-deletes
▸ problem

Logging interactions against either a Contact or an Organization without forcing a join through one or the other.

constraint: Some interactions are with a specific person (a call to a contact); some are with the org as a whole (a meeting with no named attendee). Both need to show up in the same timeline, ordered by date.

▸ approach

Interaction has nullable contact_id AND organization_id, plus type (email/call/meeting/note/event). The timeline query is one Eloquent scope that joins both as left-joins and projects a unified row. created_by + updated_by are filled by an auth-aware observer so the audit trail does not need extra writes.

app/Models/Contact.php php
class Contact extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'first_name', 'last_name', 'email', 'phone',
        'position', 'organization_id',
        'tags', 'custom_fields',
        'status', 'source', 'created_by',
    ];

    protected $casts = [
        'tags'          => 'array',
        'custom_fields' => 'array',
        'created_at'    => 'datetime',
        'updated_at'    => 'datetime',
        'deleted_at'    => 'datetime',
    ];
}
// section 07 · testing & ci

$ cat ./testing.md

▸ strategy

Pest 4 over SQLite in-memory. Unit tests on model relations; feature tests on Livewire component flows (create contact → assign organization → log interaction).

▸ tools
Pest 4.3PHPUnitPest Laravel plugin
// section 09 · results

$ cat ./results.md

01 /
7
Eloquent models (Contact, Organization, Interaction, Activity, Campaign, Segment, User)
02 /
10
Livewire components (dashboard + CRUD for contacts and orgs)
03 /
10
migrations
04 /
2
seeders (5 orgs / 8 contacts / 10 interactions in CRMSeeder)
▸ outcomes

Production-ready. Core CRUD, dashboard with stats, bilingual UI and 2FA are live. Roadmap: REST API, PDF/Excel exports, email integration and a roles/permissions layer.

// section 10 · lessons learned

$ cat ./lessons.md

// if I did it again

  • 01 /

    Modeling beats UI in a CRM

    The instinct was to start from the contact list screen. In practice, the win came from getting the Organization/Contact/Interaction triad right first — once those FKs were nullable in the right places, every screen became a thin query on top.

// related

$ grep -l "tech" ./projects/*

// projects that share part of the stack

// next step

$ automata deploy --your-operation

// Let's talk about adapting this to your case.

./let-s-talk.sh