Ver Fonte

subo render de imagenes y diseño completado

FREDY há 3 meses atrás
pai
commit
4ba0ca5d36

+ 120 - 38
Back/backendP-Educativa/app/Http/Controllers/PersonalizarController.php

@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Str;
 
 
 class PersonalizarController extends Controller
@@ -47,6 +48,65 @@ public function show()
 }
 
 
+public function eliminarPerlogo(Request $request)
+{
+    try {
+        $request->validate([
+            'rutaArchivo' => 'required|string'
+        ]);
+
+        $ruta = $request->input('rutaArchivo');
+
+        $campos = ['PERLOGO', 'PERLOGO1', 'PERLOGO2'];
+
+        $registro = DB::table('personalizar')
+            ->where(function ($query) use ($campos, $ruta) {
+                foreach ($campos as $campo) {
+                    $query->orWhere($campo, $ruta);
+                }
+            })->first();
+
+        if (!$registro) {
+            return response()->json(['mensaje' => 'No se encontró ningún campo con esa ruta'], 404);
+        }
+
+        // Verificar si el archivo existe en S3
+        if (Storage::disk('s3')->exists($ruta)) {
+            Storage::disk('s3')->delete($ruta);
+        } else {
+            return response()->json(['mensaje' => 'El archivo no existe en S3'], 404);
+        }
+
+        // Detectar qué campo contiene la ruta
+        $campoAActualizar = null;
+        foreach ($campos as $campo) {
+            if ($registro->$campo === $ruta) {
+                $campoAActualizar = $campo;
+                break;
+            }
+        }
+
+        if (!$campoAActualizar) {
+            return response()->json(['mensaje' => 'No se pudo determinar qué campo actualizar'], 400);
+        }
+
+        // Actualizar el campo a null
+        DB::table('personalizar')
+            ->where('id', $registro->id)
+            ->update([
+                $campoAActualizar => null,
+                'updated_at' => now()
+            ]);
+
+        return response()->json(['mensaje' => 'Imagen eliminada correctamente'], 200);
+
+    } catch (\Exception $e) {
+        return response()->json([
+            'mensaje' => 'Error al eliminar imagen',
+            'error' => $e->getMessage()
+        ], 500);
+    }
+}
 
 
 public function update(Request $request)
@@ -63,53 +123,51 @@ public function update(Request $request)
         $personalizar->PERCOL_LTR_BAR = $request->colorLetraNav;
         $personalizar->PERPR_COL_TIT = $request->primerColorTitulos;
         $personalizar->PERVINCULOS = $request->colorVinculos;
+$procesarImagen = function(?string $imageData, string $nombreArchivoBase, ?string $rutaActual) {
+    if (empty($imageData) || $imageData === 'null') {
+        return $rutaActual;
+    }
 
-        $procesarImagen = function(?string $imageData, string $nombreArchivo, ?string $rutaActual) {
-            if (empty($imageData) || $imageData === 'null') {
-                return $rutaActual;
-            }
-
-            if (!str_starts_with($imageData, 'data:image')) {
-
-
-                $pathActual = $rutaActual ?? '';
-
-                $urlActualCompleta = Storage::disk('s3')->url($pathActual);
-                if ($imageData === $urlActualCompleta || $imageData === $rutaActual) {
-                    return $rutaActual;
-                }
+    if (!str_starts_with($imageData, 'data:image')) {
+        // Manejo de URL ya subida (igual que antes)
+        $pathActual = $rutaActual ?? '';
+        $urlActualCompleta = Storage::disk('s3')->url($pathActual);
+        if ($imageData === $urlActualCompleta || $imageData === $rutaActual) {
+            return $rutaActual;
+        }
+        return $imageData;
+    }
 
-                return $imageData;
-            }
+    if (preg_match('/^data:image\/(\w+);base64,/', $imageData, $matches)) {
+        $fileExtension = strtolower($matches[1]); // png, jpeg, jpg, etc.
+    } else {
+        return $rutaActual;
+    }
 
-            if (preg_match('/^data:image\/(\w+);base64,/', $imageData, $matches)) {
-                $fileExtension = strtolower($matches[1]); // png, jpeg, jpg, etc.
-            } else {
-                return $rutaActual;
-            }
+    $allowedExtensions = ['jpeg', 'jpg', 'png'];
+    if (!in_array($fileExtension, $allowedExtensions)) {
+        throw new \Exception('Formato de imagen no soportado. Por favor, utiliza JPEG o PNG.');
+    }
 
-            $allowedExtensions = ['jpeg', 'jpg', 'png'];
-            if (!in_array($fileExtension, $allowedExtensions)) {
-                throw new \Exception('Formato de imagen no soportado. Por favor, utiliza JPEG o PNG.');
-            }
+    $imageBase64 = substr($imageData, strpos($imageData, ',') + 1);
+    $imageBase64 = str_replace(' ', '+', $imageBase64);
 
-            $imageBase64 = substr($imageData, strpos($imageData, ',') + 1);
-            $imageBase64 = str_replace(' ', '+', $imageBase64);
+    // Validar tamaño máximo 1MB
+    $decodedSize = strlen(base64_decode($imageBase64));
+    if ($decodedSize > 1 * 1024 * 1024) {
+        throw new \Exception('El tamaño de la imagen excede el límite permitido (1 MB).');
+    }
 
-            // Validar tamaño máximo 1MB
-            $decodedSize = strlen(base64_decode($imageBase64));
-            if ($decodedSize > 1 * 1024 * 1024) {
-                throw new \Exception('El tamaño de la imagen excede el límite permitido (1 MB).');
-            }
+    // Generar sufijo único con timestamp y UUID
+    $uniqueSuffix = time() . '_' . Str::uuid();
 
-            $path = 'personalizar/' . $nombreArchivo . '.' . $fileExtension;
+    $path = 'personalizar/' . $nombreArchivoBase . '_' . $uniqueSuffix . '.' . $fileExtension;
 
-            // Subir a S3 con visibilidad pública
-            Storage::disk('s3')->put($path, base64_decode($imageBase64), 'public');
+    // Subir a S3 con visibilidad pública
+    Storage::disk('s3')->put($path, base64_decode($imageBase64), 'public');
 
-            // Guardamos solo la ruta relativa (ejemplo: personalizar/personalizar.png)
-            return $path;
-        };
+    return $path;
+};
 
         try {
             $personalizar->PERLOGO = $procesarImagen($request->imagen, 'personalizar', $personalizar->PERLOGO);
@@ -170,5 +228,29 @@ public function update(Request $request)
     }
 }
 
+
+
+public function getPerLogo1()
+{
+    $personalizar = Personalizar::find(1);
+    if ($personalizar && $personalizar->PERLOGO1) {
+        $url = Storage::disk('s3')->url($personalizar->PERLOGO1);
+        return response()->json(['PERLOGO1' => $url], 200);
+    }
+    return response()->json(['PERLOGO1' => null], 404);
+}
+
+
+public function getPerLogo2()
+{
+    $personalizar = Personalizar::find(1);
+    if ($personalizar && $personalizar->PERLOGO2) {
+        $url = Storage::disk('s3')->url($personalizar->PERLOGO2);
+        return response()->json(['PERLOGO2' => $url], 200);
+    }
+    return response()->json(['PERLOGO2' => null], 404);
+}
+
+
 }
 

+ 4 - 0
Back/backendP-Educativa/routes/api.php

@@ -273,6 +273,10 @@ Route::get('/respuestas/formulario', [RespuestasController::class, 'obtenerRespu
 Route::put('/actualizarRespuesta', [RespuestasController::class, 'actualizarRespuesta']);
 
 Route::post('/eliminarArchivo', [RegistroAcademico::class, 'eliminarArchivo']);
+Route::post('/eliminarPerlogo', [PersonalizarController::class, 'eliminarPerlogo']);
+
+Route::get('/personalizar/perlogo1', [PersonalizarController::class, 'getPerLogo1']);
+Route::get('/personalizar/perlogo2', [PersonalizarController::class, 'getPerLogo2']);
 
 
 });

+ 255 - 347
Front/src/app/modules/Administrador/pages/personalizar/personalizar.component.css

@@ -1,449 +1,357 @@
-.centrar-form {
-    margin-top: 3%;
-    display: flex;
-    justify-content: center;
-    margin-bottom: 5%;
+/* === ESTRUCTURA PRINCIPAL === */
+.centrar {
+  margin-top: 4%;
+  line-height: auto;
+  text-align: center;
 }
 
-.form-personalizar {
-    background-color: white;
-
-    box-shadow: rgba(13, 102, 204, 0.342) 0 0 10px 7px;
-    padding-top: 40px;
-    padding-bottom: 40px;
-    padding-right: 40px;
-    padding-left: 20px;
-
+.fondo {
+  border-radius: 10px;
+  line-height: 1;
+  display: inline-block;
+  vertical-align: middle;
 }
-
-
-.form-personalizar-loader {
-    background-color: white;
-    border-radius: 15px;
-    box-shadow: rgba(13, 102, 204, 0.342) 0 0 10px 7px;
-    padding-top: 40px;
-    padding-bottom: 40px;
-    padding-right: 40px;
-    padding-left: 20px;
+.tab-container {
+  display: flex;
+  justify-content: center;
+  width: 100%;
+  background-color: transparent;
+  padding: 20px 0;
+}
+.tab-container > mat-tab-group {
+  width: 50%;
+  background-color: white;
+  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
+  align-items: center;
 }
 
-.divRes{
 
-  text-align: center;
-}
-.centrar-buttons {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-around;
+.content {
+  margin: 0;
+  padding: 10px 70px;
+  font-weight: 500;
+  font-size: 32px;
+  color: white;
+  font-family: "Serenity Medium";
 }
 
-.centrar {
-    margin-top: 4%;
-    line-height: auto;
-    text-align: center;
+/* === FORMULARIO === */
+.centrar-form {
+  margin-top: 3%;
+  margin-bottom: 5%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  }
+.form-personalizar,
+.form-personalizar-loader {
+  background-color: white;
+  box-shadow: rgba(13, 102, 204, 0.2) 0 0 10px 7px;
+  padding: 40px;
 }
 
-.fondo {
-    border-radius: 10px;
-    line-height: 1;
-    display: inline-block;
-    vertical-align: middle;
 
+.form {
+  font-family: Roboto;
 }
 
-.content {
-    margin-bottom: 0;
-    margin-top: 0;
-    padding-top: 10px;
-    padding-bottom: 10px;
-    padding-left: 70px;
-    padding-right: 70px;
-    font-weight: 500;
-    font-size: 32px;
-    color: white;
-    font-family: "Serenity Medium";
+.fila-form {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
 }
 
-.form {
-    font-family: Roboto;
+.text {
+  margin-left: 30px;
+  font-size: 24px;
+  width: 250px;
 }
 
-.fila-form {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    align-items: center;
+.input {
+  width: 410px;
 }
 
-.input {
-    width: 410px;
+/* === SPINNER === */
+mat-spinner,
+.spinner-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
-input[type="file"] {
-    display: none;
+
+.divRes {
+  display: flex;
+  justify-content: center;
+  width: 100%;
+  text-align: center;
 }
 
+/* === COLORES === */
 .input-line {
-
-    padding: 10px;
-    border: 1px solid rgb(141, 141, 141);
+  padding: 10px;
+  border: 1px solid rgb(141, 141, 141);
 }
 
 .showColor {
-    border-radius: 10px;
-    height: 30px;
-    width: 360px;
+  border-radius: 10px;
+  height: 30px;
+  width: 360px;
 }
 
-.custom-file-upload {
-    background-color: #24B27D;
-    border: none;
-    color: white;
-    padding: 5px 10px;
-    text-align: center;
-    cursor: pointer;
-    display: inline-block;
-    font-size: 15px;
-    border-radius: 5px;
-    display: flex;
-    align-items: center;
-    width: 100px;
+/* === COLOR PICKERS === */
+.style1, .style2, .style3 {
+  appearance: none;
+  background-color: transparent;
+  border: 1px solid rgb(119, 119, 119);
+  cursor: pointer;
 }
 
-.custom-file-upload:hover {
-    background-color: #21a172;
+.style1 {
+  height: 30px;
 }
 
-.text {
-    margin-left: 30px;
-    font-size: 24px;
-    width: 250px;
+.style2,
+.style3 {
+  height: 40px;
 }
 
-.style1 {
-    -webkit-appearance: none;
-    -moz-appearance: none;
-    appearance: none;
-    height: 30px;
-    background-color: transparent;
-    border: 1px solid rgb(119, 119, 119);
-    cursor: pointer;
+.style1::-webkit-color-swatch,
+.style2::-webkit-color-swatch,
+.style3::-webkit-color-swatch,
+.style1::-moz-color-swatch,
+.style2::-moz-color-swatch,
+.style3::-moz-color-swatch {
+  border-radius: 5px;
+  border: 1px solid rgb(119, 119, 119);
 }
 
-.style1::-webkit-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+/* === DEGRADADO === */
+.container {
+  display: flex;
+  width: 410px;
+  height: 50px;
 }
 
-.style1::-moz-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+.tercio {
+  flex-grow: 1;
+  flex-basis: 0;
+  margin: 0 5px;
 }
 
-.style2 {
-    -webkit-appearance: none;
-    -moz-appearance: none;
-    appearance: none;
-    height: 40px;
-    background-color: transparent;
-    border: 1px solid rgb(119, 119, 119);
-    cursor: pointer;
+.gradient {
+  height: auto;
+  width: 70%;
 }
 
-.style2::-webkit-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+/* === BOTONES === */
+.centrar-buttons {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
 }
 
-.style2::-moz-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+.button-save,
+.button-reset,
+.button-cancel {
+  border-radius: 5px;
+  font-size: 16px;
+  padding: 8px 15px;
+  border: none;
+  color: white;
+  cursor: pointer;
+  width: auto;
 }
 
-.style3 {
-    -webkit-appearance: none;
-    -moz-appearance: none;
-    appearance: none;
-    height: 40px;
-    background-color: transparent;
-    border: 1px solid rgb(119, 119, 119);
-    cursor: pointer;
+.button-save {
+  background-color: #6699cd;
 }
 
-.style3::-webkit-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+.button-save:hover {
+  background-color: #5581ac;
 }
 
-.style3::-moz-color-swatch {
-    border-radius: 5px;
-    border: 1px solid rgb(119, 119, 119);
+.button-reset {
+  background-color: #EC6830;
 }
 
+.button-reset:hover {
+  background-color: #c75a2b;
+}
 
-
-.container {
-    display: flex;
-    width: 410px;
-    height: 50px;
+.button-cancel {
+  background-color: red;
 }
 
-.tercio {
-    flex-grow: 1;
-    flex-basis: 0;
-    margin-left: 5px;
-    margin-right: 5px;
+.button-cancel:hover {
+  background-color: rgb(204, 2, 2);
+}
 
+/* === IMÁGENES === */
+input[type="file"] {
+  display: none;
 }
 
-.gradient {
-    height: auto;
-    width: 70%;
+.img-preview {
+  height: 100px;
+  width: 250px;
+  object-fit: contain;
+  margin-top: 10px;
 }
 
-.custom-file-upload {
-  display: inline-flex;          /* Cambiado a flex para alinear texto + icono */
-  align-items: center;           /* Centrar verticalmente */
-  justify-content: center;       /* Centrar horizontalmente */
-  gap: 8px;                     /* Espacio entre texto e icono */
-  padding: 10px 20px;            /* Más padding horizontal para que no se vea apretado */
+.fila {
+  width: 410px;
+  height: 100px;
+}
+
+/* === BOTONES PERSONALIZADOS === */
+.custom-file-upload,
+.custom-file-change {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 10px 20px;
   border-radius: 6px;
-  background-color: #f1f3f4;
   color: #f1f3f4;
   font-weight: 500;
   font-size: 14px;
   cursor: pointer;
   transition: background-color 0.3s ease, border-color 0.3s ease;
-  width: auto;                  /* Ancho automático para que se ajuste al contenido */
-  min-width: 130px;             /* Ancho mínimo para que no quede muy pequeño */
-}
-.grid-container {
-    width: 410px;
-    display: grid;
-    gap: 8px;
-    grid-template-columns: auto auto auto auto auto;
-    padding: 10px;
-}
-
-.grid-item {
-    height: 65px;
-    width: 65px;
-    background-color: rgba(255, 255, 255, 0.8);
-    border: 1px solid rgba(146, 146, 146, 0.8);
-    text-align: center;
-    transition: transform 0.3s ease;
+  width: auto;
+  min-width: 130px;
 }
 
-.grid-item:hover {
-    transform: scale(1.1);
+.custom-file-upload {
+  background-color: #2196F3;
 }
 
-/* esto no se si va */
-.zoomed {
-    transform: scale(1.1);
-    box-shadow: -1px 1px 16px 0px rgb(0 0 0 / 74%);
+.custom-file-upload:hover {
+  background-color: #0086f3;
 }
 
-.button-cancel {
-    border-radius: 5px;
-    font-size: 16px;
-    padding: 8px 15px;
-    border: none;
-    background-color: red;
-    color: white;
-    cursor: pointer;
+.custom-file-change {
+  background-color: #EC6830;
 }
 
-.button-cancel:hover {
-
-    background-color: rgb(204, 2, 2);
-
+.custom-file-change:hover {
+  background-color: #c75a2b;
 }
 
-.button-save {
-    border-radius: 5px;
-    font-size: 16px;
-    padding: 8px 15px;
-    border: none;
-    background-color: #6699cd;
-    color: white;
-    cursor: pointer;
-}
-
-.button-save:hover {
-
-    background-color: #5581ac;
-
-}
-
-.button-reset {
-    border-radius: 5px;
-    font-size: 16px;
-    padding: 8px 15px;
-    border: none;
-    background-color: #EC6830;
-    color: white;
-    cursor: pointer;
+/* === TEXTURAS === */
+.grid-container {
+  width: 410px;
+  display: grid;
+  gap: 8px;
+  grid-template-columns: repeat(5, auto);
+  padding: 10px;
 }
 
-.button-reset:hover {
-
-    background-color: #c75a2b;
-
+.grid-item {
+  height: 65px;
+  width: 65px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border: 1px solid rgba(146, 146, 146, 0.8);
+  text-align: center;
+  transition: transform 0.3s ease;
 }
 
-
-.img-preview {
-    height: 100px;
-    width: 250px;
-    object-fit: contain;
+.grid-item:hover {
+  transform: scale(1.1);
 }
 
-.fila {
-    width: 410px;
-    height: 100px;
+.zoomed {
+  transform: scale(1.1);
+  box-shadow: -1px 1px 16px 0px rgb(0 0 0 / 74%);
 }
 
+/* Radio de textura con fondo */
 .image-container {
-    position: relative;
-    width: 68px;
-    height: 68px;
-    transition: transform .2s;
+  position: relative;
+  width: 68px;
+  height: 68px;
+  transition: transform .2s;
 }
 
 .image-container:hover {
-    position: relative;
-    width: 68px;
-    height: 68px;
-    transform: scale(1.1);
+  transform: scale(1.1);
 }
 
 .image {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background-size: cover;
-    background-position: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-size: cover;
+  background-position: center;
 }
 
 input[type="radio"] {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    opacity: 0;
-    cursor: pointer;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+  cursor: pointer;
 }
 
-
+/* === RESPONSIVE === */
 @media only screen and (max-width: 600px) {
+  .centrar {
+    width: 100%;
+    margin-top: 5%;
+    display: flex;
+    justify-content: center;
+  }
 
-    .centrar {
-        width: 100%;
-        margin-top: 5%;
-        display: flex;
-        justify-content: center;
-    }
-
-
-    .fondo {
-        width: 85%;
-    }
-
-    .content {
-        margin-bottom: 0;
-        margin-top: 0;
-        padding-top: 10px;
-        padding-bottom: 10px;
-        padding-left: 10px;
-        padding-right: 10px;
-        font-weight: 500;
-        font-size: 24px;
-        color: white;
-        font-family: "Serenity Medium";
-    }
-
-    .centrar-form {
-        margin-top: 50px;
-        display: flex;
-        flex-direction: column;
-
-    }
-
-    .fila-form {
-        display: flex;
-        flex-direction: column;
-
-        width: auto;
-    }
-
-    .text {
-        width: auto;
-        margin-left: 0;
-    }
-
-    .input {
-        width: auto;
-        margin-left: 0;
-    }
-
-    .showColor {
-        border-radius: 10px;
-        height: 30px;
-        width: 170px;
-    }
-
-    .container {
-        display: flex;
-        width: 180px;
-        height: 50px;
-    }
-
-    .centrar-buttons {
-        display: flex;
-        flex-direction: row;
-        justify-content: space-around;
-    }
-
-    .button-save {
-        border-radius: 5px;
-        font-size: 16px;
-        padding: 8px 15px;
-        border: none;
-        background-color: #6699cd;
-        color: white;
-        cursor: pointer;
-        width: auto;
-    }
-
-    .button-cancel {
-        border-radius: 5px;
-        font-size: 16px;
-        padding: 8px 15px;
-        border: none;
-        background-color: red;
-        color: white;
-        cursor: pointer;
-        width: auto;
-    }
-
-    .img-preview {
-        height: 100px;
-        width: 250px;
-        object-fit: contain;
-        margin-top: 10px;
-    }
-
-    .grid-container {
-        grid-template-columns: repeat(3, 1fr);
-        width: 90%;
-        display: grid;
-        gap: 5px;
-        margin-left: 40px;
-    }
+  .fondo {
+    width: 85%;
+  }
 
+  .content {
+    padding: 10px;
+    font-size: 24px;
+  }
 
+  .centrar-form {
+    margin-top: 50px;
+  }
+
+  .fila-form {
+    flex-direction: column;
+    width: auto;
+  }
+
+  .text {
+    width: auto;
+    margin-left: 0;
+  }
+
+  .input {
+    width: auto;
+    margin-left: 0;
+  }
+
+  .showColor {
+    width: 170px;
+  }
+
+  .container {
+    width: 180px;
+  }
+
+  .img-preview {
+    width: 250px;
+  }
+
+  .grid-container {
+    grid-template-columns: repeat(3, 1fr);
+    width: 90%;
+    gap: 5px;
+    margin-left: 40px;
+  }
 }

+ 23 - 19
Front/src/app/modules/Administrador/pages/personalizar/personalizar.component.html

@@ -3,11 +3,14 @@
     <p class="content" [style.color]="textColor">Personalizar</p>
   </div>
 </div>
-<div class="centrar-form fadeIn">
- <mat-spinner *ngIf="isLoading;"></mat-spinner>
- <div style="background-color: white; align-items: center;" class="divRes" >
-<mat-tab-group  mat-stretch-tabs="false" class="mat-elevation-z4" animationDuration="0ms">
-<mat-tab label="Interfaz Gráfica">
+<div class="centrar-form fadeIn" style="width: 100%; display: flex; flex-direction: column; align-items: center;">
+  <div *ngIf="isLoading" style="margin-bottom: 20px;">
+    <mat-spinner></mat-spinner>
+  </div>
+
+  <div class="tab-container">
+  <mat-tab-group mat-stretch-tabs="false" class="mat-elevation-z4" animationDuration="0ms">
+    <mat-tab label="Interfaz Gráfica">
 
   <form class="form-personalizar" *ngIf="!isLoading;" (ngSubmit)="personalizar()" [formGroup]="form">
     <div class="fila-form">
@@ -62,15 +65,15 @@
           <mat-icon>upload</mat-icon>
         </label>
 
-        <label class="custom-file-upload" *ngIf="!imagenGuardada && url">
+        <label class="custom-file-change" *ngIf="!imagenGuardada && url">
           <input type="file" accept="image/*" (change)="onFileChange($event, 'imagen')">Cambiar Archivo
           <mat-icon>upload</mat-icon>
         </label>
+<button type="button" class="custom-file-upload" *ngIf="imagenGuardada" (click)="eliminarImagen(imagenGuardada,'PERLOGO')" style="background-color: #FF5C5C;">
+  Eliminar Archivo
+  <mat-icon>delete</mat-icon>
+</button>
 
-        <button class="custom-file-upload" *ngIf="imagenGuardada" (click)="eliminarImagen('imagen')" style="background-color: #FF5C5C; ">
-          Eliminar Archivo
-          <mat-icon>delete</mat-icon>
-        </button>
       </div>
 
       <!-- Errores -->
@@ -276,12 +279,12 @@
           <mat-icon>upload</mat-icon>
         </label>
 
-        <label class="custom-file-upload" *ngIf="!imagenGuardada2 && url2">
+        <label class="custom-file-change" *ngIf="!imagenGuardada2 && url2">
           <input type="file" accept="image/*" (change)="onFileChange($event, 'imagen2')">Cambiar Archivo
           <mat-icon>upload</mat-icon>
         </label>
 
-        <button class="custom-file-upload" *ngIf="imagenGuardada2" (click)="eliminarImagen('imagen2')" style="background-color: #FF5C5C; ">
+        <button  type="button" class="custom-file-upload" *ngIf="imagenGuardada2" (click)="eliminarImagen(imagenGuardada2 ,'PERLOGO2')" style="background-color: #FF5C5C; ">
           Eliminar Archivo
           <mat-icon>delete</mat-icon>
         </button>
@@ -309,20 +312,21 @@
     <div class="flex-col">
       <!-- Botón según condición -->
       <div class="justify-end mt-10">
-        <label class="custom-file-upload" *ngIf="!imagenGuardada1 && !url1">
-          <input type="file" accept="image/*" (change)="onFileChange($event, 'imagen1')">Seleccionar Archivo
+        <label class="custom-file-upload" *ngIf="!imagenGuardada1 && !url1" >
+          <input type="file" accept="image/*" (change)="onFileChange($event, 'imagen1')" style="background-color: #2196F3;" >Seleccionar Archivo
           <mat-icon>upload</mat-icon>
         </label>
 
-        <label class="custom-file-upload" *ngIf="!imagenGuardada1 && url1">
+        <label class="custom-file-change" *ngIf="!imagenGuardada1 && url1">
           <input type="file" accept="image/*" (change)="onFileChange($event, 'imagen1')">Cambiar Archivo
           <mat-icon>upload</mat-icon>
         </label>
 
-        <button class="custom-file-upload" *ngIf="imagenGuardada1" (click)="eliminarImagen('imagen1')" style="background-color: #FF5C5C;">
-          Eliminar Archivo
-          <mat-icon>delete</mat-icon>
-        </button>
+        <button type="button" class="custom-file-upload" *ngIf="imagenGuardada1" (click)="eliminarImagen(imagenGuardada1,'PERLOGO1')" style="background-color: #FF5C5C;">
+  Eliminar Archivo
+  <mat-icon>delete</mat-icon>
+</button>
+
       </div>
 
       <!-- Errores -->

+ 102 - 32
Front/src/app/modules/Administrador/pages/personalizar/personalizar.component.ts

@@ -11,6 +11,9 @@ import { EnviarInfoService } from '../../services/enviar-info.service';
   styleUrl: './personalizar.component.css'
 })
 export class PersonalizarComponent implements OnInit {
+public imagenNueva: boolean = false;
+public imagenNueva1: boolean = false;
+public imagenNueva2: boolean = false;
 
   public url: string = '';
   public msg = "";
@@ -150,34 +153,88 @@ ngOnInit(): void {
     this.form.get('imagen2')?.setValue(this.imagenGuardada2);
   });
 }
+eliminarImagen(ruta: string, nombreCampo: 'PERLOGO' | 'PERLOGO1' | 'PERLOGO2') {
+  console.log('Ruta original:', ruta);
 
-eliminarImagen(field: 'imagen' | 'imagen1' | 'imagen2') {
-  if (field === 'imagen') {
-    this.imagenGuardada = '';
-    this.url = '';
-    this.form.get('imagen')?.setValue('');
-  }
-  if (field === 'imagen1') {
-    this.imagenGuardada1 = '';
-    this.url1 = '';
-    this.form.get('imagen1')?.setValue('');
-  } else if (field === 'imagen2') {
-    this.imagenGuardada2 = '';
-    this.url2 = '';
-    this.form.get('imagen2')?.setValue('');
-  }
+  // Extraer la ruta relativa dentro del bucket
+  // Ajusta esta parte si tu estructura cambia
+const basePath = 'https://s3.us-south.cloud-object-storage.appdomain.cloud/pledu-dv-fjcm/';
+let rutaRelativa = ruta;
+
+const index = ruta.indexOf(basePath);
+if (index !== -1) {
+  rutaRelativa = ruta.substring(index + basePath.length);
+}
+
+console.log('Ruta relativa enviada al backend:', rutaRelativa);
+
+  const nombreMostrado = {
+    PERLOGO: 'Logo Principal',
+    PERLOGO1: 'Logo Secundario 1',
+    PERLOGO2: 'Logo Secundario 2'
+  }[nombreCampo];
+
+  Swal.fire({
+    title: `¿Estás seguro?`,
+    text: `Se eliminará la imagen de ${nombreMostrado}.`,
+    icon: 'warning',
+    showCancelButton: true,
+    confirmButtonColor: '#d33',
+    cancelButtonColor: '#3085d6',
+    confirmButtonText: 'Eliminar',
+    cancelButtonText: 'Cancelar'
+  }).then((result) => {
+    if (result.isConfirmed) {
+      Swal.fire({
+        title: 'Espere un momento',
+        text: "Este proceso puede tardar unos segundos",
+        icon: 'warning',
+        showConfirmButton: false,
+        allowEscapeKey: false,
+        allowOutsideClick: false,
+        timer: 2000,
+        timerProgressBar: true
+      });
+
+      // Aquí usamos rutaRelativa
+      this._personalizarService.eliminarArchivo(rutaRelativa).subscribe({
+        next: () => {
+          if (nombreCampo === 'PERLOGO') {
+            this.url = '';
+            this.form.get('imagen')?.setValue('');
+            this.formData.PERLOGO = null;
+          } else if (nombreCampo === 'PERLOGO1') {
+            this.url1 = '';
+            this.form.get('imagen1')?.setValue('');
+            this.formData.PERLOGO1 = null;
+          } else if (nombreCampo === 'PERLOGO2') {
+            this.url2 = '';
+            this.form.get('imagen2')?.setValue('');
+            this.formData.PERLOGO2 = null;
+          }
+
+          Swal.fire({
+            title: 'Eliminado',
+            text: `La imagen de ${nombreMostrado} ha sido eliminada correctamente.`,
+            icon: 'success',
+            confirmButtonColor: '#4074b0',
+            confirmButtonText: 'De acuerdo'
+          });
+           window.location.reload();
+        },
+        error: () => {
+          Swal.fire({
+            title: 'Error',
+            text: `No se pudo eliminar la imagen.`,
+            icon: 'error'
+          });
+        }
+      });
+    }
+  });
 }
 
 
-  // onSelectFile(event: any) {
-  //   if (event.target.files && event.target.files[0]) {
-  //     const reader = new FileReader();
-  //     reader.readAsDataURL(event.target.files[0]);
-  //     reader.onload = (event) => {
-  //       this.url = event.target?.result;
-  //     };
-  //   }
-  // }
 
   // preview de la imagen en input type file y convertirla a base64
 onFileChange(event: any, field: 'imagen'|'imagen1' | 'imagen2') {
@@ -186,7 +243,6 @@ onFileChange(event: any, field: 'imagen'|'imagen1' | 'imagen2') {
 
   this.clearErrors();
 
-  // Validar tipo con regex para evitar errores por mayúsculas o variantes
   const mimeType = file.type.toLowerCase();
   const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
   if (!allowedTypes.includes(mimeType)) {
@@ -194,7 +250,6 @@ onFileChange(event: any, field: 'imagen'|'imagen1' | 'imagen2') {
     return;
   }
 
-  // Validar tamaño máximo 1MB
   const allowedSize = 1 * 1024 * 1024;
   if (file.size > allowedSize) {
     this.errorSize = "Máximo 1MB.";
@@ -207,11 +262,17 @@ onFileChange(event: any, field: 'imagen'|'imagen1' | 'imagen2') {
     if (typeof result === 'string') {
       const img = new Image();
       img.onload = () => {
-        // Setear el resultado base64 en el formulario y en la variable de url correspondiente
         this.form.get(field)?.setValue(result);
-        if (field === 'imagen') this.url = result;
-        else if (field === 'imagen1') this.url1 = result;
-        else if (field === 'imagen2') this.url2 = result;
+        if (field === 'imagen') {
+          this.url = result;
+          this.imagenNueva = true;
+        } else if (field === 'imagen1') {
+          this.url1 = result;
+          this.imagenNueva1 = true;
+        } else if (field === 'imagen2') {
+          this.url2 = result;
+          this.imagenNueva2 = true;
+        }
       };
       img.src = result;
     }
@@ -281,7 +342,10 @@ clearErrors() {
             title: '¡Configuración actualizada!',
             timer: 2000,
             showConfirmButton: false
+
           });
+           window.location.reload();
+
         },
         error: (err) => {
           Swal.fire({
@@ -289,7 +353,10 @@ clearErrors() {
             title: 'Ocurrió un error al guardar',
             text: err.message,
           });
+           window.location.reload();
+
         }
+
       });
     }
   });
@@ -318,9 +385,9 @@ clearErrors() {
             confirmButtonText: 'Ok',
           }).then((result: any) => {
             if (result.isConfirmed) {
-              location.reload();
+           window.location.reload();
             }
-          })
+          });
         }, (err) => {
           console.log(err.error.message);
           Swal.fire({
@@ -328,11 +395,14 @@ clearErrors() {
             title: 'Error al hacer la actualización',
             text: `${err.error.message}`
           })
+                     window.location.reload();
+
         })
       }
     })
 
   }
 
+
 }
 

+ 32 - 1
Front/src/app/modules/Administrador/services/personalizar.service.ts

@@ -2,13 +2,18 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 import { environments } from '../../../../environments/environments';
 import { Personalizar } from '../interfaces/Personalizar.interface';
+import { Observable, Subject } from 'rxjs';
+import { tap } from 'rxjs/operators';
 
 @Injectable({
   providedIn: 'root'
 })
 export class PersonalizarService {
   private URL: string = environments.baseUrl;
-  constructor(private http: HttpClient) { }
+  private dataUpdated = new Subject<void>(); // <-- Esto hacía falta
+
+  constructor(private http: HttpClient) {}
+
   private getHeaders(): HttpHeaders {
     const token = localStorage.getItem('token') || '';
     return new HttpHeaders({
@@ -16,6 +21,7 @@ export class PersonalizarService {
       'Authorization': `Bearer ${token}`
     });
   }
+
   getOptions() {
     return this.http.get(`${this.URL}/personalizarInfo`);
   }
@@ -23,4 +29,29 @@ export class PersonalizarService {
   personalizar(personalizar: Personalizar) {
     return this.http.put(`${this.URL}/personalizar`, personalizar, { headers: this.getHeaders() });
   }
+
+  eliminarArchivo(rutaArchivo: string): Observable<any> {
+    return this.http.post(`${this.URL}/eliminarPerlogo`, { rutaArchivo }, {
+      headers: this.getHeaders()
+    }).pipe(
+      tap(() => this.dataUpdated.next())
+    );
+  }
+
+  // Este método permite a otros componentes suscribirse a los cambios
+  onDataUpdated(): Observable<void> {
+    return this.dataUpdated.asObservable();
+  }
+
+   getPERLOGO1(): Observable<{ PERLOGO1: string | null }> {
+    return this.http.get<{ PERLOGO1: string | null }>(`${this.URL}/personalizar/perlogo1`, {
+      headers: this.getHeaders()
+    });
+  }
+
+  getPERLOGO2(): Observable<{ PERLOGO2: string | null }> {
+    return this.http.get<{ PERLOGO2: string | null }>(`${this.URL}/personalizar/perlogo2`, {
+      headers: this.getHeaders()
+    });
+  }
 }

+ 2 - 0
Front/src/app/modules/Padres/pages/Registro/registro.component.ts

@@ -956,6 +956,8 @@ opcionRequiereFaturaNO: boolean = false;
 
     constructor(private _enviarInfo: EnviarInfoService, private fb: FormBuilder, public dialog: MatDialog, private _registroAdministrativo: RegistroAcademicoService,
         private _router: Router, private _gradosEducativos: GradosEducativosService, private route: ActivatedRoute, private _registro: RegistroAcademicoService, private cdr: ChangeDetectorRef
+
+
     ) {
         this.formulario();
     }

+ 1 - 1
Front/src/app/modules/Padres/pages/registroAlumno/registroAlumno.component.css

@@ -32,4 +32,4 @@
   .centrar1 {
     margin-left: -50px;
   }
-}
+}

+ 73 - 2
Front/src/app/modules/Padres/pages/registroAlumno/registroAlumno.component.ts

@@ -8,6 +8,7 @@ import jsPDF from "jspdf";
 import { forkJoin, map } from 'rxjs';
 import { Router } from "@angular/router";
 import { dA } from "@fullcalendar/core/internal-common";
+import { PersonalizarService } from '../../../Administrador/services/personalizar.service';
 
 // @Injectable({
 //   providedIn: 'root'
@@ -36,12 +37,16 @@ export class registroAlumnoComponent implements OnInit {
   public errorEmpty: any[] = [];
   public errorEmptyAd: any[] = [];
   @ViewChild(MatPaginator) paginator!: MatPaginator;
+url1: string | null = '';
+url2: string | null = '';
+
   constructor(
     private _enviarInfo: EnviarInfoService,
     private paginator1: MatPaginatorIntl,
     private _relacionService: RelacionService,
     private _registroService: RegistroAcademicoService,
     private router: Router,
+    private personalizarService : PersonalizarService,
   ) {
     const spanishRangeLabel = (page: number, pageSize: number, length: number): string => {
       if (length === 0 || pageSize === 0) {
@@ -74,7 +79,17 @@ export class registroAlumnoComponent implements OnInit {
 
   this._enviarInfo.currentMensaje.subscribe(mensajeBienvenida => {
     this.mensajeBienvenida = mensajeBienvenida;
+
+  this.personalizarService.getPERLOGO1().subscribe(resp => {
+    this.url1 = resp.PERLOGO1;
+  });
+
+  this.personalizarService.getPERLOGO2().subscribe(resp => {
+    this.url2 = resp.PERLOGO2;
   });
+  });
+
+
 
 
   this._enviarInfo.getNombreColegio().subscribe({
@@ -168,6 +183,35 @@ getAll() {
   nombre(data: any, tipo: string) {
     tipo === 'ac' ? this.router.navigate(['/homePadres/registro', btoa(data)]) : this.router.navigate(['/homePadres/registroAd', btoa(data)]);
   }
+convertImageToBase64(url: string): Promise<string | null> {
+  return new Promise((resolve, reject) => {
+   if (!url) {
+      resolve(''); // Cadena vacía en vez de null
+      return;
+    }
+    const img = new Image();
+    img.crossOrigin = 'anonymous'; // importante para evitar problemas de CORS
+    img.onload = () => {
+      const canvas = document.createElement('canvas');
+      canvas.width = img.width;
+      canvas.height = img.height;
+      const ctx = canvas.getContext('2d');
+      if (!ctx) {
+        resolve(null);
+        return;
+      }
+      ctx.drawImage(img, 0, 0);
+      const dataURL = canvas.toDataURL('image/png'); // obtiene base64 con prefijo completo
+      resolve(dataURL);
+    };
+    img.onerror = (error) => {
+      console.error('Error cargando imagen:', url, error);
+      resolve(null); // Para que no falle toda la promesa si una imagen no carga
+    };
+    img.src = url;
+  });
+}
+
 descargarPDF(id: string) {
 
   forkJoin({
@@ -185,13 +229,28 @@ descargarPDF(id: string) {
     // doc.addImage("assets/img/Encabezado-PDF.jpg", "JPEG", 6, 10, pageWidth - 8, 27);
 
  // 1. Título principal: Nombre del colegio
+// 0. Cargar las imágenes antes de usarlas
+Promise.all([
+  this.convertImageToBase64(this.url1 ?? ''),
+  this.convertImageToBase64(this.url2?? ''),
+]).then(([imgLeft, imgRight]) => {
+  if (imgLeft) {
+    doc.addImage(imgLeft, 'PNG', 15, 10, 30, 30);
+  }
+  if (imgRight) {
+    doc.addImage(imgRight, 'PNG', pageWidth - 45, 10, 30, 30);
+  }
+
+
+
+// 1. Título (Nombre del colegio)
 doc.setFontSize(14);
 doc.setFont('helvetica', 'bold');
 doc.text(this.nombreColegio, pageWidth / 2, 20, { align: 'center' });
 
-// 2. Línea horizontal debajo del nombre
+// 2. Línea horizontal
 doc.setLineWidth(0.5);
-doc.line(10, 24, pageWidth - 10, 24); // línea justo debajo del nombre
+doc.line(10, 24, pageWidth - 10, 24);
 
 // 3. Eslogan
 doc.setFontSize(12);
@@ -203,6 +262,13 @@ doc.setFontSize(12);
 doc.setFont('helvetica', 'normal');
 doc.text(this.mensajeBienvenida, pageWidth / 2, 38, { align: 'center' });
 
+// 5. Agregar los logos (a los lados del encabezado)
+const logoWidth = 30;
+const logoHeight = 30;
+
+// Logo izquierdo (PERLOGO1)
+
+
 
     doc.setFont("helvetica", "italic");
     doc.setFontSize(10);
@@ -353,6 +419,7 @@ doc.text(this.mensajeBienvenida, pageWidth / 2, 38, { align: 'center' });
     doc.text(`Página 1 de 1`, pageWidth - 30, pageHeight - 10);
 
 
+
     // Añadir nueva página para firmas y leyendas
     doc.addPage();
     doc.addImage("assets/img/Encabezado-PDF.jpg", "JPEG", 6, 10, pageWidth - 8, 27);
@@ -719,5 +786,9 @@ doc.text(this.mensajeBienvenida, pageWidth / 2, 38, { align: 'center' });
       // Descarga el PDF
       doc.save(`RegistroAlumno_${data.idAlumno}.pdf`);
     })
+    });
   }
+
 }
+
+

+ 3 - 0
Front/src/app/modules/Padres/services/registroAcademico.service.ts

@@ -97,4 +97,7 @@ eliminarArchivo(rutaArchivo: string): Observable<any> {
     return this.http.get(`${this.URL}/UserOne/${qId}`, { headers: this.getHeaders() });
   }
 
+
+
+
 }