-
Notifications
You must be signed in to change notification settings - Fork 1
Validate curp and validate RFC #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this 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.
| <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> |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| <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> |
| @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 |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| use Illuminate\Http\Request; | ||
| use Illuminate\Support\Facades\Auth; | ||
| use Illuminate\Support\Facades\Redirect; | ||
|
|
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| /** | |
| * 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. | |
| */ |
| $form = [ | ||
| 'link' => 'https://example.com/investigation/123', |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| use App\Models\TenantInvestigation; | ||
| use Illuminate\Http\Request; | ||
| use Illuminate\Support\Facades\Validator; | ||
|
|
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| /** | |
| * 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). | |
| */ |
| $this->assertDatabaseHas('tenant_investigations', [ | ||
| 'link' => $form['link'], |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| '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, |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| $investigation = factory(TenantInvestigation::class)->create([ | ||
| 'user_id' => $user->id | ||
| ]); |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
| 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(), | ||
| ]; | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
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.
Estas librerias no se estaban usando en el codigo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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
TenantInvestigationmodel with status management, UUID/link generation, and relationships to users and lessees. Includes status labels and casts for response data.tenant_investigationstable and a unique UUID column. [1] [2]Controllers and User Flow
TenantInvestigationControllerfor backend CRUD operations, status updates, and listing/filtering investigations.PublicInvestigationControllerto handle public investigation form display and submission, storing form data and dispatching validation events.CURP/RFC Validation Integration
InvestigationFormSubmittedevent andValidateCurpWithMoffinlistener to trigger asynchronous CURP and RFC validations using the Moffin API when a form is submitted. [1] [2]EventServiceProvider. [1] [2]MoffinServiceand its interface for CURP/RFC validation, with error handling and logging. [1] [2]Configuration and Navigation
services.phpto include Moffin API credentials.These changes collectively provide a robust workflow for tenant investigations, including form collection, validation, and administrative management.