Impresos y formularios

Gestión de Impresos / Formularios

Muchas Administraciones Públicas y muchas empresas importantes, disponen de sistemas antiguos para facilitar que en sus portales, los ciudadanos o personal propio, inicien un proceso a través de los datos rellenados en una solicitud en formato PDF.

Se ha pagado importantes sumas de dinero a Adobe, para hacer que los mismos documentos PDF’s fuesen los formularios de entrada de los datos requeridos y su validación.

Estas plataformas, muy costosas, han dejado de ser operativas porque el dispositivo más usado por los Ciudadanos ha dejado de ser el PC y se ha extendido el uso del móvil.

No obstante, se usan documentos PDF’s para recepcionar las solicitudes y para emitir las resoluciones, dado que incorporan las firmas electrónicas de forma muy eficiente y las leyes y normativas internacionales y nacionales, los admiten como documentos fehacientes, si sus firmas son emitidas por Entidades Reconocidas.

Así pues, la propuesta es conservar los documentos PDF, pero no así utilizarlos para (formularios) para la captura de sus datos.

Para entender perfectamente la solución técnica propuesta les sugiero que previamente lean el artículo que explica cómo obtener los puntos de ubicación de los campos en el documento PDF .

Ventajas de usar formularios en PDF:

  • Tanto para el Ciudadano como para los Empleados, lo que se cambia es la fotocopia del impreso a otra imagen igual, que sirve para rellenar los datos a mano o a través del navegador. No tiene costes de adaptación.

Desventajas de usar formularios en PDF:

  • Las plataformas para usar documentos PDF como formularios son muy caras y muy costoso de desarrollar las validaciones de los formularios.
  • Para usar en dispositivos de pantallas pequeñas (principalmente móviles) no es una solución adecuada.

La solución PHPRunner es una herramienta muy potente para hacer formularios, puede utilizarse en PC y en dispositivos móviles, con el mismo desarrollo y las validaciones y controles que requieren los formularios se pueden implementar muy rápidamente. Por el contrario, los documentos PDF que proporciona son simples y no se ajustan a los impresos que se utilizan en las empresas, que por otra parte, deben seguir ofreciendo el PDF para aquellos usuarios que los desean rellenar con bolígrafo. Para potenciar a PHPRunner le he incorporado el uso de las librerías FREE de SETASIGN

Aunque el ejemplo es una única aplicación, para las Administraciones que requieren un gran número de Impresos y una actualización constante de los mismos, la arquitectura de la solución no sería esta. Los impresos serían aplicaciones independientes, aunque compartirían códigos, datos e infraestructura entre ellos, facilitando así, la actualización continua de estos impresos.

Ejemplo de listado de Formularios del Ayuntamiento de Madrid

El ejemplo consta:

  • Relación de Impresos
  • Pasos (bloques) para la cumplimentación del formulario. No se utiliza la funcionalidad de STEP de PHPRunner, porque tiene problemas en la notificación de los errores.
  • Campos del formulario y sus traslación a puntos X, Y y página de la plantilla PDF.

Esto no es una solución, si no que es un ejemplo para que tú construyas TU SOLUCIÓN

Actualización de la funcionalidad del ejemplo (abril-2021)

He actualizado este ejemplo con mejoras funcionales y para ello he tenido que hacer una actualización técnica de la librería utilizada para generar los ficheros PDF’s.

Ampliación funcional
  • Se añaden al fichero PDF, los ficheros adjuntos que se indican en el formulario HTML.
  • Se firma electrónicamente el fichero PDF para que el fichero no se pueda modificar externamente y si se modifica, que el propio PDF identifique que ha sido modificado. También, para que quede acreditado que el fichero se ha obtenido en esta aplicación (identificación del origen).
  • Se añade campo de firma electrónica para que se pueda firmar electrónicamente el PDF sin necesidad de software específico o de pago.
Ampliación Técnica

He actualizado el producto de Setasign FPDI 2.3.6, pero lo más importante es que he utilizado la variante que utiliza la librería TCPDF, que es la librería Open Source de PHP más utilizada para la creación de ficheros PDF’s y la que añade las funcionalidades técnicas más avanzadas y en especial, la firma de documentos PDF’s.

El modelo de datos utilizado es:

Los códigos PHP más relevantes son:

print_pdf.php
<?php
/*
// Variables of SESSION

$_SESSION['S_forms_id'] = $data['idforms'];
$_SESSION['S_forms_code'] = $data['Code'];
$_SESSION['S_forms_name']  = $data['Name'];
$_SESSION['S_forms_table'] = $data['Table'];
$_SESSION['S_company_id'] = $data['companies_company_id'];
$_SESSION['S_dept_id'] = $data['departments_dept_id']; 
$_SESSION['S_forms_query'] = $data['Query'];
$_SESSION['S_forms_template_pdf'] = $data['TemplatePDF'];
$_SESSION['S_forms_template_pages'] = $data['TemplatePages'];
$_SESSION['S_forms_template_point_x'] = $data['TemplatePointX'];
$_SESSION['S_forms_template_point_y'] = $data['TemplatePointY'];

$_SESSION['S_forms_petition_id']
*/
// Recover Config variables
$Numeric_symbol_of_thousands = $_SESSION['config'][array_search('Numeric_symbol_of_thousands', array_column($_SESSION['config'], 'name'))][value];
$Numeric_decimal_symbol = $_SESSION['config'][array_search('Numeric_decimal_symbol', array_column($_SESSION['config'], 'name'))][value];
$Date_format = $_SESSION['config'][array_search('Date_format', array_column($_SESSION['config'], 'name'))][value];
$Date_and_time_format = $_SESSION['config'][array_search('Date_and_time_format', array_column($_SESSION['config'], 'name'))][value];
$Time_format = $_SESSION['config'][array_search('Time_format', array_column($_SESSION['config'], 'name'))][value];
$Long_Date_Format = $_SESSION['config'][array_search('Long_Date_Format', array_column($_SESSION['config'], 'name'))][value];
$date_default_timezone_set = $_SESSION['config'][array_search('date_default_timezone_set', array_column($_SESSION['config'], 'name'))][value];
$setlocale_LC_TIME = $_SESSION['config'][array_search('setlocale_LC_TIME', array_column($_SESSION['config'], 'name'))][value];

date_default_timezone_set($date_default_timezone_set);
setlocale(LC_TIME, $setlocale_LC_TIME);


$l_sql = $_SESSION['S_forms_query'];
$Petition_id = $_SESSION['S_forms_petition_id'];
$forms_id = $_SESSION['S_forms_id'];

// Read info of record of forms
global $conn;
$sql_1 = str_replace("#key", $Petition_id, $l_sql);
$resql_1 = db_query($sql_1,$conn);
$data=$resql_1->fetch_all(MYSQLI_ASSOC);

// get information about uploaded files
$fileArray = my_json_decode($_SESSION['S_forms_template_pdf']);
// set the source file
$template_pdf = $fileArray[0]["name"];
$TotalPagesTemplate = $_SESSION['S_forms_template_pages'];

use setasign\Fpdi\Tcpdf\Fpdi;
require_once __DIR__ . '/../../ComponentCode/fpdi_2.3.6/autoload.php';
// initiate FPDI
$pdf = new Fpdi();
// Very important
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
//$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
$pdf->SetMargins(0, 0, 0);
// set auto page breaks
//$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
$pdf->SetAutoPageBreak(FALSE, 0);

// add a page
$pdf->AddPage();
// set the source file
$pdf->setSourceFile($template_pdf);
// import page 1
$tplIdx = $pdf->importPage(1);
// use the imported page and place it at position 10,10 with a width of 100 mm
$pdf->useTemplate($tplIdx);

// Obtain measures from the page for the transformation of the Points
$pdf->SetXY(1, 1);
// $pdf->SetFont('Arial','',10); //  Font, type and size
// $pdf->SetTextColor(0, 96, 175); // Color in R, G, B
$w   = $pdf->GetPageWidth();
$h   = $pdf->GetPageHeight();
$wPt = $_SESSION['S_forms_template_point_x']; //  Measures in points of the page
$hPt = $_SESSION['S_forms_template_point_y']; //  Measures in points of the page
$coef_x = $wPt/$w; // X axis transformation coefficient
$coef_y = $hPt/$h; // Y axis transformation coefficient



