Files
Nexora/app/Services/ProjectCodeValidator.php
Javi 047e155238
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
new functions
2025-12-14 23:59:32 +01:00

543 lines
17 KiB
PHP

<?php
namespace App\Services;
use InvalidArgumentException;
class ProjectCodeValidator
{
private array $projectData;
private array $components;
private string $separator;
private string $reference;
private int $sequenceLength;
/**
* Constructor de la clase
*
* @param array|string $project Los datos del proyecto (array o JSON string)
* @throws InvalidArgumentException Si los datos no son válidos
*/
public function __construct($project)
{
//print_r($project);
// Convertir string JSON a array si es necesario
if (is_string($project)) {
$project = json_decode($project, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('JSON inválido: ' . json_last_error_msg());
}
}
if (!is_array($project)) {
throw new InvalidArgumentException('Los datos del proyecto deben ser un array o JSON string');
}
$this->projectData = $project;
// Validar que exista la configuración de codificación
/*if (!isset($this->projectData['codingConfig'])) {
throw new InvalidArgumentException('El proyecto no tiene configuración de codificación');
}*/
$this->initializeConfiguration();
}
/**
* Inicializa la configuración a partir de los datos del proyecto
*/
private function initializeConfiguration(): void
{
$codingConfig = $this->projectData['codingConfig'];
// Obtener componentes ordenados por 'order'
$this->components = $codingConfig['elements']['components'] ?? [];
usort($this->components, fn($a, $b) => $a['order'] <=> $b['order']);
$this->separator = $codingConfig['separator'] ?? '-';
$this->reference = $this->projectData['reference'] ?? '';
$this->sequenceLength = $codingConfig['sequence_length'] ?? 4;
}
/**
* Valida si un código cumple con la configuración de codificación
*
* @param string $code El código a validar
* @return bool True si el código es válido
*/
public function validate(string $code): bool
{
// Validación básica: número de partes
$parts = explode($this->separator, $code);
if (count($parts) !== 7) {
return false;
}
// Validar referencia (primera parte)
if ($parts[0] !== $this->reference) {
return false;
}
// Validar cada componente (partes 2-6)
for ($i = 1; $i <= 5; $i++) {
if (!$this->validateComponent($i - 1, $parts[$i])) {
return false;
}
}
// La parte 7 (DOCUMENT_NAME) no tiene validación específica
return true;
}
/**
* Valida un componente específico del código
*
* @param int $componentIndex Índice del componente (0-4)
* @param string $value Valor a validar
* @return bool True si el valor es válido para el componente
*/
private function validateComponent(int $componentIndex, string $value): bool
{
if (!isset($this->components[$componentIndex])) {
return false;
}
$component = $this->components[$componentIndex];
$headerLabel = $component['headerLabel'] ?? '';
// Validar longitud máxima
$maxLength = $component['data']['maxLength'] ?? null;
if ($maxLength && strlen($value) > $maxLength) {
return false;
}
// Validar según el tipo de componente
if ($headerLabel === 'Version') {
return $this->validateVersionComponent($value);
}
// Validar contra códigos permitidos
return $this->validateAgainstAllowedCodes($component, $value);
}
/**
* Valida el componente de versión
*
* @param string $value Valor de la versión
* @return bool True si es válido
*/
private function validateVersionComponent(string $value): bool
{
// La versión debe ser numérica
if (!is_numeric($value)) {
return false;
}
// La versión debe tener la longitud especificada
if (strlen($value) !== $this->sequenceLength) {
return false;
}
return true;
}
/**
* Valida un valor contra los códigos permitidos de un componente
*
* @param array $component Configuración del componente
* @param string $value Valor a validar
* @return bool True si el valor está en los códigos permitidos
*/
private function validateAgainstAllowedCodes(array $component, string $value): bool
{
$documentTypes = $component['data']['documentTypes'] ?? [];
if (empty($documentTypes)) {
return true; // No hay restricciones de códigos
}
$allowedCodes = array_column($documentTypes, 'code');
return in_array($value, $allowedCodes, true);
}
/**
* Realiza un análisis detallado de un código
*
* @param string $code El código a analizar
* @return array Array con información detallada de la validación
*/
public function analyze(string $code): array
{
$result = [
'is_valid' => false,
'code' => $code,
'parts' => [],
'errors' => [],
'warnings' => []
];
// Dividir en partes
$parts = explode($this->separator, $code);
// Verificar número de partes
$expectedParts = 7;
if (count($parts) !== $expectedParts) {
$result['errors'][] = sprintf(
'El código debe tener %d partes separadas por "%s", tiene %d',
$expectedParts,
$this->separator,
count($parts)
);
return $result;
}
// Nombres de los componentes según el orden esperado
$componentNames = [
'reference',
'maker',
'system',
'discipline',
'document_type',
'version',
'document_name'
];
// Analizar cada parte
foreach ($parts as $index => $value) {
$analysis = $this->analyzePart($index, $value);
$analysis['name'] = $componentNames[$index] ?? 'unknown';
$result['parts'][] = $analysis;
if (!$analysis['is_valid']) {
$result['errors'][] = sprintf(
'Parte %d (%s): %s',
$index + 1,
$componentNames[$index],
$analysis['error'] ?? 'Error desconocido'
);
}
}
$result['is_valid'] = empty($result['errors']);
return $result;
}
/**
* Analiza una parte específica del código
*
* @param int $partIndex Índice de la parte (0-6)
* @param string $value Valor de la parte
* @return array Análisis detallado de la parte
*/
private function analyzePart(int $partIndex, string $value): array
{
$analysis = [
'index' => $partIndex,
'value' => $value,
'is_valid' => true,
'expected' => '',
'error' => '',
'notes' => []
];
switch ($partIndex) {
case 0: // Referencia
$analysis['expected'] = $this->reference;
if ($value !== $this->reference) {
$analysis['is_valid'] = false;
$analysis['error'] = sprintf(
'Referencia incorrecta. Esperado: %s',
$this->reference
);
}
break;
case 1: // Maker (componente 0)
case 2: // System (componente 1)
case 3: // Discipline (componente 2)
case 4: // Document Type (componente 3)
$componentIndex = $partIndex - 1;
if (isset($this->components[$componentIndex])) {
$component = $this->components[$componentIndex];
$analysis = array_merge($analysis, $this->analyzeComponent($component, $value));
}
break;
case 5: // Version (componente 4)
if (isset($this->components[4])) {
$component = $this->components[4];
$analysis = array_merge($analysis, $this->analyzeComponent($component, $value));
// Información adicional para la versión
$analysis['notes'][] = sprintf(
'Longitud de secuencia: %d',
$this->sequenceLength
);
}
break;
case 6: // Document Name
$analysis['notes'][] = 'Nombre del documento (sin restricciones específicas)';
break;
default:
$analysis['is_valid'] = false;
$analysis['error'] = 'Índice de parte no válido';
break;
}
return $analysis;
}
/**
* Analiza un componente específico
*
* @param array $component Configuración del componente
* @param string $value Valor a analizar
* @return array Análisis del componente
*/
private function analyzeComponent(array $component, string $value): array
{
$result = [
'is_valid' => true,
'expected' => '',
'error' => '',
'notes' => []
];
$headerLabel = $component['headerLabel'] ?? '';
$maxLength = $component['data']['maxLength'] ?? null;
$documentTypes = $component['data']['documentTypes'] ?? [];
// Validar longitud
if ($maxLength && strlen($value) > $maxLength) {
$result['is_valid'] = false;
$result['error'] = sprintf(
'Longitud máxima excedida: %d (actual: %d)',
$maxLength,
strlen($value)
);
}
// Validar tipo de componente
if ($headerLabel === 'Version') {
if (!is_numeric($value)) {
$result['is_valid'] = false;
$result['error'] = 'Debe ser numérico';
} elseif (strlen($value) !== $this->sequenceLength) {
$result['is_valid'] = false;
$result['error'] = sprintf(
'Longitud incorrecta. Esperado: %d, Actual: %d',
$this->sequenceLength,
strlen($value)
);
}
$result['expected'] = sprintf('Número de %d dígitos', $this->sequenceLength);
} else {
// Obtener códigos permitidos
$allowedCodes = array_column($documentTypes, 'code');
if (!empty($allowedCodes) && !in_array($value, $allowedCodes, true)) {
$result['is_valid'] = false;
$result['error'] = sprintf(
'Código no permitido. Permitidos: %s',
implode(', ', $allowedCodes)
);
}
$result['expected'] = $allowedCodes ? implode('|', $allowedCodes) : 'Sin restricciones';
$result['notes'][] = sprintf('Componente: %s', $headerLabel);
// Agregar etiquetas de los códigos permitidos
$labels = [];
foreach ($documentTypes as $type) {
if (isset($type['label'])) {
$labels[] = sprintf('%s = %s', $type['code'], $type['label']);
}
}
if (!empty($labels)) {
$result['notes'][] = 'Significados: ' . implode(', ', $labels);
}
}
return $result;
}
/**
* Genera un código válido basado en la configuración
*
* @param array $componentsValues Valores para cada componente
* @param string $documentName Nombre del documento
* @return string Código generado
* @throws InvalidArgumentException Si los valores no son válidos
*/
public function generateCode(array $componentsValues, string $documentName): string
{
// Validar que tengamos valores para todos los componentes
$requiredComponents = 5; // Maker, System, Discipline, Document Type, Version
if (count($componentsValues) < $requiredComponents) {
throw new InvalidArgumentException(
sprintf('Se requieren valores para %d componentes', $requiredComponents)
);
}
// Construir las partes del código
$parts = [$this->reference];
// Agregar componentes validados
for ($i = 0; $i < $requiredComponents; $i++) {
$value = $componentsValues[$i];
if (!$this->validateComponent($i, $value)) {
throw new InvalidArgumentException(
sprintf('Valor inválido para componente %d: %s', $i, $value)
);
}
$parts[] = $value;
}
// Agregar nombre del documento
$parts[] = $this->sanitizeDocumentName($documentName);
return implode($this->separator, $parts);
}
/**
* Sanitiza el nombre del documento para usarlo en el código
*
* @param string $documentName Nombre del documento
* @return string Nombre sanitizado
*/
private function sanitizeDocumentName(string $documentName): string
{
// Reemplazar caracteres no deseados
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '_', $documentName);
// Limitar longitud si es necesario
return substr($sanitized, 0, 50);
}
/**
* Obtiene los códigos permitidos para un componente específico
*
* @param string $componentName Nombre del componente (maker, system, discipline, document_type)
* @return array Array de códigos permitidos
*/
public function getAllowedCodesForComponent(string $componentName): array
{
$componentMap = [
'maker' => 0,
'system' => 1,
'discipline' => 2,
'document_type' => 3
];
if (!isset($componentMap[$componentName])) {
return [];
}
$index = $componentMap[$componentName];
if (!isset($this->components[$index])) {
return [];
}
$component = $this->components[$index];
$documentTypes = $component['data']['documentTypes'] ?? [];
$result = [];
foreach ($documentTypes as $type) {
$result[] = [
'code' => $type['code'],
'label' => $type['label'] ?? $type['code'],
'max_length' => $type['max_length'] ?? $component['data']['maxLength'] ?? null
];
}
return $result;
}
/**
* Obtiene el formato de codificación
*
* @return string Formato de codificación
*/
public function getFormat(): string
{
return $this->projectData['coding_config']['format'] ?? '';
}
/**
* Obtiene la referencia del proyecto
*
* @return string Referencia del proyecto
*/
public function getReference(): string
{
return $this->reference;
}
/**
* Obtiene el separador usado en la codificación
*
* @return string Separador
*/
public function getSeparator(): string
{
return $this->separator;
}
/**
* Obtiene la longitud de secuencia para la versión
*
* @return int Longitud de secuencia
*/
public function getSequenceLength(): int
{
return $this->sequenceLength;
}
}
// Ejemplo de uso:
// Suponiendo que $projectDataVariable es la variable que contiene tus datos del proyecto
/*
$validator = new ProjectCodeValidator($projectDataVariable);
// Validar un código
$codigo = "SOGOS0001-SOGOS-PV0-CV-DWG-0001-InformeTecnico";
if ($validator->validate($codigo)) {
echo "✅ Código válido\n";
} else {
echo "❌ Código inválido\n";
}
// Analizar un código detalladamente
$analisis = $validator->analyze($codigo);
echo "Código analizado: " . $analisis['code'] . "\n";
echo "Válido: " . ($analisis['is_valid'] ? 'Sí' : 'No') . "\n";
if (!$analisis['is_valid']) {
echo "Errores:\n";
foreach ($analisis['errors'] as $error) {
echo "- $error\n";
}
}
// Generar un código nuevo
try {
$componentes = ['SOGOS', 'PV0', 'CV', 'DWG', '0014'];
$nuevoCodigo = $validator->generateCode($componentes, 'PlanosGenerales');
echo "Código generado: $nuevoCodigo\n";
} catch (InvalidArgumentException $e) {
echo "Error al generar código: " . $e->getMessage() . "\n";
}
// Obtener códigos permitidos para un componente
$systemCodes = $validator->getAllowedCodesForComponent('system');
echo "Códigos permitidos para System:\n";
foreach ($systemCodes as $code) {
echo "- {$code['code']}: {$code['label']}\n";
}
*/