Skip to content

Conversation

@Punksolid
Copy link
Owner

This pull request introduces a new "Tenant Investigation" feature, allowing the creation, management, and validation of tenant investigations, including CURP and RFC validation through the Moffin API. The implementation includes new models, controllers, events, listeners, services, database migrations, and configuration updates to support this workflow.

Key changes:

Tenant Investigation Core Functionality

  • Added the TenantInvestigation model with status management, UUID/link generation, and relationships to users and lessees. Includes status labels and casts for response data.
  • Created database migrations for the tenant_investigations table and a unique UUID column. [1] [2]
  • Added a factory for generating test data for tenant investigations, including states for lessee association and external data.

Controllers and User Flow

  • Implemented TenantInvestigationController for backend CRUD operations, status updates, and listing/filtering investigations.
  • Added PublicInvestigationController to handle public investigation form display and submission, storing form data and dispatching validation events.

CURP/RFC Validation Integration

  • Introduced the InvestigationFormSubmitted event and ValidateCurpWithMoffin listener to trigger asynchronous CURP and RFC validations using the Moffin API when a form is submitted. [1] [2]
  • Registered the event and listener in EventServiceProvider. [1] [2]
  • Added MoffinService and its interface for CURP/RFC validation, with error handling and logging. [1] [2]

Configuration and Navigation

  • Updated services.php to include Moffin API credentials.
  • Added "Investigaciones" to the main navigation menu.

These changes collectively provide a robust workflow for tenant investigations, including form collection, validation, and administrative management.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a comprehensive "Tenant Investigation" feature that enables landlords to create investigation links, collect tenant information through public forms, and validate CURP and RFC identifiers via the Moffin API integration.

Key Changes:

  • Added complete CRUD workflow for tenant investigations with status tracking (link generated, collecting info, analyzing, completed)
  • Integrated Moffin API service for asynchronous CURP and RFC validation
  • Created public-facing investigation forms accessible via UUID-based links without authentication

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 31 comments.

Show a summary per file
File Description
app/Models/TenantInvestigation.php Core model with status constants, UUID auto-generation, and relationship definitions
database/migrations/2026_01_03_145355_create_tenant_investigations_table.php Migration creating the tenant_investigations table with foreign keys
database/migrations/2026_01_03_151319_add_uuid_to_tenant_investigations_table.php Migration adding UUID column for public link generation
app/Services/MoffinService.php Service implementing CURP and RFC validation via Moffin API
app/Services/MoffinServiceInterface.php Interface defining validation methods
app/Events/InvestigationFormSubmitted.php Event fired when public investigation form is submitted
app/Listeners/ValidateCurpWithMoffin.php Async listener handling validation requests to Moffin API
app/Http/Controllers/Backend/TenantInvestigationController.php Admin controller for investigation management
app/Http/Controllers/PublicInvestigationController.php Public controller for form display and submission
app/Providers/EventServiceProvider.php Registered new event-listener binding
routes/web.php Added public and authenticated routes for investigations
config/services.php Added Moffin API token configuration
config/layout.php Added investigations menu item
resources/views/investigaciones/*.blade.php Admin views for CRUD operations
resources/views/public/investigation-*.blade.php Public form and success views
resources/views/layouts/*.blade.php Updated navigation and added investigation link button
database/factories/TenantInvestigationFactory.php Factory for test data generation
tests/Feature/TenantInvestigationTest.php Feature tests for investigation workflow

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to +23
<input type="url" name="link" class="form-control" value="{{ $investigation->link }}" placeholder="https://..." required>
<small class="form-text text-muted">Ingrese la URL del enlace de investigación</small>
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The edit form displays a readonly input for the link, but the form actually submits the link value to the update endpoint. This is contradictory - if the link should not be edited (readonly), it should not be submitted in the update request. Either make the field truly non-editable by not including it in the form submission, or allow editing and remove the readonly attribute.

Suggested change
<input type="url" name="link" class="form-control" value="{{ $investigation->link }}" placeholder="https://..." required>
<small class="form-text text-muted">Ingrese la URL del enlace de investigación</small>
<input type="url" class="form-control" value="{{ $investigation->link }}" placeholder="https://..." readonly>
<small class="form-text text-muted">Este enlace es generado automáticamente y no puede ser modificado.</small>

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +48
@extends ('layouts.layout-v2')
@section ('contenido')

<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<h3>Nueva Investigación de Inquilino</h3>
@if(count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $error)
<li>{{$error}}</li>
@endforeach
</ul>
</div>
@endif

{!! Form::open(array('url' => 'investigaciones', 'method' => 'POST', 'autocomplete' => 'off')) !!}
{{Form::token()}}

<div class="form-group">
<label for="link">Enlace de Investigación</label>
<input type="url" name="link" class="form-control" placeholder="Se generará automáticamente" readonly>
<small class="form-text text-muted">El enlace se generará automáticamente al guardar</small>
</div>

<div class="form-group">
<label for="lessee_id">Inquilino (Opcional)</label>
<select class="form-control" name="lessee_id">
<option value="">Seleccione un inquilino (opcional)</option>
@foreach($lessees as $lessee)
<option value="{{ $lessee->id }}">
{{ $lessee->nombre }} {{ $lessee->apellido_paterno }} {{ $lessee->apellido_materno }}
</option>
@endforeach
</select>
</div>

<div class="form-group">
<button class="btn btn-primary" type="submit">Guardar</button>
<a class="btn btn-danger" href="{{ url('investigaciones') }}">Cancelar</a>
</div>

{!! Form::close() !!}

</div>
</div>

@endsection
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create view contains a form with a readonly link field and lessee selection, but the create() controller method now auto-creates investigations and redirects to show, completely bypassing this form. This view is unreachable and serves no purpose. Either remove this view or modify the controller to use a two-step create process.

Copilot uses AI. Check for mistakes.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;

Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing class-level documentation. This controller manages the administrative side of tenant investigations and should have a docblock describing its purpose and authentication requirements.

Suggested change
/**
* Backend controller for managing tenant investigations.
*
* This controller provides administrative functionality for listing,
* creating, and managing tenant investigations. All routes pointing
* to this controller are intended to be protected by authentication
* and appropriate authorization middleware to ensure that only
* authorized backend users can access these actions.
*/

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +46
$form = [
'link' => 'https://example.com/investigation/123',
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test manually sets the link value which will be overridden by the model's boot method. The TenantInvestigation model auto-generates the link in the boot() method, so providing a link in the factory or test data is ineffective. Remove the hardcoded link from the test data to reflect actual behavior.

Copilot uses AI. Check for mistakes.
use App\Models\TenantInvestigation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing class-level documentation. This controller handles public-facing investigation forms and should have a docblock explaining its purpose, security considerations, and that these routes are accessible without authentication.

Suggested change
/**
* Controller for public-facing investigation forms.
*
* This controller exposes endpoints that allow external users to view and
* submit investigation information using a public UUID. These routes are
* intentionally accessible without authentication, so all incoming data is
* validated and only non-sensitive details are accepted and stored in the
* investigation's external response payload.
*
* Security considerations:
* - Do not expose internal tenant or investigation identifiers in responses.
* - Rely on the UUID to locate the investigation record and fail closed if
* no matching investigation is found.
* - Validate and sanitize all user input before persisting it or triggering
* downstream processes (such as CURP/RFC validation events).
*/

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +79
$this->assertDatabaseHas('tenant_investigations', [
'link' => $form['link'],
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as testCreateNewInvestigation - the hardcoded link 'https://example.com/investigation/456' will not match the auto-generated link, causing the test to fail. The test should verify the link follows the expected format rather than checking for a specific hardcoded value.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +87
'Authorization' => 'TOKEN ' . $this->token,
])->post($this->baseUrl . '/query/renapo_curp', [
'curp' => $curp,
'accountType' => $accountType,
]);

return [
'success' => $response->successful(),
'status' => $response->status(),
'data' => $response->json(),
'curp' => $curp,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];

} catch (\Exception $e) {
Log::error('Error validating CURP with Moffin', [
'curp' => $curp,
'error' => $e->getMessage(),
]);

return [
'success' => false,
'error' => $e->getMessage(),
'curp' => $curp,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];
}
}

/**
* Validate RFC with Moffin API
*
* @param string $rfc
* @param string $accountType
* @return array
*/
public function validateRfc(string $rfc, string $accountType): array
{
try {
if (empty($this->token)) {
throw new \Exception('Moffin token not configured');
}

$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->token,
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Authorization header uses different formats for CURP and RFC validation. Line 39 uses 'TOKEN ' prefix while line 87 uses 'Bearer ' prefix. This inconsistency will likely cause one of these API calls to fail. Both should use the same authentication scheme required by the Moffin API.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
$investigation = factory(TenantInvestigation::class)->create([
'user_id' => $user->id
]);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test creates an investigation on line 26 but never uses it. The test only verifies that the page loads and displays the title, not that the created investigation appears in the list. This makes the test less meaningful and leaves the created investigation unused.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +117
class MoffinService implements MoffinServiceInterface
{
protected $baseUrl;
protected $token;

/**
* Create a new class instance.
*/
public function __construct()
{
$this->baseUrl = 'https://sandbox.moffin.mx/api/v1';
$this->token = config('services.moffin.private_token');
}

/**
* Validate CURP with Moffin API
*
* @param string $curp
* @param string $accountType
* @return array
*/
public function validateCurp(string $curp, string $accountType): array
{
try {
if (empty($this->token)) {
throw new \Exception('Moffin token not configured');
}

$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'TOKEN ' . $this->token,
])->post($this->baseUrl . '/query/renapo_curp', [
'curp' => $curp,
'accountType' => $accountType,
]);

return [
'success' => $response->successful(),
'status' => $response->status(),
'data' => $response->json(),
'curp' => $curp,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];

} catch (\Exception $e) {
Log::error('Error validating CURP with Moffin', [
'curp' => $curp,
'error' => $e->getMessage(),
]);

return [
'success' => false,
'error' => $e->getMessage(),
'curp' => $curp,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];
}
}

/**
* Validate RFC with Moffin API
*
* @param string $rfc
* @param string $accountType
* @return array
*/
public function validateRfc(string $rfc, string $accountType): array
{
try {
if (empty($this->token)) {
throw new \Exception('Moffin token not configured');
}

$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $this->token,
])->post($this->baseUrl . '/query/sat_rfc', [
'rfc' => $rfc,
'accountType' => $accountType,
]);

return [
'success' => $response->successful(),
'status' => $response->status(),
'data' => $response->json(),
'rfc' => $rfc,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];

} catch (\Exception $e) {
Log::error('Error validating RFC with Moffin', [
'rfc' => $rfc,
'error' => $e->getMessage(),
]);

return [
'success' => false,
'error' => $e->getMessage(),
'rfc' => $rfc,
'account_type' => $accountType,
'validated_at' => now()->toDateTimeString(),
];
}
}
}
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for the MoffinService. This service integrates with an external API and includes error handling logic. Tests should cover: successful CURP validation, successful RFC validation, missing token configuration, API errors, and network failures. Consider using HTTP fakes to avoid actual API calls during tests.

Copilot uses AI. Check for mistakes.
@Punksolid Punksolid merged commit 97c3679 into develop Jan 12, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants