Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Elian-D/ORVIAN/llms.txt

Use this file to discover all available pages before exploring further.

ORVIAN follows a modular monolith design: all code lives in one Laravel application, but the surface area visible to each school is controlled by a combination of module configuration and plan-based feature flags. config/modules.php is the single source of truth for every module’s metadata — its display name, SVG icon slug, and the navigation sub-links rendered in the app navbar. Feature flags, stored in the Feature and Plan models, control which modules are enabled for a given school based on the plan they subscribe to.

Module Registry

config/modules.php returns an associative array keyed by module slug. Each entry defines:
KeyTypePurpose
modulestringHuman-readable module name shown in the UI
moduleIconstringSVG icon slug — maps to public/assets/icons/modules/{slug}.svg
moduleLinksarrayList of ['label', 'route'] sub-links rendered in the module navbar
// config/modules.php (excerpt)
'asistencia' => [
    'module'      => 'Asistencia',
    'moduleIcon'  => 'asistencia',
    'moduleLinks' => [
        ['label' => 'Dashboard',      'route' => 'app.attendance.dashboard'],
        ['label' => 'Sesión del Día', 'route' => 'app.attendance.session'],
        ['label' => 'Scanner',        'route' => 'app.attendance.scanner'],
        ['label' => 'Pase de Lista',  'route' => 'app.attendance.classroom.live'],
        ['label' => 'Reportes',       'route' => 'app.attendance.reports'],
        ['label' => 'Excusas',        'route' => 'app.attendance.excuses.index'],
    ],
],
Livewire components that own a full module page pass the config array directly to their layout:
->layout('layouts.app-module', config('modules.asistencia'))
This automatically populates the module navbar with the correct title, icon, and sub-links — no per-component wiring required. Adding a new module requires only one change: add the entry to config/modules.php. Every component that consumes the config inherits it automatically. Adding a sub-link to an existing module is equally simple — add the ['label', 'route'] array to that module’s moduleLinks and it appears in the navbar for all components in that module. Modules not yet in config/modules.php (e.g., notas, horarios, conversaciones, reportes, classroom) have no navbar configuration. Dashboard tiles for these modules are rendered directly in the dashboard view; adding a config/modules.php entry is required before a module can provide a proper module navbar.

Available Modules

SlugDisplay NameStatus
asistenciaAttendance✅ Active
academicoAcademic Management✅ Active
configuracionSchool Administration✅ Active
conversacionesCommunications (Chatwoot)✅ Active — links to external chat.orvian.com.do
reportesReports & Analytics🔜 Coming Soon (comingSoon flag set)
notasGrades🔜 Coming Soon (comingSoon flag set)
classroomLocal Classroom (Virtual)🔜 Coming Soon (commented out in dashboard)
horariosSchedules🔜 Coming Soon (commented out in dashboard)
inventarioInventory / Billing🚫 Not yet implemented — no entry in config/modules.php
Dashboard tiles for Coming Soon modules render with a PRONTO badge and a dimmed icon; clicking them is a no-op. Tiles for modules not included in a school’s plan render with a PLAN badge and a lock icon overlay (active = false).

Plans and Features

Plan model

App\Models\Tenant\Plan represents a subscription tier. Each plan has:
AttributeTypeDescription
slugstringMachine identifier: basic, premium, enterprise
namestringDisplay name
limit_studentsintMaximum enrolled students
limit_usersintMaximum staff/user accounts
pricedecimalMonthly price
is_featuredbooleanHighlighted in the plan picker
is_activebooleanWhether the plan is available for new subscriptions

Feature model

App\Models\Tenant\Feature represents a single toggleable capability. Features are linked to plans through a feature_plan pivot table (with an optional settings JSON column for per-plan overrides).
AttributeDescription
slugUnique identifier used in code (e.g., attendance_qr, academic_grades)
nameHuman-readable label
moduleThe module domain this feature belongs to
is_activeGlobal kill-switch for the feature
The Feature::getIcon() method returns the correct module SVG slug for rendering the feature in plan cards:
// app/Models/Tenant/Feature.php
public function getIcon(): string
{
    return match ($this->slug) {
        'attendance_qr',
        'attendance_facial'     => 'asistencia',
        'academic_grades',
        'academic_excel_import' => 'notas',
        'classroom_internal'    => 'classroom',
        'reports_advanced'      => 'reportes',
        default                 => 'administracion',
    };
}

School::canAccess() and Plan::hasFeature()

The school’s plan is eager-loaded when needed and the feature check is performed through School::canAccess(), which delegates to Plan::hasFeature():
// app/Models/Tenant/Plan.php
public function hasFeature(string $featureSlug): bool
{
    // Uses an in-memory collection check — no extra query
    return $this->features->contains('slug', $featureSlug);
}

// app/Models/Tenant/School.php
public function canAccess(string $featureSlug): bool
{
    // Delegates to the plan — returns false if no plan is assigned
    return $this->plan && $this->plan->hasFeature($featureSlug);
}

Dashboard feature-gating in practice

The dashboard view resolves active feature slugs once per request from the school’s plan, then passes them into each tile’s :active prop:
{{-- resources/views/app/dashboard.blade.php --}}
@php
    $activeModules = auth()->user()->school->plan->features->pluck('slug')->toArray();
@endphp

<x-ui.app-tile
    module="asistencia"
    title="Asistencia"
    :active="in_array('attendance_qr', $activeModules)"
    url="{{ route('app.attendance.dashboard') }}" />

<x-ui.app-tile
    module="academico"
    title="Académico"
    :active="in_array('academic_grades', $activeModules)"
    url="{{ route('app.academic.courses.index') }}" />
A tile with :active="false" is rendered with a grayscale icon and a PLAN badge — it is visible but not navigable, prompting the school to upgrade their plan.

Feature Flags in Routes

Route-level access is controlled by the can: permission middleware (see Roles & Permissions), not by a separate feature-flag middleware. A school on the basic plan whose Teacher role lacks attendance_classroom.record simply cannot reach /app/attendance/classroom because the permission check fails. For modules that are globally not yet implemented (no routes defined), any attempt to construct a URL to them will throw a RouteNotFoundException at the route() helper level, making them unreachable by construction. For modules that are commented out in the dashboard, the tiles are not rendered at all.

Module Icons

SVG icons for each module live in public/assets/icons/modules/ and are rendered through the <x-ui.module-icon> Blade component:
<x-ui.module-icon :name="$slug" class="w-10 h-10" />
The :name prop must match an SVG filename (without the .svg extension) in that directory. Available slugs and their corresponding modules:
SlugModule
asistenciaAttendance
academicoAcademic Management
administracionSchool Administration / Settings
reportesReports
conversacionesCommunications
notasGrades
classroomLocal Classroom
horariosSchedules
The component is used consistently in the module navbar, dashboard tiles, plan feature cards, and any other context that requires a module icon. Pass any Tailwind sizing class via the class attribute.
Always use <x-ui.app-tile> as the standard component for dashboard module cards. It handles all visual states — active, coming soon, plan-locked, and notification badges — in one place. Never create custom card markup for modules; doing so bypasses the consistent hover animations, lock overlays, and badge rendering that app-tile provides out of the box.
{{-- ✅ Correct --}}
<x-ui.app-tile
    module="asistencia"
    title="Asistencia"
    subtitle="Control"
    :active="$school->canAccess('attendance_qr')"
    url="{{ route('app.attendance.dashboard') }}" />

{{-- ❌ Avoid: custom card markup --}}
<a href="{{ route('app.attendance.dashboard') }}" class="rounded-2xl bg-white ...">
    <img src="/assets/icons/modules/asistencia.svg" ... />
    <span>Asistencia</span>
</a>

Build docs developers (and LLMs) love