Importar Excel

This commit is contained in:
Juan Felipe Zapata Moreno 2025-11-12 15:18:12 -06:00
parent 9a0d8030c1
commit 55523a316e
7 changed files with 893 additions and 2 deletions

View File

@ -1,4 +1,7 @@
<?php namespace App\Http\Controllers\Netbien;
<?php
namespace App\Http\Controllers\Netbien;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
@ -7,6 +10,12 @@
use App\Http\Requests\Netbien\SimCardStoreRequest;
use App\Http\Requests\Netbien\SimCardUpdateRequest;
use App\Models\SimCard;
use App\Models\Client;
use App\Models\ClientSim;
use App\Models\Packages;
use App\Enums\SimCardStatus;
use Illuminate\Http\Request;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Support\Facades\DB;
use Notsoweb\ApiResponse\Enums\ApiResponse;
@ -93,4 +102,241 @@ public function destroy(SimCard $simCard)
}
}
/* ---------------------Importar Excel--------------------------- */
private $packageCache = [];
private $stats = [
'created' => 0,
'assigned' => 0,
'packages_created' => 0,
'clients_created' => 0,
'errors' => [],
];
public function import(Request $request)
{
$request->validate([
'file' => 'required|mimes:xlsx,xls,csv|max:10240',
]);
try {
$file = $request->file('file');
$spreadsheet = IOFactory::load($file->getRealPath());
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
foreach ($rows as $index => $row) {
if ($index === 0) continue;
$this->processRow([
'iccid' => $row[4] ?? null,
'msisdn' => $row[5] ?? null,
'estado_de_la_sim' => $row[9] ?? null,
'usuario' => $row[8] ?? null,
]);
}
return ApiResponse::OK->response([
'success' => true,
'message' => 'Importación completada',
'stats' => $this->stats,
'packages_created' => array_values(array_map(fn($p) => [
'name' => $p->name,
'price' => $p->price
], $this->packageCache)),
]);
} catch (\Exception $e) {
return ApiResponse::BAD_REQUEST->response([
'success' => false,
'message' => 'Error en la importación',
'error' => $e->getMessage(),
'line' => $e->getLine(),
], 500);
}
}
private function processRow(array $row)
{
// Validar campos requeridos
if (empty($row['iccid']) || empty($row['msisdn'])) {
return;
}
try {
DB::transaction(function () use ($row) {
// Buscar o crear la SIM
$sim = SimCard::where('iccid', $row['iccid'])->first();
if (!$sim) {
// No existe, crearla
$sim = SimCard::create([
'iccid' => $row['iccid'],
'msisdn' => $row['msisdn'],
'status' => SimCardStatus::AVAILABLE,
]);
$this->stats['created']++;
}
$this->processPackageFromText($sim, $row);
// Asignar cliente
$this->assignToClient($sim, $row);
});
} catch (\Exception $e) {
$this->stats['errors'][] = [
'iccid' => $row['iccid'] ?? 'N/A',
'error' => $e->getMessage(),
];
}
}
private function processPackageFromText(SimCard $sim, array $row)
{
$estadoSim = trim($row['estado_de_la_sim'] ?? '');
if (empty($estadoSim)) {
return;
}
$packageInfo = $this->parsePackageText($estadoSim);
if (!$packageInfo) {
$this->stats['errors'][] = [
'iccid' => $sim->iccid,
'estado_sim' => $estadoSim,
'reason' => 'No se pudo parsear el paquete'
];
return;
}
$package = $this->getOrCreatePackage(
$packageInfo['type'],
$packageInfo['price']
);
$sim->packages()->attach($package->id, [
'activated_at' => now(),
'is_active' => true,
]);
}
private function parsePackageText(string $text): ?array
{
$text = strtolower($text);
$type = null;
if (str_contains($text, 'precarga') || str_contains($text, 'pre carga')) {
$type = 'Precarga';
} elseif (str_contains($text, 'prepago') || str_contains($text, 'pre pago')) {
$type = 'Prepago';
}
if (!$type) {
return null;
}
preg_match('/\$?\s*(\d+)(?:\.\d+)?/', $text, $matches);
$price = isset($matches[1]) ? (float) $matches[1] : 0;
return ['type' => $type, 'price' => $price];
}
private function getOrCreatePackage(string $type, float $price): Packages
{
$cacheKey = "{$type}_{$price}";
if (isset($this->packageCache[$cacheKey])) {
return $this->packageCache[$cacheKey];
}
$package = Packages::firstOrCreate(
['name' => $type, 'price' => $price],
['period' => 0, 'data_limit' => 0]
);
if ($package->wasRecentlyCreated) {
$this->stats['packages_created']++;
}
$this->packageCache[$cacheKey] = $package;
return $package;
}
private function assignToClient(SimCard $sim, array $row)
{
$usuario = trim($row['usuario'] ?? '');
if (empty($usuario) || strtolower($usuario) === 'si' || strtolower($usuario) === 'no') {
return;
}
$client = Client::where('full_name', $usuario)->first()
?? Client::where('full_name', 'LIKE', "%{$usuario}%")->first()
?? Client::where(function ($query) use ($usuario) {
$query->whereRaw("CONCAT(name, ' ', IFNULL(paternal,''), ' ', IFNULL(maternal,'')) LIKE ?", ["%{$usuario}%"]);
})->first();
if (!$client) {
$nameParts = $this->splitFullName($usuario);
try {
$client = Client::create([
'full_name' => $usuario,
'name' => $nameParts['name'],
'paternal' => $nameParts['paternal'],
'maternal' => $nameParts['maternal'],
]);
$this->stats['clients_created']++;
} catch (\Exception $e) {
$this->stats['errors'][] = [
'usuario' => $usuario,
'error' => 'Error al crear cliente: ' . $e->getMessage()
];
return;
}
}
$existingRelation = ClientSim::where('client_id', $client->id)
->where('sim_card_id', $sim->id)
->where('is_active', true)
->exists();
if ($existingRelation) {
return;
}
try {
ClientSim::create([
'client_id' => $client->id,
'sim_card_id' => $sim->id,
'assigned_at' => now(),
'is_active' => true,
]);
$sim->update(['status' => SimCardStatus::ASSIGNED]);
$this->stats['assigned']++;
} catch (\Exception $e) {
$this->stats['errors'][] = [
'iccid' => $sim->iccid,
'usuario' => $usuario,
'error' => 'Error al asignar cliente: ' . $e->getMessage()
];
}
}
private function splitFullName(string $fullName): array
{
$parts = array_filter(explode(' ', trim($fullName)));
$parts = array_values($parts);
return [
'name' => $parts[0] ?? '',
'paternal' => $parts[1] ?? '',
'maternal' => $parts[2] ?? '',
];
}
}

View File

@ -20,6 +20,7 @@ class Client extends Model
'name',
'paternal',
'maternal',
'full_name',
'email',
'phone',
'rfc',

View File

@ -12,6 +12,7 @@
"laravel/pulse": "^1.4",
"laravel/reverb": "^1.4",
"laravel/tinker": "^2.10",
"maatwebsite/excel": "^3.1",
"notsoweb/laravel-core": "dev-main",
"spatie/laravel-permission": "^6.16",
"tightenco/ziggy": "^2.5"

591
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0cdf26aa072a7f833793cfba72e94e81",
"content-hash": "9f4c9c1c470ced74308c732439eb5b3d",
"packages": [
{
"name": "brick/math",
@ -265,6 +265,162 @@
],
"time": "2025-01-03T16:18:33+00:00"
},
{
"name": "composer/pcre",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "composer/semver",
"version": "3.4.4",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.4"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"time": "2025-08-20T19:15:30+00:00"
},
{
"name": "defuse/php-encryption",
"version": "v2.4.0",
@ -809,6 +965,67 @@
},
"time": "2023-08-08T05:53:35+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.19.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
},
"time": "2025-10-17T16:34:55+00:00"
},
{
"name": "firebase/php-jwt",
"version": "v6.11.1",
@ -2968,6 +3185,272 @@
],
"time": "2025-04-12T22:26:52+00:00"
},
{
"name": "maatwebsite/excel",
"version": "3.1.67",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "e508e34a502a3acc3329b464dad257378a7edb4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d",
"reference": "e508e34a502a3acc3329b464dad257378a7edb4d",
"shasum": ""
},
"require": {
"composer/semver": "^3.3",
"ext-json": "*",
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0",
"php": "^7.0||^8.0",
"phpoffice/phpspreadsheet": "^1.30.0",
"psr/simple-cache": "^1.0||^2.0||^3.0"
},
"require-dev": {
"laravel/scout": "^7.0||^8.0||^9.0||^10.0",
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
},
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2025-08-26T09:13:16+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.3"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^12.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2025-07-17T11:15:13+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "monolog/monolog",
"version": "3.9.0",
@ -3841,6 +4324,112 @@
},
"time": "2024-09-04T12:51:01+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.30.1",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "fa8257a579ec623473eabfe49731de5967306c4c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c",
"reference": "fa8257a579ec623473eabfe49731de5967306c4c",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": ">=7.4.0 <8.5.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1"
},
"time": "2025-10-26T16:01:04+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('clients', function (Blueprint $table) {
$table->string('email')->nullable()->change();
$table->string('phone')->nullable()->change();
$table->string('rfc', 13)->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('clients', function (Blueprint $table) {
$table->string('full_name')->after('maternal')->nullable();
});
}
public function down(): void
{
Schema::table('clients', function (Blueprint $table) {
$table->dropColumn('full_name');
});
}
};

View File

@ -24,6 +24,7 @@
/** Rutas protegidas (requieren autenticación) */
Route::middleware('auth:api')->group(function() {
Route::post('import', [SimCardController::class, 'import']);
Route::resource('sim-cards', SimCardController::class);
Route::resource('packages', PackagesController::class);