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"; } */