$sql_1 = "
SELECT * FROM form_fields WHERE forms_idforms = $forms_id AND NumberPageTemplate <> 0 order by NumberPageTemplate, idform_fields
";
// All Field => forms_idforms, Name, Type, Length, IsNumber, IsDecimal, NumberDecimal, Font, FontSize, FontStyle, FontColor, Align, NumberPageTemplate, PointX, PointY, RightMargin, Description
$resql_1 = db_query($sql_1,$conn);
$fields=$resql_1->fetch_all(MYSQLI_ASSOC);
// Loop to complete values
for ($i = 0; $i < count($fields); $i++) {
// Align
    switch ($fields[$i][Align]) {
    case 1:
        $fields[$i][Align] = 'R' ;
        break;
    case 2:
        $fields[$i][Align] = 'C' ;
        break;
    default:
        $fields[$i][Align] = 'L' ;
        break;
    }
// FontStyle
    switch ($fields[$i][FontStyle]) {
    case 1:
        $fields[$i][FontStyle] = 'B' ;
        break;
    case 2:
        $fields[$i][FontStyle] = 'I' ;
        break;
    case 3:
        $fields[$i][FontStyle] = 'U' ;
        break;
    default:
        $fields[$i][FontStyle] = '' ;
        break;
    }
}
// Loop to control template pages
for ($page = 1; $page <= $TotalPagesTemplate; $page++) {
    foreach ($fields as $field) {
    //  Fields = forms_idforms, Name, Type, Length, IsNumber, IsDecimal, NumberDecimal, Font, FontSize, FontStyle, FontColor, Align, NumberPageTemplate, PointX, PointY, RightMargin, Description
    if ( $field['NumberPageTemplate'] == $page ) { // Field is of page of Tamplate
                    $pdf->SetXY($field['PointX']/$coef_x, $field['PointY']/$coef_y); // Positioning on the page
                    $pdf->SetMargins($field['PointX']/$coef_x,5,$field['RightMargin']/$coef_x);
                    $pdf->SetFont($field['Font'],$field['FontStyle'],$field['FontSize']); //  Font, type and size
                      $FontColor = explode(",", $field['FontColor']);										  
                    $pdf->SetTextColor($FontColor[0],$FontColor[1],$FontColor[2]); // Color in R, G, B
 			
                    $Name = $field['Name'];
                    $Value = $data[0][$Name];
                    // $Value = iconv("UTF-8", "ISO-8859-1//TRANSLIT", $Value); // Convert UTf8
                    // FontStyle
                    switch ($field[Type]) {
                    case 0: // Char
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;
                    case 1: // Num
                        $Value = number_format($Value, $field[NumberDecimal],$Numeric_decimal_symbol,$Numeric_symbol_of_thousands);
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;
                    case 2: // Integer
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;
                    case 3: // Date
                        // $date = date_create($Value);
                        // $Value = date_format($date, $Date_format);
                          $Value = strftime($Date_format, strtotime($Value));
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;
                    case 4: // Time
                        // $date = date_create($Value);
                        // $Value = date_format($date, $Time_format);
                          $Value = strftime($Time_format, strtotime($Value));
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;
                     case 5: // DateTime
                        // $date = date_create($Value);
                        // $Value = date_format($date, $Date_and_time_format);
                          $Value = strftime($Date_and_time_format, strtotime($Value));
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;   
                     case 6: // Date Long
                        // $date = date_create($Value);
                        // $Value = date_format($date, $Long_Date_Format);
                        $Value = strftime($Long_Date_Format, strtotime($Value));
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;   
                     case 7: // Boolean
                        $pdf->SetFont('ZapfDingbats',$field['FontStyle'],$field['FontSize']); //  Font, type and size
                        If ($Value == 1) {$Value = '4';} else {$Value = '';} // 'l' Punto negro
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        break;   
                     case 8: // Memo
                          $pdf->Write(5, $Value);
                        break;   
                    default:
                          $pdf->Cell(0,0,$Value,0,1,$field[Align]);
                        }

    }

  }	
  if ($page == 1 ) 
            { //Attach files
               // get information about  files
               $fileArray = my_json_decode($data[0]['B04_file_docum_1']);
               if (count($fileArray) <> 0 ){
                    $file = $fileArray[0]['name'];
                    $name = $fileArray[0]['usrName'];
                    $pathFile = __DIR__ . "/../".$file;
                    // attach an external file
                    $pdf->Annotation(47/$coef_x, 518/$coef_y, 5, 5, "$name", array('Subtype'=>'FileAttachment', 'Name' => 'PushPin', 'FS' => "$pathFile"));
               }
               $fileArray = my_json_decode($data[0]['B04_file_docum_2']);
               if (count($fileArray) <> 0 ){
                    $file = $fileArray[0]['name'];
                    $name = $fileArray[0]['usrName'];
                    $pathFile = __DIR__ . "/../".$file;
                    // attach an external file
                    $pdf->Annotation(47/$coef_x, 532.5/$coef_y, 5, 5, "$name", array('Subtype'=>'FileAttachment', 'Name' => 'PushPin', 'FS' => "$pathFile")); 
               }
               $fileArray = my_json_decode($data[0]['B04_file_docum_3']);
               if (count($fileArray) <> 0 ){
                    $file = $fileArray[0]['name'];
                    $name = $fileArray[0]['usrName'];
                    $pathFile = __DIR__ . "/../".$file;
                    // attach an external file
                    $pdf->Annotation(47/$coef_x, 547/$coef_y, 5, 5, "$name", array('Subtype'=>'FileAttachment', 'Name' => 'PushPin', 'FS' => "$pathFile")); 
               }
            }

        if ($page <> $TotalPagesTemplate) {
            //  adding the second page of the template
            $tplIdx2 = $pdf->importPage($page+1);
            $s = $pdf->getTemplatesize($tplIdx2);
            $pdf->AddPage('', $s);
            $pdf->useImportedPage($tplIdx2);
        }
}
/*
NOTES:
 - To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
 - To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
 - To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
*/
// set certificate file
$certificate = "file://".__DIR__.'/certificate/tcpdf2.crt';
// set additional information
$info = array(
      'Name' => 'Fernando Humanes',
      'Location' => 'Mi Empresa',
      'Reason' => 'Testing TCPDF',
      'ContactInfo' => 'https://fhumanes.com',
      );
