diff --git a/app/Http/Controllers/ProjectSettingsController.php b/app/Http/Controllers/ProjectSettingsController.php
new file mode 100644
index 0000000..3448fa8
--- /dev/null
+++ b/app/Http/Controllers/ProjectSettingsController.php
@@ -0,0 +1,223 @@
+authorize('update', $project);
+
+ //$project->load(['codingConfig', 'documentStatuses']);
+
+ return view('project-settings.index', compact('project'));
+ }
+
+ public function updateCoding(Request $request, Project $project)
+ {
+ $this->authorize('update', $project);
+
+ $validated = $request->validate([
+ 'format' => 'required|string|max:255',
+ 'year_format' => 'required|in:Y,y,Yy,yy,Y-m,Y/m,y-m,y/m',
+ 'separator' => 'required|string|max:5',
+ 'sequence_length' => 'required|integer|min:1|max:10',
+ 'auto_generate' => 'boolean',
+ 'elements' => 'nullable|array',
+ 'reset_sequence' => 'boolean',
+ ]);
+
+ try {
+ DB::beginTransaction();
+
+ $codingConfig = $project->codingConfig ?: new ProjectCodingConfig();
+ $codingConfig->project_id = $project->id;
+
+ $codingConfig->fill([
+ 'format' => $validated['format'],
+ 'year_format' => $validated['year_format'],
+ 'separator' => $validated['separator'],
+ 'sequence_length' => $validated['sequence_length'],
+ 'auto_generate' => $validated['auto_generate'] ?? false,
+ 'elements' => $validated['elements'] ?? [],
+ ]);
+
+ if ($request->boolean('reset_sequence')) {
+ $codingConfig->next_sequence = 1;
+ }
+
+ $codingConfig->save();
+
+ DB::commit();
+
+ return redirect()->route('project-settings.index', $project)
+ ->with('success', 'Configuración de codificación actualizada correctamente.');
+
+ } catch (\Exception $e) {
+ DB::rollBack();
+ return redirect()->back()
+ ->with('error', 'Error al actualizar la configuración: ' . $e->getMessage());
+ }
+ }
+
+ public function storeStatus(Request $request, Project $project)
+ {
+ $this->authorize('update', $project);
+
+ $validated = $request->validate([
+ 'name' => 'required|string|max:100',
+ 'color' => 'required|string|max:7',
+ 'text_color' => 'nullable|string|max:7',
+ 'description' => 'nullable|string|max:500',
+ 'allow_upload' => 'boolean',
+ 'allow_edit' => 'boolean',
+ 'allow_delete' => 'boolean',
+ 'requires_approval' => 'boolean',
+ 'is_default' => 'boolean',
+ ]);
+
+ try {
+ DB::beginTransaction();
+
+ // Si se marca como default, quitar el default de otros estados
+ if ($validated['is_default'] ?? false) {
+ $project->documentStatuses()->update(['is_default' => false]);
+ }
+
+ $status = new ProjectDocumentStatus($validated);
+ $status->project_id = $project->id;
+ $status->slug = Str::slug($validated['name']);
+
+ // Verificar que el slug sea único
+ $counter = 1;
+ $originalSlug = $status->slug;
+ while ($project->documentStatuses()->where('slug', $status->slug)->exists()) {
+ $status->slug = $originalSlug . '-' . $counter;
+ $counter++;
+ }
+
+ $status->save();
+
+ DB::commit();
+
+ return redirect()->route('project-settings.index', $project)
+ ->with('success', 'Estado creado correctamente.');
+
+ } catch (\Exception $e) {
+ DB::rollBack();
+ return redirect()->back()
+ ->with('error', 'Error al crear el estado: ' . $e->getMessage());
+ }
+ }
+
+ public function updateStatus(Request $request, Project $project, ProjectDocumentStatus $status)
+ {
+ $this->authorize('update', $project);
+
+ if ($status->project_id !== $project->id) {
+ abort(403);
+ }
+
+ $validated = $request->validate([
+ 'name' => 'required|string|max:100',
+ 'color' => 'required|string|max:7',
+ 'text_color' => 'nullable|string|max:7',
+ 'description' => 'nullable|string|max:500',
+ 'allow_upload' => 'boolean',
+ 'allow_edit' => 'boolean',
+ 'allow_delete' => 'boolean',
+ 'requires_approval' => 'boolean',
+ 'is_default' => 'boolean',
+ ]);
+
+ try {
+ DB::beginTransaction();
+
+ // Si se marca como default, quitar el default de otros estados
+ if ($validated['is_default'] ?? false) {
+ $project->documentStatuses()
+ ->where('id', '!=', $status->id)
+ ->update(['is_default' => false]);
+ }
+
+ $status->update($validated);
+
+ DB::commit();
+
+ return redirect()->route('project-settings.index', $project)
+ ->with('success', 'Estado actualizado correctamente.');
+
+ } catch (\Exception $e) {
+ DB::rollBack();
+ return redirect()->back()
+ ->with('error', 'Error al actualizar el estado: ' . $e->getMessage());
+ }
+ }
+
+ public function destroyStatus(Project $project, ProjectDocumentStatus $status)
+ {
+ $this->authorize('update', $project);
+
+ if ($status->project_id !== $project->id) {
+ abort(403);
+ }
+
+ // Verificar que no haya documentos con este estado
+ if ($status->documents()->exists()) {
+ return redirect()->back()
+ ->with('error', 'No se puede eliminar el estado porque hay documentos asociados.');
+ }
+
+ // Si es el estado por defecto, establecer otro como default
+ if ($status->is_default) {
+ $newDefault = $project->documentStatuses()
+ ->where('id', '!=', $status->id)
+ ->first();
+
+ if ($newDefault) {
+ $newDefault->update(['is_default' => true]);
+ }
+ }
+
+ $status->delete();
+
+ return redirect()->route('project-settings.index', $project)
+ ->with('success', 'Estado eliminado correctamente.');
+ }
+
+ public function reorderStatuses(Request $request, Project $project)
+ {
+ $this->authorize('update', $project);
+
+ $request->validate([
+ 'order' => 'required|array',
+ 'order.*' => 'exists:project_document_statuses,id',
+ ]);
+
+ try {
+ DB::beginTransaction();
+
+ foreach ($request->order as $index => $statusId) {
+ $status = ProjectDocumentStatus::find($statusId);
+ if ($status && $status->project_id === $project->id) {
+ $status->update(['order' => $index + 1]);
+ }
+ }
+
+ DB::commit();
+
+ return response()->json(['success' => true]);
+
+ } catch (\Exception $e) {
+ DB::rollBack();
+ return response()->json(['error' => $e->getMessage()], 500);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Livewire/CodeEdit.php b/app/Livewire/CodeEdit.php
index 93d2fa4..b4f1741 100644
--- a/app/Livewire/CodeEdit.php
+++ b/app/Livewire/CodeEdit.php
@@ -22,17 +22,33 @@ class CodeEdit extends Component
'maxLength' => 'required|integer|min:2|max:12',
];
- public function mount($componentId, $initialName = '')
+ public function mount($componentId, $initialName = '', $initialMaxLength = 3, $initialDocumentTypes = [])
{
$this->componentId = $componentId;
$this->name = $initialName;
- $this->maxLength = 3;
+ $this->maxLength = $initialMaxLength;
+ $this->documentTypes = $initialDocumentTypes;
- // Disparar evento inicial para establecer el nombre
+ // Guardar datos iniciales
+ $this->initialData = [
+ 'name' => $initialName,
+ 'maxLength' => $initialMaxLength,
+ 'documentTypes' => $initialDocumentTypes
+ ];
+
+ // Disparar eventos iniciales
$this->dispatch('nameUpdated',
componentId: $this->componentId,
data: [
- 'name' => $this->name
+ 'name' => $this->name,
+ ]
+ );
+
+ $this->dispatch('componentUpdated',
+ componentId: $this->componentId,
+ data: [
+ 'documentTypes' => $this->documentTypes,
+ 'maxLength' => $this->maxLength
]
);
}
@@ -74,7 +90,7 @@ class CodeEdit extends Component
$this->documentTypes[] = [
'code' => $this->codeInput,
'label' => $this->labelInput,
- 'max_length' => $this->maxLength,
+ //'max_length' => $this->maxLength,
];
$this->sortList();
diff --git a/app/Livewire/ProjectNameCoder.php b/app/Livewire/ProjectNameCoder.php
index 5c43255..4f8610a 100644
--- a/app/Livewire/ProjectNameCoder.php
+++ b/app/Livewire/ProjectNameCoder.php
@@ -3,11 +3,14 @@
namespace App\Livewire;
use Livewire\Component;
+use App\Models\Project;
+use App\Models\ProjectCodingConfig;
class ProjectNameCoder extends Component
{
public $components = [];
public $nextId = 1;
+ public $project;
protected $listeners = [
'nameUpdated' => 'headerLabelUpdate',
@@ -15,10 +18,40 @@ class ProjectNameCoder extends Component
'removeComponent' => 'removeComponent'
];
- public function mount()
+ public function mount(Project $project)
{
// Inicializar con un componente vacío
- $this->addComponent();
+ $this->project = $project;
+
+ // Si hay configuración inicial, cargarla
+ if ($project->codingConfig) {
+ $this->loadDatabaseConfiguration();
+ } else {
+ // Inicializar con un componente vacío
+ $this->addComponent();
+ }
+ }
+
+ private function loadDatabaseConfiguration()
+ {
+ // Buscar la configuración de codificación del proyecto
+ $config = $this->project->codingConfig;
+
+ if ($config && isset($config->elements['components'])) {
+ $this->components = $config->elements['components'];
+ $this->nextId = count($this->components) + 1;
+
+ // Asegurar que cada componente tenga los campos necesarios
+ foreach ($this->components as &$component) {
+ $component['data'] = $component['data'] ?? [];
+ $component['order'] = $component['order'] ?? 0;
+ $component['headerLabel'] = $component['headerLabel'] ?? '';
+ $component['documentTypes'] = $component['documentTypes'] ?? [];
+ }
+ } else {
+ // Si no hay configuración, inicializar con un componente vacío
+ $this->addComponent();
+ }
}
public function addComponent()
@@ -72,7 +105,6 @@ class ProjectNameCoder extends Component
}
}
}
-
// Ordenar el array por el campo 'order'
usort($this->components, function($a, $b) {
return $a['order'] - $b['order'];
@@ -142,6 +174,101 @@ class ProjectNameCoder extends Component
return $total;
}
+ public function saveConfiguration()
+ {
+ try {
+ // Preparar la configuración completa
+ $configData = [
+ 'components' => $this->components,
+ //'total_components' => $this->componentsCount,
+ //'total_document_types' => $this->totalDocumentTypes,
+ //'generated_format' => $this->generateFormatString(),
+ //'last_updated' => now()->toDateTimeString(),
+ ];
+
+ // Buscar o crear la configuración de codificación
+ $codingConfig = ProjectCodingConfig::firstOrNew(['project_id' => $this->project->id]);
+
+ // Actualizar los campos
+ $codingConfig->fill([
+ 'elements' => $configData,
+ 'format' => $this->generateFormatString(),
+ 'auto_generate' => true,
+ ]);
+
+ $codingConfig->save();
+
+ // Emitir evento de éxito
+ $this->dispatch('configurationSaved', [
+ 'message' => 'Configuración guardada exitosamente',
+ 'format' => $this->generateFormatString()
+ ]);
+
+ } catch (\Exception $e) {
+ $this->dispatch('configurationError', [
+ 'message' => 'Error al guardar la configuración: ' . $e->getMessage()
+ ]);
+ }
+ }
+
+ private function autoSave()
+ {
+ // Auto-guardar cada 30 segundos de inactividad o cuando haya cambios importantes
+ // Esto es opcional pero mejora la experiencia de usuario
+ $this->dispatch('configurationAutoSaved');
+ }
+
+ private function generateFormatString()
+ {
+ $formatParts = [];
+
+ // Agregar el código del proyecto
+ $formatParts[] = $this->project->code ?? $this->project->reference ?? 'PROJ';
+
+ // Agregar cada componente
+ foreach ($this->components as $component) {
+ if (!empty($component['headerLabel'])) {
+ $formatParts[] = '[' . strtoupper($component['headerLabel']) . ']';
+ }
+ }
+
+ // Agregar el nombre del documento
+ $formatParts[] = '[DOCUMENT_NAME]';
+
+ return implode('-', $formatParts);
+ }
+
+ public function getExampleCodeAttribute()
+ {
+ $exampleParts = [];
+
+ // Agregar el código del proyecto
+ $exampleParts[] = $this->project->code ?? $this->project->reference ?? 'PROJ';
+
+ // Agregar cada componente con un valor de ejemplo
+ foreach ($this->components as $component) {
+ if (!empty($component['headerLabel'])) {
+ $exampleParts[] = $this->getExampleForComponent($component);
+ }
+ }
+
+ // Agregar nombre de documento de ejemplo
+ $exampleParts[] = 'Documento-Ejemplo';
+
+ return implode('-', $exampleParts);
+ }
+
+ private function getExampleForComponent($component)
+ {
+ if (isset($component['data']['documentTypes']) && count($component['data']['documentTypes']) > 0) {
+ // Tomar el primer tipo de documento como ejemplo
+ $firstType = $component['data']['documentTypes'][0];
+ return $firstType['code'] ?? $firstType['name'] ?? 'TIPO';
+ }
+
+ return 'VALOR';
+ }
+
public function render()
{
return view('livewire.project-name-coder');
diff --git a/app/Models/Project.php b/app/Models/Project.php
index b3b3258..84826f5 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -87,4 +87,109 @@ class Project extends Model
{
return $this->belongsTo(Company::class);
}
+
+ // Agregar estas relaciones al modelo Project
+ public function codingConfig()
+ {
+ return $this->hasOne(ProjectCodingConfig::class);
+ }
+
+ public function documentStatuses()
+ {
+ //return $this->hasMany(ProjectDocumentStatus::class);
+ }
+
+ public function defaultStatus()
+ {
+ //return $this->hasOne(ProjectDocumentStatus::class)->where('is_default', true);
+ }
+
+ // Método para inicializar la configuración
+ public function initializeSettings()
+ {
+ // Crear configuración de codificación si no existe
+ if (!$this->codingConfig) {
+ $this->codingConfig()->create([
+ 'format' => '[PROJECT]-[TYPE]-[YEAR]-[SEQUENCE]',
+ 'next_sequence' => 1,
+ 'year_format' => 'Y',
+ 'separator' => '-',
+ 'sequence_length' => 4,
+ 'auto_generate' => true,
+ ]);
+ }
+
+ // Crear estados predeterminados si no existen
+ /*
+ if ($this->documentStatuses()->count() === 0) {
+ $defaultStatuses = [
+ [
+ 'name' => 'Borrador',
+ 'slug' => 'draft',
+ 'color' => '#6b7280', // Gris
+ 'order' => 1,
+ 'is_default' => true,
+ 'allow_upload' => true,
+ 'allow_edit' => true,
+ 'allow_delete' => true,
+ 'requires_approval' => false,
+ 'description' => 'Documento en proceso de creación',
+ ],
+ [
+ 'name' => 'En Revisión',
+ 'slug' => 'in_review',
+ 'color' => '#f59e0b', // Ámbar
+ 'order' => 2,
+ 'is_default' => false,
+ 'allow_upload' => false,
+ 'allow_edit' => false,
+ 'allow_delete' => false,
+ 'requires_approval' => true,
+ 'description' => 'Documento en proceso de revisión',
+ ],
+ [
+ 'name' => 'Aprobado',
+ 'slug' => 'approved',
+ 'color' => '#10b981', // Verde
+ 'order' => 3,
+ 'is_default' => false,
+ 'allow_upload' => false,
+ 'allow_edit' => false,
+ 'allow_delete' => false,
+ 'requires_approval' => false,
+ 'description' => 'Documento aprobado',
+ ],
+ [
+ 'name' => 'Rechazado',
+ 'slug' => 'rejected',
+ 'color' => '#ef4444', // Rojo
+ 'order' => 4,
+ 'is_default' => false,
+ 'allow_upload' => true,
+ 'allow_edit' => true,
+ 'allow_delete' => false,
+ 'requires_approval' => false,
+ 'description' => 'Documento rechazado',
+ ],
+ [
+ 'name' => 'Archivado',
+ 'slug' => 'archived',
+ 'color' => '#8b5cf6', // Violeta
+ 'order' => 5,
+ 'is_default' => false,
+ 'allow_upload' => false,
+ 'allow_edit' => false,
+ 'allow_delete' => false,
+ 'requires_approval' => false,
+ 'description' => 'Documento archivado',
+ ],
+ ];
+
+ foreach ($defaultStatuses as $status) {
+ //$this->documentStatuses()->create($status);
+ }
+ }*/
+
+ return $this;
+ }
}
diff --git a/app/Models/ProjectCodingConfig.php b/app/Models/ProjectCodingConfig.php
new file mode 100644
index 0000000..9490d55
--- /dev/null
+++ b/app/Models/ProjectCodingConfig.php
@@ -0,0 +1,107 @@
+ 'array',
+ 'auto_generate' => 'boolean',
+ ];
+
+ public function project()
+ {
+ return $this->belongsTo(Project::class);
+ }
+
+ // Método para generar un código de documento
+ public function generateCode($type = 'DOC', $year = null)
+ {
+ $code = $this->format;
+
+ // Reemplazar variables
+ $replacements = [
+ '[PROJECT]' => $this->project->code ?? 'PROJ',
+ '[TYPE]' => $type,
+ '[YEAR]' => $year ?? date($this->year_format),
+ '[MONTH]' => date('m'),
+ '[DAY]' => date('d'),
+ '[SEQUENCE]' => str_pad($this->next_sequence, $this->sequence_length, '0', STR_PAD_LEFT),
+ '[RANDOM]' => strtoupper(substr(md5(uniqid()), 0, 6)),
+ ];
+
+ // Si hay elementos personalizados, agregarlos
+ if (is_array($this->elements)) {
+ foreach ($this->elements as $key => $value) {
+ $replacements["[{$key}]"] = $value;
+ }
+ }
+
+ $code = str_replace(array_keys($replacements), array_values($replacements), $code);
+
+ // Incrementar secuencia
+ if (strpos($this->format, '[SEQUENCE]') !== false && $this->auto_generate) {
+ $this->increment('next_sequence');
+ }
+
+ return $code;
+ }
+
+ // Método para obtener elementos disponibles
+ public static function getAvailableElements()
+ {
+ return [
+ 'project' => [
+ 'label' => 'Código del Proyecto',
+ 'description' => 'Código único del proyecto',
+ 'variable' => '[PROJECT]',
+ ],
+ 'type' => [
+ 'label' => 'Tipo de Documento',
+ 'description' => 'Tipo de documento (DOC, IMG, PDF, etc.)',
+ 'variable' => '[TYPE]',
+ ],
+ 'year' => [
+ 'label' => 'Año',
+ 'description' => 'Año actual en formato configurable',
+ 'variable' => '[YEAR]',
+ ],
+ 'month' => [
+ 'label' => 'Mes',
+ 'description' => 'Mes actual (01-12)',
+ 'variable' => '[MONTH]',
+ ],
+ 'day' => [
+ 'label' => 'Día',
+ 'description' => 'Día actual (01-31)',
+ 'variable' => '[DAY]',
+ ],
+ 'sequence' => [
+ 'label' => 'Secuencia',
+ 'description' => 'Número secuencial autoincremental',
+ 'variable' => '[SEQUENCE]',
+ ],
+ 'random' => [
+ 'label' => 'Aleatorio',
+ 'description' => 'Cadena aleatoria de 6 caracteres',
+ 'variable' => '[RANDOM]',
+ ],
+ ];
+ }
+}
\ No newline at end of file
diff --git a/database/migrations/2025_12_07_141030_create_project_coding_configs_table.php b/database/migrations/2025_12_07_141030_create_project_coding_configs_table.php
new file mode 100644
index 0000000..5aef554
--- /dev/null
+++ b/database/migrations/2025_12_07_141030_create_project_coding_configs_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->foreignId('project_id')->constrained()->onDelete('cascade')->unique();
+ $table->string('format')->default('[PROJECT]-[TYPE]-[YEAR]-[SEQUENCE]');
+ $table->json('elements')->nullable(); // Elementos configurados del código
+ $table->integer('next_sequence')->default(1);
+ $table->string('year_format')->default('Y'); // Y, y, YY, yyyy
+ $table->string('separator')->default('-');
+ $table->integer('sequence_length')->default(4);
+ $table->boolean('auto_generate')->default(true);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('project_coding_configs');
+ }
+};
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index d6be9f7..5bed522 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -134,6 +134,45 @@
{{ __('Create User') }}
+
+
{{ $project->reference }} - {{ $project->name }}
+{{ $status->description }}
+ @endif + + +