orderBy('id', 'asc')->paginate(config('app.pagination')); return ApiResponse::OK->response([ 'data' => $simCards, ]); } public function show(SimCard $simCard) { $simCard->load('packSims.package:id,name'); return ApiResponse::OK->response([ 'data' => $simCard, ]); } public function store(SimCardStoreRequest $request) { try { DB::beginTransaction(); $simCard = SimCard::create($request->validated()); if ($request->has('package_id')) { // Asignar el paquete con fecha de activación $simCard->packages()->attach($request->package_id, [ 'activated_at' => now(), 'is_active' => true, ]); } DB::commit(); $simCard->load('activePackage'); return ApiResponse::CREATED->response([ 'data' => $simCard, ]); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al crear SIM card', 'error' => $e->getMessage(), ]); } } public function update(SimCardUpdateRequest $request, SimCard $simCard) { $simCard->update($request->validated()); return ApiResponse::OK->response([ 'data' => $simCard, ]); } public function destroy(SimCard $simCard) { try { DB::beginTransaction(); $simCard->delete(); DB::commit(); return ApiResponse::NO_CONTENT->response(); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al eliminar SIM card', 'error' => $e->getMessage(), ]); } } /* ---------------------Importar Excel--------------------------- */ private $packageCache = []; private $columnMap = []; private $stats = [ 'created' => 0, 'assigned' => 0, 'packages_created' => 0, 'clients_created' => 0, 'sales_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(); DB::beginTransaction(); try { // Leer encabezados de la primera fila $this->buildColumnMap($rows[0]); foreach ($rows as $index => $row) { if ($index === 0) continue; // Saltar encabezados try { $this->processRow([ 'iccid' => $this->getColumnValue($row, 'ICCID'), 'msisdn' => $this->getColumnValue($row, 'MSISDN'), 'paquetes' => $this->getColumnValue($row, 'PAQUETES'), 'usuario' => $this->getColumnValue($row, 'USUARIO'), 'fecha_venta' => $this->getColumnValue($row, 'FECHA_VENTA'), 'metodo_pago' => $this->getColumnValue($row, 'METODO_PAGO'), ], $index + 1); } catch (\Exception $e) { // Capturar información detallada del error antes de revertir DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'Error en la importación', 'error' => $e->getMessage(), 'fila' => $index + 1, 'datos_fila' => [ 'iccid' => $this->getColumnValue($row, 'ICCID'), 'msisdn' => $this->getColumnValue($row, 'MSISDN'), 'paquetes' => $this->getColumnValue($row, 'PAQUETES'), 'usuario' => $this->getColumnValue($row, 'USUARIO'), 'fecha_venta' => $this->getColumnValue($row, 'FECHA_VENTA'), 'metodo_pago' => $this->getColumnValue($row, 'METODO_PAGO'), ], 'stats' => $this->stats, ], 500); } } DB::commit(); 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) { DB::rollBack(); throw $e; } } catch (\Exception $e) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'Error en la importación', 'error' => $e->getMessage(), ], 500); } } private function processRow(array $row, int $rowNumber = 0) { // Validar campos requeridos if (empty($row['iccid']) || empty($row['msisdn'])) { return; } // 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']++; } // Determinar si es una venta (tiene usuario Y paquete) $hasUsuario = !empty($row['usuario']) && strtolower(trim($row['usuario'])) !== 'si' && strtolower(trim($row['usuario'])) !== 'no'; $hasPaquete = !empty($row['paquetes']); if ($hasUsuario && $hasPaquete) { // Es una venta - procesar como venta completa $this->processSale($sim, $row); } else { // No es venta - solo asignar paquete y/o cliente si existen if ($hasPaquete) { $this->processPackageFromText($sim, $row); } if ($hasUsuario) { $this->assignToClient($sim, $row); } } } private function processSale(SimCard $sim, array $row) { // Parsear el paquete desde el texto $estadoSim = trim($row['paquetes'] ?? ''); $packageInfo = $this->parsePackageText($estadoSim); if (!$packageInfo) { $this->stats['errors'][] = [ 'iccid' => $sim->iccid, 'estado_sim' => $estadoSim, 'reason' => 'No se pudo parsear el paquete para la venta' ]; return; } // Obtener o crear el paquete $package = $this->getOrCreatePackage( $packageInfo['type'], $packageInfo['price'] ); // Buscar o crear el cliente $usuario = trim($row['usuario'] ?? ''); $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); $client = Client::create([ 'full_name' => $usuario, 'name' => $nameParts['name'], 'paternal' => $nameParts['paternal'], 'maternal' => $nameParts['maternal'], ]); $this->stats['clients_created']++; } // Parsear fecha de venta $saleDate = $this->parseSaleDate($row['fecha_venta'] ?? null); // Validar y normalizar método de pago $paymentMethod = $this->normalizePaymentMethod($row['metodo_pago'] ?? null); // Crear la venta $sale = Sale::create([ 'client_id' => $client->id, 'cash_close_id' => null, // Se dejará null para importaciones 'total_amount' => $package->price, 'payment_method' => $paymentMethod, 'sale_date' => $saleDate, ]); // Crear el item de venta SaleItem::create([ 'sale_id' => $sale->id, 'sim_card_id' => $sim->id, 'package_id' => $package->id, ]); // Asignar SIM al cliente $existingRelation = ClientSim::where('client_id', $client->id) ->where('sim_card_id', $sim->id) ->where('is_active', true) ->exists(); if (!$existingRelation) { ClientSim::create([ 'client_id' => $client->id, 'sim_card_id' => $sim->id, 'assigned_at' => $saleDate, 'is_active' => true, ]); } // Asignar paquete a la SIM $sim->packages()->attach($package->id, [ 'activated_at' => $saleDate, 'is_active' => true, ]); // Actualizar status de la SIM $sim->update(['status' => SimCardStatus::ASSIGNED]); $this->stats['sales_created']++; $this->stats['assigned']++; } private function parseSaleDate($dateValue) { if (empty($dateValue)) { return now(); } if (is_numeric($dateValue)) { try { $excelBaseDate = new \DateTime('1899-12-30'); $excelBaseDate->modify("+{$dateValue} days"); return $excelBaseDate->format('Y-m-d H:i:s'); } catch (\Exception $e) { return now(); } } // Intentar parsear como fecha try { return \Carbon\Carbon::parse($dateValue)->format('Y-m-d H:i:s'); } catch (\Exception $e) { return now(); } } private function normalizePaymentMethod($method) { if (empty($method)) { return 'import'; } $method = strtolower(trim($method)); // Mapear variaciones comunes $methodMap = [ 'cash' => 'cash', 'efectivo' => 'cash', 'cash' => 'cash', 'card' => 'card', 'tarjeta' => 'card', 'credito' => 'card', 'debito' => 'card', 'transfer' => 'transfer', 'transferencia' => 'transfer', 'import' => 'import', 'importacion' => 'import', ]; return $methodMap[$method] ?? 'import'; } private function processPackageFromText(SimCard $sim, array $row) { $estadoSim = trim($row['paquetes'] ?? ''); 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::create([ 'name' => $type, 'price' => (float) $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); $client = Client::create([ 'full_name' => $usuario, 'name' => $nameParts['name'], 'paternal' => $nameParts['paternal'], 'maternal' => $nameParts['maternal'], ]); $this->stats['clients_created']++; } $existingRelation = ClientSim::where('client_id', $client->id) ->where('sim_card_id', $sim->id) ->where('is_active', true) ->exists(); if ($existingRelation) { return; } 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']++; } 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] ?? '', ]; } /** * Construye un mapa de nombres de columnas a índices */ private function buildColumnMap(array $headers) { $this->columnMap = []; foreach ($headers as $index => $header) { // Normalizar el nombre de la columna (mayúsculas, sin espacios extra) $normalizedHeader = strtoupper(trim($header ?? '')); if (!empty($normalizedHeader)) { $this->columnMap[$normalizedHeader] = $index; } } } /** * Obtiene el valor de una columna por su nombre */ private function getColumnValue(array $row, string $columnName) { $normalizedName = strtoupper(trim($columnName)); // Buscar en el mapa de columnas if (isset($this->columnMap[$normalizedName])) { $index = $this->columnMap[$normalizedName]; return $row[$index] ?? null; } // Si no se encuentra la columna, retornar null return null; } }