// set document signature
// $pdf->setSignature($certificate, $certificate, 'tcpdfdemo', '', 2, $info);
$pdf->setSignature($certificate, $certificate, 'Secret', '', 2, $info);

// *** set an empty signature appearance ***
$pdf->addEmptySignatureAppearance(307.31/$coef_x, 661.74/$coef_y,257/$coef_x, 41.87/$coef_y);


$pdf->Output('forms.pdf','D');
/*
// --------------------  foot to save the new PDF document ------------------
$temp_file = tempnam(sys_get_temp_dir(), 'PDF');
$pdf->Output('F',$temp_file);

// ------------------ Operation with file result -------------------------------------------
$documento = file_get_contents($temp_file);
unlink($temp_file);  // delete file tmp
header("Content-Disposition: attachment; filename= forms.pdf");
header('Content-Type: application/pdf');
echo $documento;
*/
?>

 

capture_sql_fields.php
<?php
$data = $button->getCurrentRecord();

// From the form selection
$l_idforms = $data['idforms'];
$l_code  = $data['Code'];
$l_name  = $data['Name'];
$l_table = $data['Table'];
$l_sql = $data['Query'];

global $conn;
// Delete fields of Query
$sql_1 = "delete FROM form_fields WHERE forms_idforms = $l_idforms";
$resql_1 = db_query($sql_1,$conn);
// Select fields of Query
$sql_1 = str_replace("#key", "1", $l_sql);
$resql_1 = db_query($sql_1,$conn);
$data2=$resql_1->fetch_all(MYSQLI_ASSOC);
//
$fields = array_keys($data2[0]);
foreach ($fields as $field) {
// Insert fields of query    
   $sql_1 = "
    Insert into form_fields
    (forms_idforms, Name)
    values ($l_idforms,'$field')";
    $resql_1 = db_query($sql_1,$conn);
}
?>

He utilizado un formulario de una Administración Pública de Madrid, para que fuese un caso real.

Tiene 2 páginas. En la segunda también dispone de campos.

DEMO. Disponéis de esta URL https://fhumanes.com/forms/ , por si lo deseáis probar (está en Español e Inglés). Claves de acceso «admin/admin»

También os dejo el proyecto (PHPRunner 10.2), el modelo de datos y un ejemplo de la Base de datos. Os dejo el fichero que va en el directorio “FILES” y que es la plantilla PDF del ejemplo.

Para cualquier duda o consulta, os dejo mi cuenta de email [email protected]

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner