HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux Bradford-Sitios 6.14.0-1017-azure #17~24.04.1-Ubuntu SMP Mon Dec 1 20:10:50 UTC 2025 x86_64
User: www-data (33)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/api_matriculas/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Log;
use App\Http\Middleware\VerifyCsrfToken;

// ===============================
// 🔹 Auth
// ===============================
use App\Http\Controllers\AuthController;

// ===============================
// 🔹 Core Controllers
// ===============================
use App\Http\Controllers\{
    ConfigurationController,
    ContractController,
    ContractFormatController,
    CountryController,
    CourseController,
    PresencialController,
    CourseLetterController,
    DashboardController,
    EnrollmentConfigController,
    ExcelUploadController,
    ExcelUploadLineController,
    GenderController,
    MyProfileController,
    ParentController,
    PeriodController,
    PeriodPricingController,
    PostulationController,
    ProfileController,
    RegionController,
    RelationshipController,
    ReportController,
    StudentController,
    UserController,
    PaymentConceptConfigController,
    WebhookGeneralController
};
use App\Http\Controllers\Odoo\OdooPublicController;

// ===============================
// 🔹 Toku Controllers
// ===============================
use App\Http\Controllers\Toku\{
    PaymentMethodController,
    CustomerController,
    EventsController,
    InvoiceController,
    PaymentController,
    RedirectionController,
    SubscriptionController,
    TransactionController,
    WebhookController
};
use App\Http\Controllers\Toku\webhook\{
    WebhookCustomerController,
    WebhookInvoiceController,
    WebhookPaymentController,
    WebhookPaymentIntentController,
    WebhookPaymentMethodController,
    WebhookPayoutController,
    WebhookSubscriptionController,
    WebhookTransactionController
};

// ===============================
// 🔹 Signapis Controllers
// ===============================
use App\Http\Controllers\Signapis\{
    SignapisDocumentController,
    SignapisLogosController,
    SignapisNotificationsController,
    SignapisReportsController,
    SignapisSigninController,
    SignapisTemplatesController,
    SignapisTestController,
    SignapisWebhookController
};

// ===============================
// 🔹 Firmaki Controllers
// ===============================
use App\Http\Controllers\Firmaki\WebhookController as FirmakiWebhookController;

// ===============================
// 🔹 Rutas protegidas por Sanctum
// ===============================
Route::middleware('auth:sanctum')->get('/user', fn(Request $r) => $r->user());

// ===============================
// 🔹 Autenticación
// ===============================
Route::post('/login', [AuthController::class, 'login']);
Route::post('/activate-account', [AuthController::class, 'activateAccount']);
Route::post('/validate-code-account', [AuthController::class, 'validateCodeActivation']);
Route::post('/activate-password', [AuthController::class, 'activatePasswordWithCode']);
Route::post('/recovery', [AuthController::class, 'sendRecoveryCode']);
Route::post('/validate-code', [AuthController::class, 'validateCode']);
Route::post('/reset-password', [AuthController::class, 'resetPassword']);

Route::post('/sign-in', [AuthController::class, 'loginApi']);
Route::post('/logout', [AuthController::class, 'logout']);

// ===============================================
// 🔹 TODO el resto del API (usuarios + integraciones)
// ===============================================
Route::middleware(['auth.any'])->group(function () {

    // =======================================
    // 🔹 PERFIL / SESIÓN
    // =======================================
    Route::get('/me', [AuthController::class, 'validateSession']);
    Route::get('/me-2/{id}', [AuthController::class, 'getUser']);

    // =======================================
    // 🔹 MI PERFIL
    // =======================================
    Route::get('/my-profile', [MyProfileController::class, 'show']);
    Route::put('/my-profile', [MyProfileController::class, 'update']);
    Route::put('/my-profile/password', [MyProfileController::class, 'updatePassword']);


    Route::middleware(['auth.any'])->group(function () {
        // =======================================
        // 🔹 DASHBOARD
        // =======================================
        Route::get('/dashboard', [DashboardController::class, 'dashboard']);

        // =======================================
        // 🔹 CORE CRUD
        // =======================================
        Route::get('/profiles-list', fn(ProfileController $c) => $c->list(false));
        Route::get('/profiles', [ProfileController::class, 'list']);
        Route::post('/profiles/store', [ProfileController::class, 'store']);
        Route::post('/profiles/permissions-matrix', [ProfileController::class, 'updatePermissionsMatrix']);
        Route::get('/profiles/{id}', [ProfileController::class, 'show']);
        Route::put('/profiles/{id}', [ProfileController::class, 'update']);
        Route::delete('/profiles/{id}', [ProfileController::class, 'destroy']);
        Route::get('/permissions', [ProfileController::class, 'getPermissions']);

        Route::get('/courses-list', fn(CourseController $c) => $c->list(false));
        Route::get('/courses', [CourseController::class, 'list']);
        Route::post('/courses/store', [CourseController::class, 'store']);
        Route::get('/courses/{id}', [CourseController::class, 'show']);
        Route::put('/courses/{id}', [CourseController::class, 'update']);
        Route::delete('/courses/{id}', [CourseController::class, 'destroy']);


        Route::get('/course-letters-list', fn(CourseLetterController $c) => $c->list(false));
        Route::get('/course-letters', [CourseLetterController::class, 'list']);
        Route::post('/course-letters/store', [CourseLetterController::class, 'store']);
        Route::get('/course-letters/{id}', [CourseLetterController::class, 'show']);
        Route::put('/course-letters/{id}', [CourseLetterController::class, 'update']);
        Route::delete('/course-letters/{id}', [CourseLetterController::class, 'destroy']);


        Route::get('/genders-list', fn(GenderController $c) => $c->list(false));
        Route::get('/genders', [GenderController::class, 'list']);
        Route::post('/genders/store', [GenderController::class, 'store']);
        Route::get('/genders/{id}', [GenderController::class, 'show']);
        Route::put('/genders/{id}', [GenderController::class, 'update']);
        Route::delete('/genders/{id}', [GenderController::class, 'destroy']);

        Route::get('/relationships-list', fn(RelationshipController $c) => $c->list(false));
        Route::get('/relationships', [RelationshipController::class, 'list']);
        Route::post('/relationships/store', [RelationshipController::class, 'store']);
        Route::get('/relationships/{id}', [RelationshipController::class, 'show']);
        Route::put('/relationships/{id}', [RelationshipController::class, 'update']);
        Route::delete('/relationships/{id}', [RelationshipController::class, 'destroy']);

        Route::get('/countries-list', fn(CountryController $c) => $c->list(false));
        Route::get('/countries', [CountryController::class, 'list']);
        Route::post('/countries/store', [CountryController::class, 'store']);
        Route::get('/countries/{id}', [CountryController::class, 'show']);
        Route::put('/countries/{id}', [CountryController::class, 'update']);
        Route::delete('/countries/{id}', [CountryController::class, 'destroy']);

        Route::get('/regions-list', fn(RegionController $c) => $c->list(false));
        Route::get('/regions', [RegionController::class, 'list']);
        Route::post('/regions/store', [RegionController::class, 'store']);
        Route::get('/regions/{id}', [RegionController::class, 'show']);
        Route::put('/regions/{id}', [RegionController::class, 'update']);
        Route::delete('/regions/{id}', [RegionController::class, 'destroy']);

        Route::get('/students', [StudentController::class, 'list']);
        Route::post('/students/store', [StudentController::class, 'store']);
        Route::get('/students/{id}', [StudentController::class, 'show']);
        Route::put('/students/{id}', [StudentController::class, 'update']);
        Route::delete('/students/{id}', [StudentController::class, 'destroy']);
        Route::put('/students/{studentId}/period/{periodId}/sibling-order-override', [StudentController::class, 'setSiblingOrderOverride']);

        Route::get('/parents-list', fn(ParentController $c) => $c->list(true));
        Route::get('/parents', [ParentController::class, 'list']);
        Route::post('/parents/store', [ParentController::class, 'store']);
        Route::get('/parents/{id}', [ParentController::class, 'show']);
        Route::put('/parents/{id}', [ParentController::class, 'update']);
        Route::delete('/parents/{id}', [ParentController::class, 'destroy']);

        Route::get('/users', [UserController::class, 'list']);
        Route::post('/users/store', [UserController::class, 'store']);
        Route::get('/users/{id}', [UserController::class, 'show']);
        Route::put('/users/{id}', [UserController::class, 'update']);
        Route::delete('/users/{id}', [UserController::class, 'destroy']);

        Route::get('/configuration', [ConfigurationController::class, 'showByCode']);
        Route::post('/configuration', [ConfigurationController::class, 'storeOrUpdate']);

        Route::get('/contract-formats', [ContractFormatController::class, 'list']);
        Route::get('/contract-formats/{periodId}', [ContractFormatController::class, 'showByPeriod']);
        Route::post('/contract-formats', [ContractFormatController::class, 'storeOrUpdate']);

        Route::post('/contract-formats/copy', [ContractFormatController::class, 'copy']);

        Route::get('postulations', [PostulationController::class, 'list']);
        Route::post('postulations/store', [PostulationController::class, 'store']);
        Route::get('postulations/{id}', [PostulationController::class, 'show']);
        Route::put('postulations/{id}', [PostulationController::class, 'update']);
        Route::delete('postulations/{id}', [PostulationController::class, 'destroy']);

        Route::get('periods/my-periods', [PeriodController::class, 'myPeriods']);
        Route::get('periods', [PeriodController::class, 'list']);
        Route::post('periods', [PeriodController::class, 'store']);
        Route::get('periods/{id}', [PeriodController::class, 'show']);
        Route::put('periods/{id}', [PeriodController::class, 'update']);
        Route::delete('periods/{id}', [PeriodController::class, 'destroy']);
        Route::put('/periods/{id}/activate', [PeriodController::class, 'activate']);
        Route::put('/periods/{id}/deactivate', [PeriodController::class, 'deactivate']);
        Route::get('/get-period', [PeriodController::class, 'getPeriodActivate']);

        Route::get('/contracts', [ContractController::class, 'list']);
        Route::get('/contracts/incomplete-enrollments', [ContractController::class, 'incompleteEnrollments']);
        Route::post('/contracts/recalculate-siblings', [ContractController::class, 'recalculateSiblings']);
        Route::get('/contracts/{id}', [ContractController::class, 'show']);
        Route::put('/contracts/{code}/update-parent', [ContractController::class, 'updateParent']);
        Route::put('/contracts/{code}/change-parent', [ContractController::class, 'changeParent']);
        Route::get('/contracts/{code}/get-contract', [ContractController::class, 'showByCode']);
        Route::put('/contracts/{code}/signature', [ContractController::class, 'signature']);
        Route::post('/contracts/{code}/upload-signed', [ContractController::class, 'uploadSignedPdf']);
        Route::post('contracts', [ContractController::class, 'store']);
        Route::put('/contracts/{id}', [ContractController::class, 'update']);
        Route::delete('/contracts/{id}', [ContractController::class, 'destroy']);
        // Route::get('/contracts/{id}/send-pdf', [ContractController::class, 'sendPDF'])->name('contracts.send_pdf');
        Route::get('/get-contract', [ContractController::class, 'getContractActive']);

        Route::get('/webhook-logs', [WebhookGeneralController::class, 'list']);  // Listar Webhooks

        // =======================================
        // 🔹 REPORTES
        // =======================================
        Route::prefix('reports')->group(function () {
            Route::get('/contracts', [ReportController::class, 'contractsReport']);
            Route::get('/contracts/export-excel', [ReportController::class, 'contractsReportExcel']);
            Route::get('/financial', [ReportController::class, 'financialReport']);
            Route::get('/financial-by-period/{periodId}', [ReportController::class, 'financialReportByPeriod']);
            Route::get('/financial/export-excel', [ReportController::class, 'financialReportExcel']);
            Route::get('/comparative-multiple', [ReportController::class, 'comparativeReport']);
            Route::get('/comparative-multiple/export-excel', [ReportController::class, 'comparativeReportExcel']);
            Route::get('/odoo', [ReportController::class, 'odooReport']);
            Route::get('/odoo/export-excel', [ReportController::class, 'odooReportExcel']);
        });

        // =======================================
        // Presencial - Flujo agente
        // =======================================
        Route::prefix('presencial')->group(function () {
            Route::get('/available-students/{parentId}', [PresencialController::class, 'availableStudents']);
            Route::post('/preview-debts', [PresencialController::class, 'previewDebts']);
            Route::post('/contracts', [PresencialController::class, 'createContract']);
            Route::post('/payments', [PresencialController::class, 'registerPayment']);
            Route::get('/payments/{contractId}', [PresencialController::class, 'getPayments']);
            Route::get('/reconciliation', [PresencialController::class, 'reconciliation']);
            Route::post('/pos-payment', [PresencialController::class, 'posPayment']);
            Route::get('/payment-methods', [PresencialController::class, 'getPaymentMethods']);
            Route::put('/payment-methods/{id}', [PresencialController::class, 'updatePaymentMethod']);
            Route::get('/subscription-status/{paymentId}', [PresencialController::class, 'getSubscriptionStatus']);
            Route::get('/pending-subscriptions/{contractId}', [PresencialController::class, 'getPendingSubscriptions']);
            Route::post('/cancel-subscription/{paymentId}', [PresencialController::class, 'cancelSubscription']);
            Route::post('/reverse-detail/{detailId}', [PresencialController::class, 'reverseDetail']);
            Route::post('/toku-payment', [PresencialController::class, 'tokuPayment']);
            Route::post('/toku-cancel/{paymentId}', [PresencialController::class, 'tokuCancel']);
            Route::get('/toku-status/{paymentId}', [PresencialController::class, 'tokuStatus']);
            Route::get('/pending-toku/{contractId}', [PresencialController::class, 'getPendingToku']);
        });

        // =======================================
        // Integration Logs
        // =======================================
        Route::get('/integration-logs', function (Request $request) {
            $query = \App\Models\ApiIntegrationLog::orderBy('created_at', 'desc');
            if ($request->entity_type) $query->where('entity_type', $request->entity_type);
            if ($request->status) $query->where('status', $request->status);
            if ($request->date_from) $query->whereDate('created_at', '>=', $request->date_from);
            if ($request->date_to) $query->whereDate('created_at', '<=', $request->date_to);
            return genericResponse(['data' => $query->paginate(50), 'message' => 'ok', 'code' => 200], 200);
        });

        Route::get('/sync-logs', function (Request $request) {
            $query = \App\Models\SyncLog::orderBy('created_at', 'desc');
            if ($request->sync_type) $query->where('sync_type', $request->sync_type);
            if ($request->status) $query->where('status', $request->status);
            if ($request->date_from) $query->whereDate('created_at', '>=', $request->date_from);
            if ($request->date_to) $query->whereDate('created_at', '<=', $request->date_to);
            return genericResponse(['data' => $query->paginate(50), 'message' => 'ok', 'code' => 200], 200);
        });

        // =======================================
        // Period Pricing Config
        // =======================================
        Route::prefix('period-pricing')->group(function () {
            Route::get('/concept-codes', [PeriodPricingController::class, 'conceptCodes']);
            Route::post('/import-excel', [PeriodPricingController::class, 'importExcel']);
            Route::post('/copy', [PeriodPricingController::class, 'copy']);
            Route::post('/bulk-delete', [PeriodPricingController::class, 'bulkDelete']);
            Route::post('/bulk', [PeriodPricingController::class, 'bulkSave']);
            Route::get('/{periodId}/export-excel', [PeriodPricingController::class, 'exportExcel']);
            Route::get('/{periodId}', [PeriodPricingController::class, 'list']);
            Route::post('/', [PeriodPricingController::class, 'store']);
            Route::put('/{id}', [PeriodPricingController::class, 'update']);
            Route::delete('/{id}', [PeriodPricingController::class, 'destroy']);
        });

        // =======================================
        // Payment Concepts Config
        // =======================================
        Route::prefix('payment-concepts')->group(function () {
            Route::get('/meta/currency-types', [PaymentConceptConfigController::class, 'currencyTypes']);
            Route::get('/', [PaymentConceptConfigController::class, 'listConcepts']);
            Route::put('/{id}', [PaymentConceptConfigController::class, 'updateConcept']);
            Route::get('/{id}/details', [PaymentConceptConfigController::class, 'listDetails']);
            Route::post('/{id}/details', [PaymentConceptConfigController::class, 'storeDetail']);
            Route::put('/details/{detailId}', [PaymentConceptConfigController::class, 'updateDetail']);
            Route::delete('/details/{detailId}', [PaymentConceptConfigController::class, 'destroyDetail']);
        });

        // =======================================
        // Enrollment Period Configs
        // =======================================
        Route::prefix('enrollment-configs')->group(function () {
            Route::get('/{periodId}', [EnrollmentConfigController::class, 'list']);
            Route::post('/', [EnrollmentConfigController::class, 'storeOrUpdate']);
            Route::put('/{id}/toggle', [EnrollmentConfigController::class, 'toggle']);
            Route::delete('/{id}', [EnrollmentConfigController::class, 'destroy']);
        });

        // Excel Uploads
        Route::prefix('excel-uploads')->group(function () {
            Route::get('/', [ExcelUploadController::class, 'list']);
            Route::get('/stats', [ExcelUploadController::class, 'stats']);
            Route::get('/{id}', [ExcelUploadController::class, 'show']);
            Route::post('/', [ExcelUploadController::class, 'store']);
            Route::put('/{uploadId}/lines/{lineNumber}', [ExcelUploadController::class, 'updateLine']);
            Route::post('/{id}/confirm', [ExcelUploadController::class, 'confirm']);
            Route::post('/{id}/cancel', [ExcelUploadController::class, 'cancel']);
            Route::delete('/{id}', [ExcelUploadController::class, 'destroy']);
            Route::post('/{id}/reprocess', [ExcelUploadController::class, 'reprocess']);
            Route::get('/{id}/logs', [ExcelUploadController::class, 'getLogs']);
            Route::get('/{id}/download-all', [ExcelUploadController::class, 'downloadAllLines']);
        });

        // Excel Upload Types
        Route::prefix('excel-upload-types')->group(function () {
            Route::get('/', [ExcelUploadController::class, 'getTypes']);
            Route::get('/{code}', [ExcelUploadController::class, 'getTypeDetail']);
            Route::get('/{code}/template', [ExcelUploadController::class, 'downloadTemplate']);
            Route::get('/{code}/stats', [ExcelUploadController::class, 'getTypeStats']);
        });

        // Upload Lines
        Route::prefix('upload-lines')->group(function () {
            Route::get('/', [ExcelUploadLineController::class, 'list']);
            Route::get('/{id}', [ExcelUploadLineController::class, 'show']);
            Route::post('/bulk-update', [ExcelUploadLineController::class, 'bulkUpdate']);
            Route::post('/{id}/restore', [ExcelUploadLineController::class, 'restore']);
            // Eliminar línea individual
            Route::delete('/{uploadId}/lines/{lineNumber}', [ExcelUploadLineController::class, 'deleteLine']);

            // Eliminar múltiples líneas
            Route::post('/{uploadId}/lines/bulk-delete', [ExcelUploadLineController::class, 'bulkDeleteLines']);
        });
    });


    // ======================================
    // 🔹 TOKU ROUTES (internas + webhooks)
    // ======================================
    Route::prefix('/toku')->group(function () {

        // 🔸 Clientes internos (API)
        Route::post('customers', [CustomerController::class, 'store']);
        Route::put('customers/{id}', [CustomerController::class, 'update']);
        Route::get('customers/{id}', [CustomerController::class, 'show']);
        Route::delete('customers/{id}', [CustomerController::class, 'delete']);

        // 🔸 Eventos / Facturas / Pagos / Suscripciones
        Route::get('events', [EventsController::class, 'index']);

        Route::post('invoices', [InvoiceController::class, 'store']);
        Route::put('invoices/{id}', [InvoiceController::class, 'update']);
        Route::get('invoices/{id}', [InvoiceController::class, 'show']);
        Route::post('invoices/{id}/void', [InvoiceController::class, 'void']);
        Route::delete('invoices/{id}', [InvoiceController::class, 'destroy']);
        Route::get('invoices/customer/{id}', [InvoiceController::class, 'byCustomer']);

        Route::post('payments', [PaymentController::class, 'storeOrUpdate']);
        Route::put('payments', [PaymentController::class, 'update']);
        Route::delete('payments', [PaymentController::class, 'destroy']);
        Route::get('organization/payments', [PaymentController::class, 'index']);
        Route::get('payments/{id}', [PaymentController::class, 'show']);

        Route::delete('payment-methods/{id}', [PaymentMethodController::class, 'delete']);
        Route::get('organization/payment-methods', [PaymentMethodController::class, 'list']);
        Route::get('payment-methods/customers/{customer_id}', [PaymentMethodController::class, 'listByCustomer']);
        Route::post('subscriptions/associate/payment-method', [PaymentMethodController::class, 'associateToSubscription']);
        Route::post('subscriptions/associate/payment-method/batch', [PaymentMethodController::class, 'associateBatch']);

        Route::post('redirection', [RedirectionController::class, 'store']);
        Route::put('redirection', [RedirectionController::class, 'update']);

        Route::post('subscriptions', [SubscriptionController::class, 'create']);
        Route::put('subscriptions/{id}', [SubscriptionController::class, 'update']);
        Route::get('subscriptions/{id}', [SubscriptionController::class, 'get']);
        Route::delete('subscriptions/{id}', [SubscriptionController::class, 'delete']);
        Route::get('subscriptions/customer/{id}', [SubscriptionController::class, 'getByCustomer']);

        Route::get('transactions', [TransactionController::class, 'index']);
        Route::get('transactions/{id}', [TransactionController::class, 'show']);
        Route::get('transactions/{id}/fees', [TransactionController::class, 'fees']);

        // Webhooks
        Route::get('/webhook-endpoints', [WebhookController::class, 'index']);  // Listar Webhooks
        Route::post('/webhook-endpoints', [WebhookController::class, 'store']); // Crear Webhook
        Route::put('/webhook-endpoints/{id}', [WebhookController::class, 'updateWebhook']); // Editar Webhook
        Route::get('/webhook-endpoints/{id}', [WebhookController::class, 'show']); // Obtener Webhook
        Route::post('/dummy_webhook', [WebhookController::class, 'testDummy']); // Envóo test Webhook
        // ======================================
        // 🔹 Webhooks (acepta X-App-Token o Bearer)
        // ======================================
        Route::prefix('webhooks')->group(function () {

            Route::post('/transactions', [WebhookTransactionController::class, 'handle']);
            Route::post('/customers', [WebhookCustomerController::class, 'handle']);
            Route::post('/subscriptions', [WebhookSubscriptionController::class, 'handle']);
            Route::post('/invoices', [WebhookInvoiceController::class, 'handle']);
            Route::post('/payments', [WebhookPaymentController::class, 'handle']);
            Route::post('/payouts', [WebhookPayoutController::class, 'handle']);
            Route::post('/payment-methods', [WebhookPaymentMethodController::class, 'handle']);
            Route::post('/payment-intents', [WebhookPaymentIntentController::class, 'handle']);
        });

        // Dummy webhook test
        Route::post('/dummy_webhook', [WebhookController::class, 'testDummy']);
    });

    // ======================================
    // 🔹 SIGNAPIS ROUTES
    // ======================================
    Route::prefix('/signapis')->group(function () {

        // ======================================
        // 🔹 RUTAS DE PRUEBAS
        // ======================================
        Route::post('/test/send', [SignapisTestController::class, 'sendToSignapis']);
        Route::post('/test/webhook-register', [SignapisTestController::class, 'registerWebhookTest']);
        Route::post('/test/webhook', [SignapisTestController::class, 'webhook']);


        // ======================================
        // 🔹 Webhooks
        // ======================================
        Route::get('/webhook', [SignapisWebhookController::class, 'getWebhook']);
        Route::post('/webhook', [SignapisWebhookController::class, 'configureWebhook']);
        Route::delete('/webhook', [SignapisWebhookController::class, 'deleteWebhook']);
        Route::post('/webhook/test', [SignapisWebhookController::class, 'testWebhook']);

        // ======================================
        // 🔹 Logos
        // ======================================
        Route::post('/logos', [SignapisLogosController::class, 'createLogo']);
        Route::get('/logos', [SignapisLogosController::class, 'listLogos']);
        Route::get('/logos/{code}', [SignapisLogosController::class, 'getLogo']);
        Route::delete('/logos/{code}', [SignapisLogosController::class, 'deleteLogo']);

        // ======================================
        // 🔹 Templates
        // ======================================
        Route::post('/templates', [SignapisTemplatesController::class, 'createTemplate']);
        Route::get('/templates', [SignapisTemplatesController::class, 'listTemplates']);
        Route::get('/templates/{code}/{type}', [SignapisTemplatesController::class, 'getTemplate']);
        Route::patch('/templates/{code}/{type}', [SignapisTemplatesController::class, 'updateTemplate']);
        Route::delete('/templates/{code}/{type}', [SignapisTemplatesController::class, 'deleteTemplate']);
        Route::delete('/templates/{code}', [SignapisTemplatesController::class, 'deleteTemplatesByCode']);
        Route::post('/templates/test', [SignapisTemplatesController::class, 'testTemplate']);

        // ======================================
        // 🔹 Notifications
        // ======================================
        Route::post('/signatories/{signatoryId}/resend', [SignapisNotificationsController::class, 'resendInvitation']);

        // ======================================
        // 🔹 Documentos
        // ======================================
        Route::post('/documents', [SignapisDocumentController::class, 'createDocument']); // Crear documento
        Route::get('/documents/{documentId}', [SignapisDocumentController::class, 'getDocument']); // Obtener documento

        // Firmantes
        Route::post('/documents/signatories', [SignapisDocumentController::class, 'addSignatories']); // Agregar firmantes
        Route::patch('/documents/signatories/{signatoryId}', [SignapisDocumentController::class, 'updateSignatory']); // Actualizar firmante
        Route::delete('/documents/signatories/{signatoryId}', [SignapisDocumentController::class, 'deleteSignatory']); // Eliminar firmante
        Route::post('/documents/signatories/{signatoryId}/reject', [SignapisDocumentController::class, 'rejectDocument']); // Rechazar documento

        // Cancelaciones
        Route::patch('/documents/{documentId}/cancel', [SignapisDocumentController::class, 'cancelDocumentById']); // Cancelar por ID interno
        Route::patch('/documents/client/{documentClientId}/cancel', [SignapisDocumentController::class, 'cancelDocumentByClientId']); // Cancelar por ID cliente

        // ======================================
        // 🔹 Batches
        // ======================================
        Route::post('/documents/batch/file', [SignapisDocumentController::class, 'uploadBatchFile']);
        Route::post('/documents/batch', [SignapisDocumentController::class, 'createBatch']);
        Route::post('/documents/batch/signatories/{signatoryId}/sign', [SignapisDocumentController::class, 'signBatch']);
        Route::post('/documents/batch/signatories/{signatoryId}/reject', [SignapisDocumentController::class, 'rejectBatch']);
        Route::get('/documents/batch/{groupId}', [SignapisDocumentController::class, 'getBatch']);
        Route::patch('/documents/batch/{groupId}/cancel', [SignapisDocumentController::class, 'cancelBatch']);
        Route::post('/documents/batch/certify', [SignapisDocumentController::class, 'certifyBatch']);
        Route::get('/documents/batch/{groupId}/summary/{email}', [SignapisDocumentController::class, 'getBatchSignerSummary']);
        Route::patch('/documents/batch/{groupId}/summary/{email}', [SignapisDocumentController::class, 'updateBatchSigner']);

        // ======================================
        // 🔹 Documentos (Individual Signing)
        // ======================================
        Route::post('/documents/signatories/{signatoryId}/sign', [SignapisSigninController::class, 'signDocument']);

        // ======================================
        // 🔹 Reportes
        // ======================================
        Route::get('/reports/statusDocuments', [SignapisReportsController::class, 'getStatusDocumentsReport']);
        Route::get('/reports/company', [SignapisReportsController::class, 'getCompanyReport']);
    });

    // ======================================
    // 🔹 WEBHOOK DE PRUEBA
    // ======================================
    Route::post('/payment-webhook', function (Request $request) {
        Log::info('Webhook /payment-webhook recibido', [
            'ip'      => $request->ip(),
            'headers' => $request->headers->all(),
            'payload' => $request->all(),
        ]);
        return response()->json(['ok' => true], 200);
    })->withoutMiddleware([VerifyCsrfToken::class]);
});

// =======================================
// API v1 - Endpoints para Opens (inbound)
// =======================================
Route::prefix('v1')->middleware(['api.v1'])->group(function () {
    // Apoderados
    Route::get('/parents', [OdooPublicController::class, 'listParents']);
    Route::get('/parents/{rut}', [OdooPublicController::class, 'showParentByRut']);
    Route::post('/parents', [OdooPublicController::class, 'createOrUpdateParents']);

    // Alumnos
    Route::get('/students', [OdooPublicController::class, 'listStudents']);
    Route::get('/students/{rut}', [OdooPublicController::class, 'showStudentByRut']);
    Route::post('/students', [OdooPublicController::class, 'createOrUpdateStudents']);

    // Catálogos
    Route::get('/relationships', [OdooPublicController::class, 'listRelationships']);
    Route::get('/courses', [OdooPublicController::class, 'listCourses']);
    Route::get('/course-letters', [OdooPublicController::class, 'listCourseLetters']);
    Route::get('/genders', [OdooPublicController::class, 'listGenders']);
});

Route::post('/toku/webhooks', [WebhookGeneralController::class, 'handle']);
Route::post('/signapis-webhooks', [WebhookGeneralController::class, 'handle']);
Route::get('/contracts/{id}/send-pdf', [ContractController::class, 'sendPDF'])->name('contracts.send_pdf');
Route::get('/contracts/{id}/pdf-view', function ($id, ContractController $controller, Request $request) {
    return $controller->generatePdf($id, $request, 'id');
});

Route::get('/contracts/{code}/pdf', function ($code, ContractController $controller, Request $request) {
    return $controller->generatePdf($code, $request, 'code');
});

Route::get('/presencial/receipt/{paymentId}/pdf', [App\Http\Controllers\PresencialController::class, 'downloadReceipt']);
Route::get('/presencial/attachment/{paymentId}', [App\Http\Controllers\PresencialController::class, 'serveAttachment']);

Route::get('/contracts/{code}/pdf64', [ContractController::class, 'generatePDF64'])
    ->name('contracts.pdf');