Procházet zdrojové kódy

Actualizo, solo me falta el diseño , y editar el fromulario con respuestas

FREDY před 3 měsíci
rodič
revize
1e0fd57007

+ 69 - 1
Back/backendP-Educativa/app/Http/Controllers/RespuestasController.php

@@ -10,6 +10,8 @@ use Illuminate\Http\Request;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Validation\ValidationException;
 use Illuminate\Support\Facades\DB;
+use Barryvdh\DomPDF\Facade\Pdf;
+use Illuminate\Support\Facades\Log;  // <-- Importa Log
 class RespuestasController extends Controller
 {
 
@@ -18,10 +20,11 @@ public function recibirRespuesta(Request $request)
     try {
         $validated = $request->validate([
             'form_id' => 'required|integer|exists:forms,id',
+            'usuarioRegistro' => 'required|string|exists:usuarios,idUsuario', // 👈 validación correcta para varchar
             'contenido_json' => 'required|json',
         ]);
 
-        $usuarioLogueado = auth()->user()->idUsuario;
+        $usuarioLogueado = $validated['usuarioRegistro'];
 
         $formId = DB::table('respuestas_formulario')->insertGetId([
             'form_id' => $validated['form_id'],
@@ -44,6 +47,71 @@ public function recibirRespuesta(Request $request)
     }
 }
 
+public function tieneRespuesta(Request $request)
+{
+    $request->validate([
+        'form_id' => 'required|integer|exists:forms,id',
+        'usuario_id' => 'required|string',
+    ]);
+
+    $formId = $request->input('form_id');
+    $usuarioId = $request->input('usuario_id');
+
+    $existe = DB::table('respuestas_formulario')
+        ->where('form_id', $formId)
+        ->where('id_usuario', $usuarioId)
+        ->exists();
+
+    return response()->json([
+        'success' => true,
+        'hasRespuesta' => $existe
+    ]);
+}
+
+ public function generarPDF($id_usuario)
+{
+    try {
+        $respuesta = DB::table('respuestas_formulario')
+            ->where('id_usuario', $id_usuario)
+            ->first();
+
+        if (!$respuesta) {
+            return response()->json(['error' => 'No hay respuesta registrada'], 404);
+        }
+
+        $contenido = json_decode($respuesta->contenido_json, true);
+        $tabs = $contenido['tabs'];
+
+        // Formatear las claves
+        $tabsFormateados = [];
+        foreach ($tabs as $tituloTab => $campos) {
+            $camposFormateados = [];
+            foreach ($campos as $clave => $valor) {
+                $claveFormateada = ucwords(str_replace('_', ' ', $clave));
+                $camposFormateados[$claveFormateada] = $valor;
+            }
+            $tabsFormateados[$tituloTab] = $camposFormateados;
+        }
+
+        $pdf = PDF::loadView('respuesta', [
+            'respuesta' => $tabsFormateados,
+            'usuario' => $respuesta->id_usuario,
+            'fecha' => $respuesta->created_at
+        ]);
+
+        return $pdf->download("respuesta_{$id_usuario}.pdf");
+
+    } catch (\Exception $e) {
+        Log::error('Error generando PDF para usuario ' . $id_usuario . ': ' . $e->getMessage(), [
+            'exception' => $e,
+        ]);
+
+        return response()->json([
+            'error' => 'Error al generar el PDF',
+            'message' => $e->getMessage()
+        ], 500);
+    }
+}
 
 
 

+ 1 - 0
Back/backendP-Educativa/composer.json

@@ -6,6 +6,7 @@
     "license": "MIT",
     "require": {
         "php": "^8.1",
+        "barryvdh/laravel-dompdf": "^3.1",
         "guzzlehttp/guzzle": "^7.2",
         "laravel/framework": "^10.10",
         "laravel/sanctum": "^3.3",

+ 366 - 1
Back/backendP-Educativa/composer.lock

@@ -4,8 +4,85 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "4e543c7e6b57212aa291590199ce9cbf",
+    "content-hash": "4c8ef6520f1415391848bc2dccc6e54e",
     "packages": [
+        {
+            "name": "barryvdh/laravel-dompdf",
+            "version": "v3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barryvdh/laravel-dompdf.git",
+                "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
+                "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
+                "shasum": ""
+            },
+            "require": {
+                "dompdf/dompdf": "^3.0",
+                "illuminate/support": "^9|^10|^11|^12",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "larastan/larastan": "^2.7|^3.0",
+                "orchestra/testbench": "^7|^8|^9|^10",
+                "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.1"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-13T15:07:54+00:00"
+        },
         {
             "name": "brick/math",
             "version": "0.12.1",
@@ -457,6 +534,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.3.3",
@@ -2209,6 +2441,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": "monolog/monolog",
             "version": "3.7.0",
@@ -3606,6 +3905,72 @@
             ],
             "time": "2024-04-27T21:32:50+00:00"
         },
+        {
+            "name": "sabberworm/php-css-parser",
+            "version": "v8.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
+                "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9",
+                "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9",
+                "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.41",
+                "rawr/cross-data-providers": "^2.0.0"
+            },
+            "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.9.0"
+            },
+            "time": "2025-07-11T13:20:48+00:00"
+        },
         {
             "name": "stechstudio/laravel-jwt",
             "version": "1.12",

+ 69 - 0
Back/backendP-Educativa/resources/views/respuesta.blade.php

@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Respuesta del Usuario</title>
+    <style>
+        body {
+            font-family: 'Segoe UI', sans-serif;
+            font-size: 13px;
+            margin: 40px;
+            color: #2c3e50;
+        }
+
+        h1 {
+            font-size: 20px;
+            margin-bottom: 5px;
+        }
+
+        p {
+            margin: 0 0 15px;
+        }
+
+        h2 {
+            margin-top: 40px;
+            font-size: 16px;
+            color: #2c3e50;
+            border-bottom: 2px solid #3498db;
+            padding-bottom: 4px;
+        }
+
+        .campo {
+            margin-bottom: 8px;
+        }
+
+        .campo span {
+            display: inline-block;
+            min-width: 200px;
+            font-weight: bold;
+        }
+
+        .footer {
+            margin-top: 60px;
+            font-size: 11px;
+            text-align: center;
+            color: #999;
+        }
+    </style>
+</head>
+<body>
+
+    <h1>Formulario contestado por el usuario: {{ $usuario }}</h1>
+    <p><strong>Fecha de respuesta:</strong> {{ \Carbon\Carbon::parse($fecha)->format('d/m/Y H:i') }}</p>
+
+    @foreach ($respuesta as $titulo => $campos)
+        <h2>{{ $titulo }}</h2>
+
+        @foreach ($campos as $clave => $valor)
+            <div class="campo">
+                <span>{{ $clave }}:</span> {{ $valor }}
+            </div>
+        @endforeach
+    @endforeach
+
+    <div class="footer">
+        Documento generado automáticamente. Plataforma educativa - {{ date('Y') }}.
+    </div>
+
+</body>
+</html>

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

@@ -260,6 +260,7 @@ Route::get('/formularios/publicado', [FormController::class, 'getPublicado']);
 
 //Respuestas de Formularios creados
     Route::post('/recibirRespuesta', [RespuestasController::class, 'recibirRespuesta']);
+Route::get('/generar-pdf/{id_usuario}', [RespuestasController::class, 'generarPDF']);
 
 
 });

+ 3 - 3
Front/src/app/auth/services/auth.service.ts

@@ -31,16 +31,16 @@ export class AuthService {
     return this.http.post(`${this.URL}/login`, auth)
   }
 
-  
+
   _isLoggedIn(): boolean {
     const token = localStorage.getItem('token');
     return token != null;
   }
-  
+
   verifyToken(idUsuario: Object) {
     return this.http.post(`${this.URL}/verifyTokens`, idUsuario);
   }
-  
+
   sendMail(email:any){
     return this.http.post(`${this.URL}/send-test-email`, email)
   }

+ 0 - 1
Front/src/app/modules/Administrador/pages/admin-form/form-selector/form-selector.component.ts

@@ -33,7 +33,6 @@ export class FormSelectorComponent implements OnInit {
 displayedColumns: string[] = ['id', 'title', 'ver', 'editar', 'publicar', 'eliminar'];
   selectedFormId: number | null = null;
   isLoading = false;
-
   @ViewChild(MatPaginator) paginator!: MatPaginator;
   @ViewChild(MatSort) sort!: MatSort;
 

+ 2 - 0
Front/src/app/modules/Padres/padres-routing.module.ts

@@ -9,6 +9,7 @@ import { registroComponent, RegistroAdComponent } from './pages/Registro/registr
 import { CircularHijosComponent } from './pages/circularHijos/circular-hijos.component';
 import { BitacoraHijosComponent } from './pages/bitacora-hijos/bitacora-hijos.component';
 import { FormulariosComponent } from './pages/formularios/formularios.component';
+import { ListaFormulariosComponent } from './pages/lista-formularios/lista-formularios.component';
 
 
 
@@ -25,6 +26,7 @@ const routes: Routes = [
             { path: 'circularHijo/:id', component: CircularHijosComponent },
             { path: 'bitacoraHijo/:id', component: BitacoraHijosComponent },
             { path: 'formularios', component: FormulariosComponent },
+          { path: 'formularios/lista', component: ListaFormulariosComponent },
 
 
             { path: '', redirectTo: 'registroAlumno', pathMatch: 'full' },

+ 2 - 0
Front/src/app/modules/Padres/padres.module.ts

@@ -15,6 +15,7 @@ import { RegistroAdComponent, registroComponent } from './pages/Registro/registr
 import { CircularHijosComponent } from './pages/circularHijos/circular-hijos.component';
 import { BitacoraHijosComponent } from './pages/bitacora-hijos/bitacora-hijos.component';
 import { FormulariosComponent } from './pages/formularios/formularios.component';
+import { ListaFormulariosComponent } from './pages/lista-formularios/lista-formularios.component';
 
 
 
@@ -31,6 +32,7 @@ import { FormulariosComponent } from './pages/formularios/formularios.component'
     CircularHijosComponent,
     BitacoraHijosComponent,
     FormulariosComponent,
+    ListaFormulariosComponent,
   ],
   imports: [
     CommonModule,

+ 7 - 1
Front/src/app/modules/Padres/pages/formularios/formularios.component.html

@@ -1,9 +1,13 @@
-<form [formGroup]="form" (ngSubmit)="onSubmit()" *ngIf="form" class="containerPagina">
+
+
+<form [formGroup]="form" (ngSubmit)="onSubmit()" class="containerPagina">
 
   <mat-tab-group [(selectedIndex)]="selectedTabIndex">
+
     <mat-tab *ngFor="let tab of configuration.tabs" [label]="tab.title">
 
       <div class="grid" [style.gridTemplateColumns]="'repeat(' + tab.columns + ', 1fr)'" style="gap: 16px; padding: 16px;">
+
         <div *ngFor="let el of tab.elements" class="form-element"
              [style.gridColumnStart]="el.position.col + 1"
              [style.gridRowStart]="el.position.row + 1">
@@ -46,9 +50,11 @@
           </div>
 
         </div>
+
       </div>
 
     </mat-tab>
+
   </mat-tab-group>
 
   <div class="button-container" style="margin-top: 16px;">

+ 104 - 52
Front/src/app/modules/Padres/pages/formularios/formularios.component.ts

@@ -1,4 +1,3 @@
-import { Usuario } from './../../../Administrador/interfaces/Usuario.interface';
 import { Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { FormService } from './../../../Administrador/services/FormService.service';
@@ -11,42 +10,45 @@ import { recibirRespuestas } from '../../services/recibirRespuestas.service';
 })
 export class FormulariosComponent implements OnInit {
 
- Padre = JSON.parse(
-  localStorage.getItem('userDataS')
-    ? localStorage.getItem('userDataS') || ''
-    : localStorage.getItem('userData') || ''
-);
+  Padre = (() => {
+    const simulacion = localStorage.getItem('userDataS');
+    const real = localStorage.getItem('userData');
+    try {
+      return simulacion ? JSON.parse(simulacion) : real ? JSON.parse(real) : null;
+    } catch (e) {
+      console.error('Error al parsear usuario del localStorage:', e);
+      return null;
+    }
+  })();
 
-usuarioLogueadoId = this.Padre ? this.Padre[0]: null;
+  usuarioLogueadoId = this.Padre ? this.Padre[0] : null;
 
   form!: FormGroup;
   configuration: any;
-  selectedTabIndex = 0;
+   selectedTabIndex = 0;
   formId: any;
+  yaRespondido: boolean = false;
+  respuestasPrevias: any = null;
 
   constructor(
     private formService: FormService,
     private fb: FormBuilder,
-    private _recibirRespuestas: recibirRespuestas,
-
+    private _recibirRespuestas: recibirRespuestas
   ) {}
 
   ngOnInit(): void {
     this.obtenerFormularioPublicado();
-
-console.log('Padre:', this.usuarioLogueadoId);
-
+    console.log('Padre:', this.Padre);
+    console.log('usuarioLogueadoId:', this.usuarioLogueadoId);
   }
 
   obtenerFormularioPublicado(): void {
     this.formService.getPublishedForm().subscribe({
       next: (response: any) => {
         if (response.success && response.form) {
- this.formId = response.form.id;
-
+          this.formId = response.form.id;
           this.configuration = response.form.configuration;
 
-          // Si configuration viene como string JSON, parsea a objeto
           if (typeof this.configuration === 'string') {
             try {
               this.configuration = JSON.parse(this.configuration);
@@ -56,7 +58,39 @@ console.log('Padre:', this.usuarioLogueadoId);
             }
           }
 
-          this.reconstruirFormulario(this.configuration);
+          // Verificar si el usuario ya respondió
+          this._recibirRespuestas.tieneRespuesta(this.formId, this.usuarioLogueadoId).subscribe({
+            next: (res: any) => {
+              if (res.success && res.hasRespuesta) {
+                this.yaRespondido = true;
+
+                // Obtener respuestas previas para cargar en el formulario
+                this._recibirRespuestas.obtenerRespuesta(this.formId, this.usuarioLogueadoId).subscribe({
+                  next: (respuestaData: any) => {
+                    if (respuestaData.success && respuestaData.respuesta) {
+                      this.respuestasPrevias = JSON.parse(respuestaData.respuesta.contenido_json);
+                      this.reconstruirFormulario(this.configuration, this.respuestasPrevias);
+                    } else {
+                      this.reconstruirFormulario(this.configuration); // Si no hay datos, form vacío
+                    }
+                  },
+                  error: (err: any) => {
+                    console.error('Error al obtener respuesta previa:', err);
+                    this.reconstruirFormulario(this.configuration); // En caso de error cargar formulario vacío
+                  }
+                });
+
+              } else {
+                this.yaRespondido = false;
+                this.reconstruirFormulario(this.configuration);
+              }
+            },
+            error: (err) => {
+              console.error('Error verificando respuestas:', err);
+              this.yaRespondido = false;
+              this.reconstruirFormulario(this.configuration);
+            }
+          });
         } else {
           console.log('No se encontró formulario publicado');
         }
@@ -67,7 +101,7 @@ console.log('Padre:', this.usuarioLogueadoId);
     });
   }
 
-  reconstruirFormulario(config: any): void {
+  reconstruirFormulario(config: any, datosPrevios: any = null): void {
     if (!config || !config.tabs) {
       console.error('Configuración inválida o vacía');
       return;
@@ -79,7 +113,8 @@ console.log('Padre:', this.usuarioLogueadoId);
       tab.elements.forEach((el: any) => {
         const controlName = el.element.name;
         const validators = el.element.required ? [Validators.required] : [];
-        group[controlName] = this.fb.control('', validators);
+        const valorInicial = datosPrevios?.tabs?.[tab.title]?.[controlName] || '';
+        group[controlName] = this.fb.control(valorInicial, validators);
       });
     });
 
@@ -87,42 +122,59 @@ console.log('Padre:', this.usuarioLogueadoId);
   }
 
   onSubmit(): void {
-    if (this.form.valid) {
-      console.log('Formulario enviado con datos:', this.form.value);
-
-     const dataToSend = {
-  form_id: this.formId,
-  usuarioRegistro: this.usuarioLogueadoId, // acá va el id del usuario
-  contenido_json: JSON.stringify(this.form.value)
-};
-
-
-   this._recibirRespuestas.recibirRespuesta(dataToSend).subscribe({
-  next: (response: any) => {  // <-- usa any para que TS no te marque error
-    if (response.success) {
-      console.log('Respuesta guardada con éxito');
-    } else {
-      console.error('Error guardando respuesta:');
-    }
-  },
-  error: (err) => {
-    console.error('Error en el envío:', err);
-  }
-});
+    if (!this.form) return;
+
+    if (this.form.invalid) {
+      console.warn('Formulario inválido');
+      return;
     }
+
+    console.log('Formulario enviado con datos:', this.form.value);
+
+    const respuestasPorSeccion: any = {};
+
+    this.configuration.tabs.forEach((tab: any) => {
+      respuestasPorSeccion[tab.title] = {};
+      tab.elements.forEach((el: any) => {
+        const campo = el.element.name;
+        respuestasPorSeccion[tab.title][campo] = this.form.value[campo];
+      });
+    });
+
+    const dataToSend = {
+      form_id: this.formId,
+      usuarioRegistro: this.usuarioLogueadoId,
+      contenido_json: JSON.stringify({ tabs: respuestasPorSeccion })
+    };
+
+    this._recibirRespuestas.recibirRespuesta(dataToSend).subscribe({
+      next: (response: any) => {
+        if (response.success) {
+          console.log('Respuesta guardada con éxito');
+          this.yaRespondido = true;
+        } else {
+          console.error('Error guardando respuesta');
+        }
+      },
+      error: (err) => {
+        console.error('Error en el envío:', err);
+      }
+    });
   }
-}
 
-// onSubmit(): void {
-//   if (this.form.valid) {
-//     console.log('Formulario enviado con datos:', this.form.value);
+  descargarPDF() {
+    const usuarioId = this.usuarioLogueadoId || 'desconocido';
 
-//     // Armar el payload que enviarás al backend
-//     const dataToSend = {
-//       form_id: this.formId,        // el id del formulario
-//       usuarioRegistro: this.usuarioRegistro, // por ejemplo, el id del usuario logueado, si lo tienes
-//       contenido_json: JSON.stringify(this.form.value) // enviar el contenido como JSON string
-//     };
+    this._recibirRespuestas.descargarPDF(usuarioId).subscribe((res) => {
+      const blob = new Blob([res], { type: 'application/pdf' });
+      const url = window.URL.createObjectURL(blob);
 
-//     // Aquí llamas al servicio para enviar los datos al backend
+      const a = document.createElement('a');
+      a.href = url;
+      a.download = `respuesta_${usuarioId}.pdf`;
+      a.click();
 
+      window.URL.revokeObjectURL(url);
+    });
+  }
+}

+ 11 - 0
Front/src/app/modules/Padres/pages/lista-formularios/lista-formularios.component.css

@@ -0,0 +1,11 @@
+.centrar {
+  margin-top: 4%;
+  text-align: center;
+  width: 100%;
+ padding: 40px;
+
+}
+.tabla-container{
+  width: 95%;
+  margin-left: 40px;
+}

+ 57 - 0
Front/src/app/modules/Padres/pages/lista-formularios/lista-formularios.component.html

@@ -0,0 +1,57 @@
+<div class="centrar">
+  <div class="fondo" [style.background-color]="color">
+    <p class="content" [style.color]="'white'">Formularios Publicados</p>
+  </div>
+</div>
+
+  <div class="tabla-container" *ngIf="formularios.length > 0; else sinFormularios">
+  <table mat-table [dataSource]="formularios" class="mat-elevation-z8" >
+
+    <!-- Columna ID -->
+    <ng-container matColumnDef="id">
+      <th mat-header-cell *matHeaderCellDef>ID</th>
+      <td mat-cell *matCellDef="let form">{{ form.id }}</td>
+    </ng-container>
+
+    <!-- Columna Título -->
+    <ng-container matColumnDef="titulo">
+      <th mat-header-cell *matHeaderCellDef>Título</th>
+      <td mat-cell *matCellDef="let form">{{ form.title }}</td>
+    </ng-container>
+
+    <!-- Columna Acción -->
+    <ng-container matColumnDef="accion">
+      <th mat-header-cell *matHeaderCellDef>Acciones</th>
+      <td mat-cell *matCellDef="let form">
+        <!-- Si ya respondió -->
+        <ng-container *ngIf="form.respondido; else btnContestar">
+          <button mat-icon-button (click)="descargarPDF(form.usuarioId)" matTooltip="Descargar PDF">
+            <mat-icon color="warn">picture_as_pdf</mat-icon>
+          </button>
+
+          <button mat-icon-button (click)="editarFormulario(form.id)" matTooltip="Editar Respuesta">
+            <mat-icon color="primary">edit</mat-icon>
+          </button>
+        </ng-container>
+
+        <!-- Si aún no respondió -->
+        <ng-template #btnContestar>
+          <button mat-icon-button (click)="contestarFormulario()" matTooltip="Contestar Formulario">
+            <mat-icon color="accent">edit_note</mat-icon>
+          </button>
+        </ng-template>
+      </td>
+    </ng-container>
+
+    <!-- Encabezados y filas -->
+    <tr mat-header-row *matHeaderRowDef="columnas"></tr>
+    <tr mat-row *matRowDef="let row; columns: columnas;"></tr>
+  </table>
+</div>
+
+<!-- Mensaje si no hay formularios -->
+<ng-template #sinFormularios>
+  <p style="text-align: center; color: gray;">No hay formularios publicados disponibles.</p>
+</ng-template>
+
+

+ 23 - 0
Front/src/app/modules/Padres/pages/lista-formularios/lista-formularios.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ListaFormulariosComponent } from './lista-formularios.component';
+
+describe('ListaFormulariosComponent', () => {
+  let component: ListaFormulariosComponent;
+  let fixture: ComponentFixture<ListaFormulariosComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ListaFormulariosComponent]
+    })
+    .compileComponents();
+    
+    fixture = TestBed.createComponent(ListaFormulariosComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 110 - 0
Front/src/app/modules/Padres/pages/lista-formularios/lista-formularios.component.ts

@@ -0,0 +1,110 @@
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+import { recibirRespuestas } from './../../services/recibirRespuestas.service';
+import { FormService } from './../../../Administrador/services/FormService.service';
+import { EnviarInfoService } from '../../../Administrador/services/enviar-info.service';
+
+@Component({
+  selector: 'app-lista-formularios',
+  templateUrl: './lista-formularios.component.html',
+  styleUrls: ['./lista-formularios.component.css']
+})
+export class ListaFormulariosComponent implements OnInit {
+   color: string = '';
+  textColor: string = '';
+  formTitle: string = '';
+  isLoading = false;
+  formularios: any[] = [];
+  columnas: string[] = ['id', 'titulo', 'accion'];
+
+  Padre = (() => {
+    const simulacion = localStorage.getItem('userDataS');
+    const real = localStorage.getItem('userData');
+    try {
+      return simulacion ? JSON.parse(simulacion) : real ? JSON.parse(real) : null;
+    } catch (e) {
+      console.error('Error al parsear usuario del localStorage:', e);
+      return null;
+    }
+  })();
+
+  usuarioLogueadoId = this.Padre ? this.Padre[0] : null;
+  dataSource: any;
+
+  constructor(
+    private _recibirRespuestas: recibirRespuestas,
+    private router: Router,
+    private formService: FormService,
+   private _enviarInfo: EnviarInfoService
+  ) {}
+
+  ngOnInit() {
+    this.obtenerFormulariosPublicados();
+ this._enviarInfo.currentTextColor.subscribe(color => this.textColor = color);
+    this._enviarInfo.currentColor.subscribe(color => this.color = color);  }
+
+  obtenerFormulariosPublicados(): void {
+    this.formService.getPublishedForm().subscribe({
+      next: (response: any) => {
+        if (response.success && response.form) {
+          this.formularios = [
+            {
+              id: response.form.id,
+              title: response.form.title || 'Formulario publicado',
+              respondido: false,
+              usuarioId: this.usuarioLogueadoId
+            }
+          ];
+
+          // Verificar si el usuario ya respondió
+          this._recibirRespuestas.tieneRespuesta(response.form.id, this.usuarioLogueadoId).subscribe({
+            next: (res: any) => {
+              if (res.success && res.hasRespuesta) {
+                this.formularios[0].respondido = true;
+              }
+            },
+            error: (err) => {
+              console.error('Error verificando respuestas:', err);
+            }
+          });
+        } else {
+          console.log('No se encontró formulario publicado');
+        }
+      },
+      error: (err) => {
+        console.error('Error al obtener el formulario:', err);
+      }
+    });
+  }
+
+  contestarFormulario() {
+    this.router.navigate(['homePadres/formularios']);
+  }
+
+ descargarPDF(usuarioId: string) {
+  this._recibirRespuestas.descargarPDF(usuarioId).subscribe((res) => {
+    const blob = new Blob([res], { type: 'application/pdf' });
+    const url = window.URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = `respuesta_${usuarioId}.pdf`;
+    a.click();
+    window.URL.revokeObjectURL(url);
+  });
+}
+editarFormulario(formId: number) {
+  this.router.navigate(['homePadres/formularios'], {
+    queryParams: {
+      editar: true,
+      formId: formId
+    }
+  });
+}
+
+applyFilter(event: any): void {
+    const filterValue = (event.target.value || '').trim().toLowerCase();
+    this.dataSource.filter = filterValue;
+  }
+
+
+}

+ 15 - 0
Front/src/app/modules/Padres/services/recibirRespuestas.service.ts

@@ -21,4 +21,19 @@ export class recibirRespuestas {
     recibirRespuesta(data: any) {
       return this.http.post(`${this.URL}/recibirRespuesta`, data,  { headers: this.getHeaders() });
     }
+     tieneRespuesta(formId: number, usuarioId: number) {
+    return this.http.get(`${this.URL}/respuestas/check?form_id=${formId}&usuario_id=${usuarioId}`,{ headers: this.getHeaders() });
+  }
+ descargarPDF(usuarioId: string) {
+  return this.http.get(`${this.URL}/generar-pdf/${usuarioId}`, {
+    headers: this.getHeaders(),
+    responseType: 'blob'
+  });
 }
+  obtenerRespuesta(formId: number, usuarioId: string) {
+    return this.http.get<any>(`${this.URL}/respuestas/obtener?formId=${formId}&usuarioId=${usuarioId}`,{ headers: this.getHeaders() });
+  }
+  }
+
+
+

+ 1 - 1
Front/src/app/shared/sidebar/sidebar.component.ts

@@ -86,7 +86,7 @@ export class SidebarComponent {
     { icon: "account_circle", text: "Mi cuenta", url: "main" },
     { icon: "calendar_month", text: "Calendario", url: "main" },
     { icon: "tune", text: "Registro del Alumno", url: "registroAlumno" },
-            { icon: "tune", text: "formularios", url: "formularios" },
+            { icon: "tune", text: "formularios", url: "formularios/lista" },
 
 
   ]