Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Chilean Bundle Laravel Package

freshwork/chilean-bundle

Laravel package providing Chilean-specific helpers: RUT/RUN validation and formatting, regions and communes data, and localization utilities for building apps tailored to the Chilean market.

View on GitHub
Deep Wiki
Context7

Getting Started

Check your floor before require. v3 is PHP 8.2+ and Laravel 10+ (tested against 11 and 12). If you're on PHP 7.x or Laravel 9, you stay on the 2.x line:

composer require freshwork/chilean-bundle:^2.2   # legacy, different API
composer require freshwork/chilean-bundle        # 3.x

Do not mix 3.x docs/examples into a 2.x codebase — format() semantics, the enum data, and Iva/Clp/Phone are all new. Publish the config to see the storage/format knobs the new features read from:

php artisan vendor:publish --tag=chilean-bundle

Verify the wiring first in php artisan tinker, and use Rut::check() for the smoke test because it never throws:

use Freshwork\ChileanBundle\Rut;

Rut::check('12.345.678-9');              // true  — never throws
Rut::check('12.345.678-0');              // false
(new Rut('12.345.678-9'))->format();     // "12.345.678-9"
(new Rut('12.345.678-9'))->clean();      // "123456789"
Rut::random();                           // a freshly generated, valid RUT

If check() works but format() throws on your own data, that's the v3 behavior change, not a broken install — see Gotchas.

First real use case: RUT on a signup/profile form. The shortest path is still the string rule, which now ships en/es translations, so you may not need custom messages at all:

$request->validate([
    'rut'   => ['required', 'cl_rut'],
    'phone' => ['nullable', 'cl_phone'],
]);

Then let the new Eloquent cast own storage so you never hand-normalize:

protected function casts(): array
{
    return ['rut' => RutCast::class]; // confirm storage-format option in the cast/config
}

Where to look next, in order:

  1. Rut — now includes check(), random(), quiet(), and the RutFormat enum. Read these before writing helpers.
  2. RutCast + the config file — the cast is the single place that decides what form lands in the DB.
  3. Region / Comuna enums — these replace the old facade-bound region data.
  4. The service provider — confirms the exact registered keys (cl_rut, cl_phone) and the Rules\Rut object binding.
  5. Iva / Clp / Phone — secondary, but Clp::format() is a quick presentation win.

Implementation Patterns

Normalize → validate → store clean, display formatted — now split across the cast and the request. The cast handles persistence; the FormRequest only needs to make the rule and the unique check agree on a canonical form. Since Rut throws on garbage in v3, guard with the never-throwing static:

class StoreCustomerRequest extends FormRequest
{
    protected function prepareForValidation(): void
    {
        if ($this->filled('rut') && Rut::check($this->input('rut'))) {
            $this->merge(['rut' => (new Rut($this->input('rut')))->clean()]);
        }
        // if check() is false, leave the raw value so 'cl_rut' produces the error
    }

    public function rules(): array
    {
        return ['rut' => ['required', 'cl_rut', 'unique:customers,rut']];
    }
}

The key rule: the cast's storage format and your normalization must match, or unique compares clean input against formatted rows and silently passes duplicates. Pick one canonical form (clean, no dots) and enforce it in both places.

Reformat only at the presentation boundary, and be defensive because rows written before v3 may not be valid:

public function getRutFormattedAttribute(): ?string
{
    return Rut::check($this->rut) ? (new Rut($this->rut))->format() : null;
}

Use the shipped Rules\Rut object instead of hand-rolling. v3 gives you a proper rule class; there is no reason to write your own unless you need domain-specific logic:

use Freshwork\ChileanBundle\Rules\Rut as RutRule;

return ['rut' => ['required', new RutRule(), 'unique:customers,rut']];

String rules are fine for 90% of forms; the object earns its keep when you reuse validation outside HTTP (console commands, importers) or want to configure storage/format per call site. Keep any custom messages in lang/ — the package already ships en/es, so you're usually overriding, not authoring.

Side doors: imports, API payloads, queued jobs. The cast covers anything that flows through Eloquent, which is most of it. Anything that bypasses the model (raw inserts, DB::table() upserts, third-party feeds) still needs an explicit Rut::check() in the DTO/importer, plus a booted() hook if you want a belt-and-braces guard:

protected static function booted(): void
{
    static::saving(function ($model) {
        if ($model->isDirty('rut') && ! Rut::check($model->rut)) {
            throw new InvalidArgumentException('Invalid RUT on save');
        }
    });
}

Factories and seeders — delete your mod-11 helper. This is the biggest day-to-day win in v3. Rut::random() replaces the compute-the-DV-by-hand block entirely:

Customer::factory()->create(['rut' => (string) Rut::random()]);

Cast to string if you don't want the Rut object flowing into the factory attribute array. Keep the hand-rolled helper only if you need deterministic/known-value fixtures.

Regions and comunas for selects. The facade's static region array is superseded by an enum — do the binding in a view composer or a small support class, not in Blade:

View::composer('customers.form', function ($view) {
    $view->with([
        'regions' => Region::options(),                       // label => value for a select
        'comunas' => Comuna::inRegion($selectedRegionCode),   // filter by region
    ]);
});

Comuna is backed by the official CUT code — store the code, render officialName(), and use fromName()/inRegion() when migrating legacy free-text region strings. This is a genuine upgrade over the old array shape, but it is a shape change: any Blade/JS expecting the old keys needs updating.

IVA and CLP. Iva::of(), add(), net(), fromGross() all round to the peso, and Clp::format()/Clp::parse() give you the $1.234.567 presentation for free. Use them for display and for single-figure quotes; for invoice line items keep the arithmetic in integer pesos (or cents, if your ledger needs them) and round explicitly. A helper that rounds to the peso will not reconcile a document where net + IVA must equal total exactly across many lines. Likewise, don't feed user input through Clp::parse() and store a float — parse to int at the boundary.

Phone: same pattern as RUT. The cl_phone rule and Phone class give you mobile/landline detection and E.164 normalization. Store E.164, display human-readable, and normalize in prepareForValidation — exactly the RUT workflow.

Integration tip: mask in the UI, validate on the server. text input with inputmode="numeric", a JS mask that inserts dots and uppercases the DV. Never <input type="number"> — dots and K get stripped or rejected. The package's Stringable implementation means a Rut returned from an accessor serializes into JSON as a string; check your API response shape if you start returning the object rather than a scalar.

Gotchas and Tips

format() / normalize() now throw InvalidFormatException. This is the upgrade break that will 500 your app. 2.x returned false silently, so existing code like $rut->format() ?: $fallback or @if($rut->format()) is now a runtime exception. Grep for ->format( and ->normalize( before upgrading, and audit display accessors — any legacy row with a malformed RUT becomes a 500 on page render. Rut::check() guards, quiet() restores the old return-false behavior, and an explicit try/catch is fine at a display boundary. Assert this in a test; it's the behavior most likely to regress silently.

vnSeparator() changed its type hint from array to string. If you subclassed Rut or overrode that method, your signature is now incompatible and PHP will fatal. Rare, but check before assuming the upgrade is a no-op.

RutCast and unique: can disagree. If the cast stores formatted and your request normalizes to clean, unique:customers,rut checks a clean value against formatted rows and lets duplicates through. Configure the cast's storage format to match your canonical form, and add a unique index on the column — the index catches what the rule misses.

Don't store enum labels or case names. Region/Comuna are enums; store the stable identifier (CUT code / backed value), not officialName() or the case name. Labels, ordering, and even case names are fair game to change between minors.

The DV can be K, and case is not guaranteed. Uppercase before comparison or storage. 'k' and 'K' are the same DV; a lowercase k must not create a second customer.

Leading zeros and short RUTs. Sub-8-digit RUTs are commonly written with leading zeros (01234567-8) and may be treated differently than the padded value. Pick a canonical form (pad to 8 before validation and storage) and apply it in prepareForValidation — not ad hoc in controllers.

Never use a formatted RUT as a lookup key. 12.345.678-9 and 12345678-9 are the same person. Index and query on the clean value with a unique constraint. The configurable cast storage format makes this easier to get right and easier to get wrong — decide once, document it, test it.

Global format state is still stateful. RutFormat makes explicit per-call formatting the idiomatic option — prefer passing the enum at the call site over mutating the config default. Long-running processes (Octane, queue workers, Swoole) inherit whatever the last request set; reset in middleware/worker boot if you do change it.

Config caching. After php artisan config:cache, edits to config/chilean-bundle.php are invisible. config:clear before debugging "why did the storage format change not apply."

Confirm the rule keys in your version. cl_rut and cl_phone are registered by the service provider. If you get "validation rule not found," open the provider and copy the registered key — don't add a second, conflicting rule.

Upgrade in one direction. 2.x is still supported, so you can migrate piecemeal — but don't run a codebase where some paths expect false from format() and others expect an exception. Read the linked upgrade guide, land the exception-handling change in one PR, then adopt the new features.

Package risk, revised. v3 is a materially better-maintained package than 2.x: 105 tests / 952 assertions, PHPStan level 8, Pint, a PHP 8.2–8.5 CI matrix, Dependabot, and first-class translations. That said, it's still a small-maintainer project with no downstream dependents. Pin an exact version, keep a CI smoke test (Rut::check('12.345.678-9') === true plus (new Rut('12.345.678-9'))->format() === '12.345.678-9' — the second half is what actually broke in v3), and wrap the package behind your own RutValidator/RutFormatter so a future swap is a one-file change. The mod-11 algorithm is ~20 lines if it ever comes to that.

Extension points. The Rut class, the RutFormat enum, and the config are the stable surfaces. The Iva rounding mode, Comuna data completeness, and Region::options() key shape are convenience, not contract — assert on them in tests if you depend on specifics.

Testing. Build a data provider: valid RUTs, K DVs, lowercase k, formatted vs clean, leading zeros, empty string, checksum-off-by-one — plus v3-specific cases: format() throws on invalid input, quiet()->format() === false, Rut::check() returns bool for everything including null/garbage, and Rut::random() output always passes check(). Add Iva boundary tests (net→gross→net round-trip) and Clp::parse(Clp::format($n)) === $n. Those cases catch essentially all real-world bugs and make any future migration trivial to verify.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource