diff --git a/app/Exports/ContactosExport.php b/app/Exports/ContactosExport.php index 04d6da0c8915163cd6429186f859f31f9cd67a0c..41f5f09bd8539c0a81c79ad9c597882f8cdf0352 100644 --- a/app/Exports/ContactosExport.php +++ b/app/Exports/ContactosExport.php @@ -5,6 +5,7 @@ use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithHeadings; use App\Models\Contacto; +use App\Models\Grupos; class ContactosExport implements FromCollection, WithHeadings { @@ -22,9 +23,6 @@ public function __construct($contactIds, $extraFields = [], $filters = []) public function collection() { $query = Contacto::query(); - if (!empty($this->contactIds)) { - $query->whereIn('contactos.id', $this->contactIds); - } // Aplicar filtros adicionales usando relaciones $this->applyFilters($query); @@ -33,12 +31,25 @@ public function collection() $contactos = $query->with([ 'profesion', 'cargo', - 'caracteristicas', 'telefonos', 'pareja', 'correos' ])->get(); + if (!empty($this->contactIds)) { + $contactosObligatorios = Contacto::whereIn('id', $this->contactIds)->with([ + 'profesion', + 'cargo', + 'telefonos', + 'pareja', + 'correos' + ])->get(); + + // Combinar los resultados y eliminar duplicados + $contactos = $contactos->merge($contactosObligatorios)->unique('id'); + + } + // Mapear los datos a un formato exportable return $contactos->map(function ($contacto) { return $this->formatContactoData($contacto); @@ -54,28 +65,76 @@ public function headings(): array private function applyFilters($query) { - if (!empty($this->filters['listas'])) { - $query->orWhereHas('listas', function ($q) { - $q->whereIn('listas.id', $this->filters['listas']); + /* if(!empty($this->filters['subgrupos'])){ + $query->orWhereHas('subgrupos', function ($q) { + $q->whereIn('subgrupos.id', $this->filters['subgrupos']); }); - } + } */ - if (!empty($this->filters['caracteristicas'])) { - $query->orWhereHas('caracteristicas', function ($q) { - $q->whereIn('caracteristicas.id', $this->filters['caracteristicas']); + if (!empty($this->filters['subgrupos'])) { + $query->orWhereHas('subgrupos', function ($q) { + $q->whereIn('subgrupos.id', $this->filters['subgrupos']); }); - } - - if (!empty($this->filters['grupos'])) { - $query->orWhereHas('grupos', function ($q) { - $q->whereIn('grupos.id', $this->filters['grupos']); + } elseif (!empty($this->filters['grupos'])) { + $grupoId = $this->filters['grupos']; + $subgruposIds = Grupos::find($grupoId)->subgrupos->pluck('id')->toArray(); + + $query->where(function ($query) use ($grupoId, $subgruposIds) { + // Contactos directamente en el grupo + $query->orWhereHas('grupos', function ($q) use ($grupoId) { + $q->where('grupos.id', $grupoId); + }); + + // Contactos en subgrupos del grupo (solo si hay subgrupos) + if (!empty($subgruposIds)) { + $query->orWhereHas('subgrupos', function ($q) use ($subgruposIds) { + $q->whereIn('subgrupos.id', $subgruposIds); + }); + } }); } - if (!empty($this->filters['cargos'])) { - $query->orWhereHas('cargo', function ($q) { - $q->whereIn('cargos.id', $this->filters['cargos']); - }); + if ($this->filters['mesInicio'] != "NaN" && $this->filters['diaInicio'] != "NaN") { + $mesInicio = $this->filters['mesInicio']; + $diaInicio = $this->filters['diaInicio']; + + if ($this->filters['mesFin'] != "NaN" && $this->filters['diaFin'] != "NaN") { + $mesFin = $this->filters['mesFin']; + $diaFin = $this->filters['diaFin']; + + if ($mesInicio === $mesFin && $diaInicio === $diaFin) { + $query->where('mes_cump', $mesInicio) + ->where('dia_cump', $diaInicio); + } else { + $query->where(function ($query) use ($mesInicio, $diaInicio, $mesFin, $diaFin) { + // Condición para fechas dentro del mismo mes + $query->where(function ($query) use ($mesInicio, $diaInicio, $mesFin, $diaFin) { + $query->where('mes_cump', $mesInicio) + ->whereBetween('dia_cump', [$diaInicio, $diaFin]); + }) + // Condición para fechas que abarcan varios meses + ->orWhere(function ($query) use ($mesInicio, $diaInicio, $mesFin, $diaFin) { + $query->where(function ($query) use ($mesInicio, $diaInicio) { + $query->where('mes_cump', '>', $mesInicio) + ->orWhere(function ($query) use ($mesInicio, $diaInicio) { + $query->where('mes_cump', $mesInicio) + ->where('dia_cump', '>=', $diaInicio); + }); + }) + ->where(function ($query) use ($mesFin, $diaFin) { + $query->where('mes_cump', '<', $mesFin) + ->orWhere(function ($query) use ($mesFin, $diaFin) { + $query->where('mes_cump', $mesFin) + ->where('dia_cump', '<=', $diaFin); + }); + }); + }); + }); + } + } else { + $query->where('mes_cump', $mesInicio) + ->where('dia_cump', $diaInicio); + } } } @@ -90,7 +149,13 @@ private function formatContactoData($contacto) ]; $extraData = []; - foreach ($this->extraFields as $field) { + $fields = $this->extraFields; + + if (in_array('todos', $fields)) { + $fields = ['domicilio', 'cumpleanos', 'telefonos', 'conyuge', 'email']; + } + + foreach ($fields as $field) { switch ($field) { case 'domicilio': $extraData = array_merge($extraData, [ @@ -113,7 +178,6 @@ private function formatContactoData($contacto) 'Tipo' => $contacto->telefonos->pluck('tipo')->join(', ') ?? 'N/A', 'Estatus' => $contacto->telefonos->pluck('estatus')->join(', ') ?? 'N/A', 'Extensión' => $contacto->telefonos->pluck('ext')->join(', ') ?? 'N/A', - 'ID Radio' => $contacto->telefonos->pluck('id_radio')->join(', ') ?? 'N/A', 'Observaciones' => $contacto->telefonos->pluck('observaciones')->join(', ') ?? 'N/A', ]); break; @@ -137,7 +201,13 @@ private function formatContactoData($contacto) private function getExtraHeadings() { $headings = []; - foreach ($this->extraFields as $field) { + $fields = $this->extraFields; + + if (in_array('todos', $fields)) { + $fields = ['domicilio', 'cumpleanos', 'telefonos', 'conyuge', 'email']; + } + + foreach ($fields as $field) { switch ($field) { case 'domicilio': $headings = array_merge($headings, ['Domicilio Oficial', 'Código Postal', 'Localidad Oficial', 'Municipio Oficial', 'Estado', 'País']); @@ -146,7 +216,7 @@ private function getExtraHeadings() $headings[] = 'Cumpleaños'; break; case 'telefonos': - $headings = array_merge($headings, ['Número de teléfono', 'Tipo', 'Estatus', 'Extensión', 'ID Radio', 'Observaciones']); + $headings = array_merge($headings, ['Número de teléfono', 'Tipo', 'Estatus', 'Extensión', 'Observaciones']); break; case 'conyuge': $headings[] = 'Cónyuge'; diff --git a/app/Http/Controllers/ContactoController.php b/app/Http/Controllers/ContactoController.php index 0b824b4bfd3078fcdbc456f12e0d1f0d0d314a7d..b2b0055eb782afa5ef988e0c464bd8c134269cf0 100644 --- a/app/Http/Controllers/ContactoController.php +++ b/app/Http/Controllers/ContactoController.php @@ -550,108 +550,87 @@ public function viewUpload() public function upload(Request $request) { - $request->validate([ - 'csv_file' => 'required|mimes:csv,txt', - ]); - - $profesiones = Profesion::select('id', 'nombre')->get(); - - $path = $request->file('csv_file')->getRealPath(); - $content = file_get_contents($path); - $content = str_replace("\xEF\xBB\xBF", '', $content); - - $data = array_map('str_getcsv', explode("\n", $content)); - $header = array_shift($data); - - foreach ($data as $row) { - if (count($header) !== count($row)) { - continue; - } - $row = array_combine($header, $row); - - //TODO: Implementar lo mismo para los demas catalogos - - $existProfesion = $profesiones->firstWhere('nombre', $row['profesion']); - - $datosContacto = [ - 'nombre' => $row['nombre'], - 'ap_paterno' => $row['ap_paterno'], - 'ap_materno' => $row['ap_materno'], - 'cargo_desc' => $row['cargo_desc'], - 'mes_cump' => $row['mes_cump'], - 'dia_cump' => $row['dia_cump'], - 'domicilio_oficial' => $row['domicilio_oficial'], - 'codigo_postal' => $row['codigo_postal'], - 'localidad_oficial' => $row['localidad_oficial'], - 'municipio_oficial' => $row['municipio_oficial'], - 'estado' => $row['estado'], - 'pais' => $row['pais'], - 'domicilio_par' => $row['domicilio_par'], - 'codigo_postal_par' => $row['codigo_postal_par'], - 'localidad_par' => $row['localidad_par'], - 'municipio_par' => $row['municipio_par'], - 'estado_par' => $row['estado_par'], - 'pais_par' => $row['pais_par'], - 'finado' => $row['finado'], - 'foto_perfil' => 'assets/images/profile-icon.webp' - ]; - - if(isset($existProfesion)){ - $datosContacto['profesion_id'] = $existProfesion->id; - } - - $contacto = Contacto::create($datosContacto); - - // Procesar teléfonos - $numeros = explode(';', $row['numero']); - $tipos = explode(';', $row['tipo']); - $estatuses = explode(';', $row['estatus']); - $exts = explode(';', $row['ext']); - $observaciones = explode(';', $row['observaciones']); - - for ($i = 0; $i < count($numeros); $i++) { - if ( $numeros[$i] != 'null') { - Telefono::create([ - 'contacto_id' => $contacto->id, - 'numero' => $numeros[$i], - 'tipo' => $tipos[$i], - 'estatus' => $estatuses[$i], - 'ext' => $exts[$i], - 'observaciones' => $observaciones[$i], - ]); - } - } - - // Procesar redes sociales - $redes_sociales = explode(';', $row['red_social']); - $tipos_redes_sociales = explode(';', $row['tipo_red_social']); - - for ($i = 0; $i < count($redes_sociales); $i++) { - if ($redes_sociales[$i] != 'null' || $tipos_redes_sociales[$i] != 'null') { - RedesSociales::create([ - 'contacto_id' => $contacto->id, - 'red_social' => $redes_sociales[$i], - 'tipo_red_social' => $tipos_redes_sociales[$i], - ]); + try{ + $request->validate([ + 'csv_file' => 'required|mimes:csv,txt', + ]); + + $profesiones = Profesion::select('id', 'nombre')->get(); + + $path = $request->file('csv_file')->getRealPath(); + $content = file_get_contents($path); + $content = str_replace("\xEF\xBB\xBF", '', $content); + + $data = array_map('str_getcsv', explode("\n", $content)); + $header = array_shift($data); + $cont = 1; + + foreach ($data as $row) { + if (count($header) !== count($row)) { + continue; } - } - - // Procesar correos electrónicos - $correos = explode(';', $row['correo_electronico']); - $tipos_correos = explode(';', $row['tipo_correo_electronico']); - - for ($i = 0; $i < count($correos); $i++) { - if ($correos[$i] != 'null' || $tipos_correos[$i] != 'null') { - CorreoContactos::create([ - 'contacto_id' => $contacto->id, - 'correo_electronico' => $correos[$i], - 'tipo_correo_electronico' => $tipos_correos[$i], - ]); + $row = array_combine($header, $row); + //TODO: Implementar lo mismo para los demas catalogos + + $existProfesion = $profesiones->firstWhere('nombre', $row['profesion']); + + $datosContacto = [ + 'nombre' => $row['nombre'], + 'ap_paterno' => $row['ap_paterno'], + 'ap_materno' => $row['ap_materno'], + 'cargo_desc' => $row['cargo_desc'], + 'mes_cump' => $row['mes_cump'], + 'dia_cump' => $row['dia_cump'], + 'domicilio_oficial' => $row['domicilio_oficial'], + 'codigo_postal' => $row['codigo_postal'], + 'localidad_oficial' => $row['localidad_oficial'], + 'municipio_oficial' => $row['municipio_oficial'], + 'estado' => $row['estado'], + 'pais' => $row['pais'], + 'domicilio_par' => $row['domicilio_par'], + 'codigo_postal_par' => $row['codigo_postal_par'], + 'localidad_par' => $row['localidad_par'], + 'municipio_par' => $row['municipio_par'], + 'estado_par' => $row['estado_par'], + 'pais_par' => $row['pais_par'], + 'finado' => $row['finado'], + 'foto_perfil' => 'assets/images/profile-icon.webp' + ]; + + if(isset($existProfesion)){ + $datosContacto['profesion_id'] = $existProfesion->id; } + + $contacto = Contacto::create($datosContacto); + + // Procesar teléfonos + $numeros = explode(';', $row['numero']); + $tipos = explode(';', $row['tipo']); + $estatuses = explode(';', $row['estatus']); + $exts = explode(';', $row['ext']); + $observaciones = explode(';', $row['observaciones']); + + $this->validateAndCreateTelefonos($contacto->id, $numeros, $tipos, $estatuses, $exts, $observaciones, $cont); + + // Procesar redes sociales + $redes_sociales = explode(';', $row['red_social']); + $tipos_redes_sociales = explode(';', $row['tipo_red_social']); + + $this->validateAndCreateRS($contacto->id, $redes_sociales, $tipos_redes_sociales, $cont); + + // Procesar correos electrónicos + $correos = explode(';', $row['correo_electronico']); + $tipos_correos = explode(';', $row['tipo_correo_electronico']); + + $this->validateAndCreateEmails($contacto->id, $correos, $tipos_correos, $cont); + $cont++; } + + return redirect()->route('contacto.get')->with('success', 'Importación de contactos almacenada correctamente'); + }catch(\Throwable $e){ + return redirect()->route('contacto.upload')->withErrors('Error al importar los contactos, favor de revisar el archivo CSV. \n' . $e->getMessage()); } - - return redirect()->route('contacto.get')->with('success', 'Importación de contactos almacenada correctamente'); + } public function obtenerEventos(Request $request){ @@ -696,6 +675,83 @@ public function obtenerEventos(Request $request){ return response()->json($eventos); } + + private function validateAndCreateTelefonos($contactoId, $numeros, $tipos, $estatuses, $exts, $observaciones, $cont) + { + for ($i = 0; $i < count($numeros); $i++) { + if ($numeros[$i] != 'null') { + if(!filter_var($numeros[$i], FILTER_VALIDATE_INT)){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El número de teléfono debe ser un número entero. Valor actual: ' . $numeros[$i]); + } + if(!in_array($tipos[$i], ['Directo', 'Particular', 'Celular', 'Conmutador'])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El tipo de teléfono debe ser "Directo", "Particular", "Celular" o "Conmutador". Valor actual: ' . $tipos[$i]); + } + if(!in_array($estatuses[$i], ['Público', 'Privado'])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El estatus del teléfono debe ser "Público" o "Privado". Valor actual: ' . $estatuses[$i]); + } + if(!is_string($exts[$i])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: La extensión del teléfono debe ser una cadena de texto. Valor actual: ' . $exts[$i]); + } + if(!is_string($observaciones[$i])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: Las observaciones del teléfono deben ser una cadena de texto. Valor actual: ' . $observaciones[$i]); + } + + if($exts[$i] === 0 || $exts[$i] === '0'){ + $exts[$i] = ""; + } + + Telefono::create([ + 'contacto_id' => $contactoId, + 'numero' => $numeros[$i], + 'tipo' => $tipos[$i], + 'estatus' => $estatuses[$i], + 'ext' => $exts[$i], + 'observaciones' => $observaciones[$i], + ]); + } + } + } + + private function validateAndCreateRS($contactoId, $redes_sociales, $tipos_redes_sociales, $cont) + { + for ($i = 0; $i < count($redes_sociales); $i++) { + if ($redes_sociales[$i] != 'null') { + if(!filter_var($redes_sociales[$i], FILTER_VALIDATE_URL)){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: La red social debe ser una URL válida. Valor actual: ' . $redes_sociales[$i]); + } + if(!in_array($tipos_redes_sociales[$i], ['Facebook', 'Twitter', 'Instagram', 'LinkedIn', 'Página Web'])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El tipo de red social debe ser "Facebook", "Twitter", "Instagram", "LinkedIn" o "Página Web". Valor actual: ' . $tipos_redes_sociales[$i]); + } + + RedesSociales::create([ + 'contacto_id' => $contactoId, + 'red_social' => $redes_sociales[$i], + 'tipo_red_social' => $tipos_redes_sociales[$i], + ]); + } + } + } + + private function validateAndCreateEmails($contactoId, $correos, $tipos_correos, $cont) + { + for ($i = 0; $i < count($correos); $i++) { + if ($correos[$i] != 'null') { + if(!filter_var($correos[$i], FILTER_VALIDATE_EMAIL)){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El correo electrónico debe ser una dirección de correo válida. Valor actual: ' . $correos[$i]); + } + if(!in_array($tipos_correos[$i], ['Personal', 'Oficial'])){ + throw new \Exception('Error en la fila ' . $cont . ' del archivo CSV: El tipo de correo electrónico debe ser "Personal" o "Oficial". Valor actual: ' . $tipos_correos[$i]); + } + + CorreoContactos::create([ + 'contacto_id' => $contactoId, + 'correo_electronico' => $correos[$i], + 'tipo_correo_electronico' => $tipos_correos[$i], + ]); + } + } + + } } \ No newline at end of file diff --git a/app/Http/Controllers/ReporteController.php b/app/Http/Controllers/ReporteController.php index 185df6d5122cd9403ef8317ee107867092af2de3..35417cefe40338ee4666cc0d53ccdde0f71eddb6 100644 --- a/app/Http/Controllers/ReporteController.php +++ b/app/Http/Controllers/ReporteController.php @@ -10,7 +10,10 @@ use Illuminate\Http\Request; use App\Exports\ContactosExport; +use Barryvdh\DomPDF\Facade\Pdf; +use Illuminate\Support\Facades\Storage; use Maatwebsite\Excel\Facades\Excel; +use ZipArchive; class ReporteController extends Controller { @@ -88,17 +91,50 @@ public function export(Request $request) return response()->json(['Error, No seleccionaste un archivo de exportación valido.'], 400); } - // TODO: Traer datos de todos los contactos $contactIds = array_column($request->input('contactos', []), 'id'); $extraFields = $request->input('exportar', []); $filters = [ 'listas' => $request->input('listas', []), - 'caracteristicas' => $request->input('caracteristicas', []), - 'grupos' => $request->input('grupos', []), - 'cargos' => $request->input('cargos', []), + 'subgrupos' => $request->input('subgrupos', []), + 'grupos' => $request->input('grupo'), + 'mesInicio' => $request->input('mesInicio'), + 'diaInicio' => $request->input('diaInicio'), + 'mesFin' => $request->input('mesFin'), + 'diaFin' => $request->input('diaFin'), ]; - // TODO: Crear archivos de exportacion: PDF, Excel - return Excel::download(new ContactosExport($contactIds, $extraFields, $filters), 'contactos.xlsx'); - //dd($request->all()); + $export = new ContactosExport($contactIds, $extraFields, $filters); + if($request->archivo == 'excel'){ + return Excel::download($export, 'contactos.xlsx'); + }else if($request->archivo == 'pdf'){ + $pdf = Pdf::loadView('adminGen.contactos.contactos_pdf', ['contactos' => $export->collection()])->setPaper('A1', 'landscape');; + return $pdf->download('contactos.pdf'); + }else if($request->archivo == 'ambos'){ + $excelPath = storage_path('app/public/contactos.xlsx'); + $pdfPath = storage_path('app/public/contactos.pdf'); + + Excel::store($export, 'public/contactos.xlsx'); + $pdf = Pdf::loadView('adminGen.contactos.contactos_pdf', ['contactos' => $export->collection()])->setPaper('A1', 'landscape'); + Storage::put('public/contactos.pdf', $pdf->output()); + + // Crear archivo ZIP + $zipPath = storage_path('app/public/contactos.zip'); + $zip = new ZipArchive; + if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) { + $zip->addFile($excelPath, 'contactos.xlsx'); + $zip->addFile($pdfPath, 'contactos.pdf'); + $zip->close(); + } + + // Descargar archivo ZIP + return response()->download($zipPath)->deleteFileAfterSend(true); + }else{ + return response()->json(['Error, No seleccionaste un archivo de exportación valido.'], 400); + } + } + + public function getSubgrupos(Int $id) + { + $grupo = Grupos::find($id); + return response()->json($grupo->subgrupos); } } diff --git a/app/Http/Middleware/CheckRoles.php b/app/Http/Middleware/CheckRoles.php index 6fc699e7f2a63a10e16823569279c2f1c85bfa58..faf81069cace9fedea9a07bf6e8286f743debe7f 100644 --- a/app/Http/Middleware/CheckRoles.php +++ b/app/Http/Middleware/CheckRoles.php @@ -24,7 +24,7 @@ public function handle(Request $request, Closure $next, ... $roles): Response } } - return abort(401); + return redirect()->back()->withErrors('No tienes los permisos necesarios para acceder a esta página.'); }else{ return $next($request); } diff --git a/app/Models/Grupos.php b/app/Models/Grupos.php index 426746206471317a40a1811559ad1a88aaad9af5..8624fc77f864fc34e4f79ee8079bc3f3e2e7817b 100644 --- a/app/Models/Grupos.php +++ b/app/Models/Grupos.php @@ -31,6 +31,11 @@ public function contactos(): BelongsToMany { return $this->belongsToMany(Contacto::class, 'contactos_grupos', 'contacto_id', 'grupo_id'); } + + public function subgrupos(): HasMany + { + return $this->hasMany(Subgrupo::class, 'grupo_id'); + } public $timestamps = false; } diff --git a/composer.json b/composer.json index 449396ab5afe82815af950e713556287d8d79f20..06bea708e19329eee4a6967572959427c39728d1 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^3.1", "laravel/framework": "^11.9", "laravel/jetstream": "^5.1", "laravel/sanctum": "^4.0", diff --git a/composer.lock b/composer.lock index 594bb15649492af61ec01c6155d88934dab50380..a1287ad9ed19c43f9a599654c693707c365433cb 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "43505f151f7ef0c372d5c440872d788e", + "content-hash": "3fae89651502cd60092c646a3b720c5a", "packages": [ { "name": "bacon/bacon-qr-code", @@ -60,6 +60,83 @@ }, "time": "2024-10-01T13:55:55+00:00" }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "8d698b4aec2be91fe1e47ba71ba258acbf23357d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8d698b4aec2be91fe1e47ba71ba258acbf23357d", + "reference": "8d698b4aec2be91fe1e47ba71ba258acbf23357d", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7.0", + "orchestra/testbench": "^7|^8|^9", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-01-21T20:24:00+00:00" + }, { "name": "brick/math", "version": "0.12.1", @@ -563,6 +640,161 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "a51bd7a063a65499446919286fb18b518177155a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/a51bd7a063a65499446919286fb18b518177155a", + "reference": "a51bd7a063a65499446919286fb18b518177155a", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.0" + }, + "time": "2025-01-15T14:09:04+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.4.0", @@ -2613,6 +2845,73 @@ }, "time": "2022-12-02T22:17:43+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "mobiledetect/mobiledetectlib", "version": "4.8.06", @@ -4194,6 +4493,71 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.7.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" + }, + "time": "2024-10-27T17:38:32+00:00" + }, { "name": "spatie/laravel-permission", "version": "6.10.1", diff --git a/database/migrations/2025_01_10_162200_update_ext_row_telefonos_table.php b/database/migrations/2025_01_10_162200_update_ext_row_telefonos_table.php new file mode 100644 index 0000000000000000000000000000000000000000..3696088fa545923d48af98f5e1903706105c531e --- /dev/null +++ b/database/migrations/2025_01_10_162200_update_ext_row_telefonos_table.php @@ -0,0 +1,28 @@ +string('ext')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('telefonos', function (Blueprint $table) { + $table->integer('ext')->change(); + }); + } +}; diff --git a/resources/views/adminGen/contactos/contactos_pdf.blade.php b/resources/views/adminGen/contactos/contactos_pdf.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..1b6d04e5d9170bac6eb975af480a92293d7bf907 --- /dev/null +++ b/resources/views/adminGen/contactos/contactos_pdf.blade.php @@ -0,0 +1,48 @@ + + + + + + Exportación de Contactos + + + +

Listado de Contactos

+ + + + + + + + + @if (!empty($contactos)) + @foreach (array_keys(collect($contactos[0])->except(['Profesión', 'Nombre', 'Apellido Paterno', 'Apellido Materno', 'Cargo'])->toArray()) as $campo) + + @endforeach + @endif + + + + @foreach ($contactos as $contacto) + + + + + + + + @foreach (array_keys(collect($contacto)->except(['Profesión', 'Nombre', 'Apellido Paterno', 'Apellido Materno', 'Cargo'])->toArray()) as $campo) + + @endforeach + + @endforeach + +
ProfesiónNombreApellido PaternoApellido MaternoCargo{{ $campo }}
{{ $contacto['Profesión'] }}{{ $contacto['Nombre'] }}{{ $contacto['Apellido Paterno'] }}{{ $contacto['Apellido Materno'] }}{{ $contacto['Cargo'] }}{{ $contacto[$campo] ?? '' }}
+ + diff --git a/resources/views/adminGen/contactos/export.blade.php b/resources/views/adminGen/contactos/export.blade.php index aa4e64c86125d8bb38f067022e56707b5c504531..ac629c06a0d20eeaeeaada77dc4e680590ad3855 100644 --- a/resources/views/adminGen/contactos/export.blade.php +++ b/resources/views/adminGen/contactos/export.blade.php @@ -17,7 +17,7 @@
-
+ -
+ + + +
+ + +
+ +
- @foreach($grupos as $grupo) @endforeach -
+ +
+ +
+ + +
+
+
- +
+ +
+ + +
+
+ + + -
- - - -
- - -
+
-->
@@ -147,7 +165,7 @@ class="w-full px-3 py-2 bg-white border border-gray-300 rounded-md" let contactosSeleccionados = []; let listasSeleccion = []; let caracteristicasSeleccion = []; - let gruposSeleccion = []; + let subgruposSeleccion = []; let cargosSeleccion = []; let exportarSeleccion = []; @@ -218,15 +236,25 @@ function eliminarContacto(contactoId) { function logData() { const select = document.getElementById('archivo'); const selectedOption = select.options[select.selectedIndex]; + + const fechaInicio = new Date(document.getElementById('fechaInicio').value); + const fechaFin = new Date(document.getElementById('fechaFin').value); + fechaInicio.setHours(fechaInicio.getHours() + 6); + fechaFin.setHours(fechaFin.getHours() + 6); + const data = { contactos: contactosSeleccionados, listas: listasSeleccion, - caracteristicas: caracteristicasSeleccion, - grupos: gruposSeleccion, + subgrupos: subgruposSeleccion, cargos: cargosSeleccion, exportar: exportarSeleccion, archivo: selectedOption.value, + grupo: document.getElementById('grupos').value, + mesInicio: fechaInicio ? (fechaInicio.getMonth() + 1).toString().padStart(2, '0') : null, + diaInicio: fechaInicio ? fechaInicio.getDate().toString().padStart(2, '0') : null, + mesFin: fechaFin ? (fechaFin.getMonth() + 1).toString().padStart(2, '0') : null, + diaFin: fechaFin ? fechaFin.getDate().toString().padStart(2, '0') : null, }; fetch('/reportes/contactos/exportar', { @@ -237,27 +265,55 @@ function logData() { "X-CSRF-Token": $('meta[name="_token"]').attr('content') } }) - .then(response => { - if (!response.ok) { - return response.json().then(err => { - throw err; - }); + .then(response => { + if (!response.ok) { + return response.json().then(err => { + throw err; + }); + } + + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = "contactos_report.xlsx"; // Nombre por defecto + + if (contentDisposition) { + const match = contentDisposition.match(/filename="?([^"]+)"?/); + if (match && match[1]) { + filename = match[1]; } - return response.blob(); // Procesar como Blob (archivo binario) - }) - .then(blob => { - const url = window.URL.createObjectURL(blob); // Crear URL temporal para el archivo - const link = document.createElement('a'); // Crear un elemento dinámicamente - link.href = url; - link.download = 'contactos_report.xlsx'; // Nombre del archivo - document.body.appendChild(link); // Añadir el enlace al DOM - link.click(); // Simular clic para iniciar descarga - link.remove(); // Remover el enlace del DOM - window.URL.revokeObjectURL(url); // Liberar el URL temporal - }) - .catch(err => { - showToast(err.message || 'Error al descargar el archivo', 'Error', 'error'); - }); + } + + return response.blob().then(blob => ({ blob, filename })); + }) + .then(({ blob, filename }) => { + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + }) + .catch(err => { + console.error("Error al descargar el archivo:", err); + showToast(err.message || 'Error al descargar el archivo', 'Error', 'error'); + }); + + } + + async function cargarSubgrupos(grupoId) { + const response = await fetch(`/reportes/subgrupos/${grupoId}`); + const subgrupos = await response.json(); + + const subgruposSelect = document.getElementById('subgrupos'); + subgruposSelect.innerHTML = ''; // Limpiar los subgrupos previos + + subgrupos.forEach(subgrupo => { + const option = document.createElement('option'); + option.value = subgrupo.id; + option.textContent = subgrupo.nombre; + subgruposSelect.appendChild(option); + }); } @@ -283,8 +339,8 @@ function agregarEtiqueta(selectId, containerId) { case 'cargos': cargosSeleccion.push(selectedOption.value); break; - case 'grupos': - gruposSeleccion.push(selectedOption.value); + case 'subgrupos': + subgruposSeleccion.push(selectedOption.value); break; case 'exportar': exportarSeleccion.push(selectedOption.value); diff --git a/routes/web.php b/routes/web.php index e8fe08a0ceee3d459a57e5124292ae449ed3d1ef..b9ec60852d295f663350bc9017772d7cbefb1d87 100644 --- a/routes/web.php +++ b/routes/web.php @@ -116,9 +116,9 @@ config('jetstream.auth_session'), 'verified', CheckBanned::class)->name('contacto.')->group(function() { - Route::get('/contactos', [ContactoController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); + Route::get('/contactos', [ContactoController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector' ); Route::get('/contactos/crear', [ContactoController::class, 'create'])->name('create')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); - Route::get('/contactos/{id}/ver', [ContactoController::class, 'show'])->name('show')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); + Route::get('/contactos/{id}/ver', [ContactoController::class, 'show'])->name('show')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector' ); Route::post('/contactos/crear', [ContactoController::class, 'store'])->name('store')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); Route::get('/contactos/subir', [ContactoController::class, 'viewUpload'])->name('viewUpload')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); Route::post('/contactos/subir', [ContactoController::class, 'upload'])->name('upload')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); @@ -132,12 +132,12 @@ config('jetstream.auth_session'), 'verified', CheckBanned::class)->name('listas.')->group(function() { - Route::get('/listas', [ListaController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin'); - Route::get('/listas/crear', [ListaController::class, 'create'])->name('create')->middleware(CheckRoles::class . ':admingen,admin'); - Route::post('/listas/crear', [ListaController::class, 'store'])->name('store')->middleware(CheckRoles::class . ':admingen,admin'); - route::get('/listas/{id}/ver', [ListaController::class, 'show'])->name('show')->middleware(CheckRoles::class . ':admingen,admin'); - Route::get('/listas/{id}/editar', [ListaController::class, 'edit'])->name('edit')->middleware(CheckRoles::class . ':admingen,admin' ); - Route::put('/listas/{id}/editar', [ListaController::class, 'update'])->name('update')->middleware(CheckRoles::class . ':admingen,admin' ); + Route::get('/listas', [ListaController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin,capturista'); + Route::get('/listas/crear', [ListaController::class, 'create'])->name('create')->middleware(CheckRoles::class . ':admingen,admin,capturista'); + Route::post('/listas/crear', [ListaController::class, 'store'])->name('store')->middleware(CheckRoles::class . ':admingen,admin,capturista'); + route::get('/listas/{id}/ver', [ListaController::class, 'show'])->name('show')->middleware(CheckRoles::class . ':admingen,admin,capturista'); + Route::get('/listas/{id}/editar', [ListaController::class, 'edit'])->name('edit')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); + Route::put('/listas/{id}/editar', [ListaController::class, 'update'])->name('update')->middleware(CheckRoles::class . ':admingen,admin,capturista' ); Route::delete('/listas/{id}', [ListaController::class, 'destroy'])->name('destroy')->middleware(CheckRoles::class . ':admingen,admin'); }); @@ -153,7 +153,8 @@ config('jetstream.auth_session'), 'verified', CheckBanned::class)->name('reporte.')->group(function(){ - Route::get('/reportes/contactos', [ReporteController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin'); - Route::get('/reportes/contactos/buscar', [ReporteController::class, 'find'])->name('find')->middleware(CheckRoles::class . ':admingen,admin'); - Route::post('/reportes/contactos/exportar', [ReporteController::class, 'export'])->name('export')->middleware(CheckRoles::class . ':admingen,admin'); -}); \ No newline at end of file + Route::get('/reportes/contactos', [ReporteController::class, 'index'])->name('get')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector'); + Route::get('/reportes/contactos/buscar', [ReporteController::class, 'find'])->name('find')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector'); + Route::post('/reportes/contactos/exportar', [ReporteController::class, 'export'])->name('export')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector'); + Route::get('/reportes/subgrupos/{id}', [ReporteController::class, 'getSubgrupos'])->name('subgrupos')->middleware(CheckRoles::class . ':admingen,admin,capturista,lector'); + }); \ No newline at end of file