Skip to main content

Overview

The Sites & Fronts catalog manages the geographic and operational structure of work locations. This hierarchical system organizes sites by operational fronts (patios, vessels, platforms).

Data Models

Frente (Front)

Operational fronts represent major work area categories:
operaciones/models/catalogos_models.py
class Frente(models.Model):
    descripcion = models.CharField(max_length=200)
    nivel_afectacion = models.IntegerField(blank=True, null=True)
    comentario = models.TextField(blank=True, null=True)
    activo = models.BooleanField(default=True)

    class Meta:
        db_table = 'frente'

    def __str__(self):
        return self.descripcion

Sitio (Site)

Sites are specific work locations linked to a front:
operaciones/models/catalogos_models.py
class Sitio(models.Model):
    descripcion = models.CharField(max_length=100)
    activo = models.BooleanField(default=True)
    id_frente = models.ForeignKey(Frente, on_delete=models.CASCADE, blank=True, null=True)
    comentario = models.TextField(blank=True, null=True)
    
    class Meta:
        db_table = 'sitio'

    def __str__(self):
        return self.descripcion

Front Types

Fronts use nivel_afectacion to categorize their scope:
Fronts that affect site-level operations (e.g., Patio, Vessel, Platform)

Front Management

Listing Fronts

operaciones/views/catalogos.py
@login_required(login_url='/accounts/login/')
def lista_frentes(request):
    """Lista de todas los estados de cobro"""
    return render(request, 'operaciones/catalogos/frentes/lista_frentes.html')

DataTable API

operaciones/views/catalogos.py
def datatable_frentes(request):
    draw = int(request.GET.get('draw', 1))
    start = int(request.GET.get('start', 0))
    length = int(request.GET.get('length', 10))
    search_value = request.GET.get('filtro', '')
    
    tipos = Frente.objects.filter(activo=1).annotate(
        estado_texto=Case(
            When(activo=True, then=Value('Activo')),
            When(activo=False, then=Value('Inactivo')),
            default=Value('Desconocido'),
            output_field=CharField()
        ),
        nivel_texto=Case(
            When(nivel_afectacion=1, then=Value('Sitios')),
            When(nivel_afectacion=2, then=Value('OTs')),
            default=Value('No definido'),
            output_field=CharField()
        )
    ).order_by('id')
    
    if search_value:
        tipos = tipos.filter(
            Q(descripcion__icontains=search_value) |
            Q(activo__icontains=search_value)
        )
    
    total_records = tipos.count()
    tipos = tipos[start:start + length]
    
    data = []
    for tipo in tipos:
        data.append({
            'id': tipo.id,
            'descripcion': tipo.descripcion,
            'activo': tipo.estado_texto,
            'activo_bool': tipo.activo,
            'nivel_afectacion': tipo.nivel_texto,
        })
    
    return JsonResponse({
        'draw': draw,
        'recordsTotal': total_records,
        'recordsFiltered': total_records,
        'data': data
    })

Creating Fronts

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def crear_frente(request):
    try:
        descripcion = request.POST.get('descripcion')
        afectacion = request.POST.get('afectacion')
        comentario = request.POST.get('comentario', '')
        activo = True
        
        frente = Frente.objects.create(
            descripcion=descripcion,
            nivel_afectacion=afectacion,
            comentario=comentario,
            activo=activo
        )
        
        return JsonResponse({
            'exito': True,
            'tipo_aviso': 'exito',
            'detalles': 'Frente creado correctamente',
            'id': frente.id
        })
    except Exception as e:
        return JsonResponse({
            'exito': False,
            'tipo_aviso': 'error',
            'detalles': f'Error al crear frente: {str(e)}'
        })

Updating Fronts

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def editar_frente(request):
    try:
        id = request.POST.get('id')
        descripcion = request.POST.get('descripcion')
        afectacion = request.POST.get('afectacion')
        comentario = request.POST.get('comentario', '')
        
        frente = Frente.objects.get(id=id)
        frente.descripcion = descripcion
        frente.nivel_afectacion = afectacion
        frente.comentario = comentario
        frente.save()
        
        return JsonResponse({
            'tipo_aviso': 'exito',
            'detalles': 'Frente actualizada correctamente',
            'exito': True
        })
    except Frente.DoesNotExist:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': 'Frente no encontrada',
            'exito': False
        })
    except Exception as e:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': f'Error al actualizar el frente: {str(e)}',
            'exito': False
        })

Deleting (Deactivating) Fronts

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def eliminar_frente(request):
    try:
        id = request.POST.get('id')
        
        if not id:
            return JsonResponse({
                'tipo_aviso': 'error',
                'detalles': 'ID de frente no proporcionado',
                'exito': False
            })

        frente = Frente.objects.get(id=id)
        frente.activo = False
        frente.save()

        return JsonResponse({
            'tipo_aviso': 'exito',
            'detalles': 'Frente desactivado correctamente',
            'exito': True
        })

    except Frente.DoesNotExist:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': 'Frente no encontrado',
            'exito': False
        })
        
    except Exception as e:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': f'Error al desactivar frente: {str(e)}',
            'exito': False
        })

Site Management

Listing Sites

operaciones/views/catalogos.py
@login_required(login_url='/accounts/login/')
def lista_sitios(request):
    """Lista de todas los sitios"""
    frentes = Frente.objects.filter(activo=True, nivel_afectacion=1)
    return render(request, 'operaciones/catalogos/sitios/lista_sitios.html', {'frentes': frentes})
The view preloads active fronts with nivel_afectacion=1 for the dropdown selector.

DataTable API for Sites

operaciones/views/catalogos.py
def datatable_sitios(request):
    draw = int(request.GET.get('draw', 1))
    start = int(request.GET.get('start', 0))
    length = int(request.GET.get('length', 10))
    search_value = request.GET.get('filtro', '')
    
    sitios = Sitio.objects.filter(activo=1).annotate(
        estado_texto=Case(
            When(activo=True, then=Value('Activo')),
            When(activo=False, then=Value('Inactivo')),
            default=Value('Desconocido'),
            output_field=CharField()
        ),
        frente_descripcion=F('id_frente__descripcion')
    ).order_by('id')
    
    if search_value:
        sitios = sitios.filter(
            Q(descripcion__icontains=search_value) |
            Q(activo__icontains=search_value)
        )
    
    total_records = sitios.count()
    sitios = sitios[start:start + length]
    
    data = []
    for sitio in sitios:
        data.append({
            'id': sitio.id,
            'descripcion': sitio.descripcion,
            'activo': sitio.estado_texto,
            'activo_bool': sitio.activo,
            'frente': sitio.frente_descripcion,
        })
    
    return JsonResponse({
        'draw': draw,
        'recordsTotal': total_records,
        'recordsFiltered': total_records,
        'data': data
    })
Query Optimization: Uses F() expression to fetch related front description in a single query.

Creating Sites

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def crear_sitio(request):
    try:
        descripcion = request.POST.get('descripcion')
        comentario = request.POST.get('comentario', '')
        id_frente = request.POST.get('id_frente')
        activo = True
        
        sitio = Sitio.objects.create(
            descripcion=descripcion,
            comentario=comentario,
            id_frente_id=id_frente,
            activo=activo
        )
        
        return JsonResponse({
            'exito': True,
            'tipo_aviso': 'exito',
            'detalles': 'Sitio creada correctamente',
            'id': sitio.id
        })
    except Exception as e:
        return JsonResponse({
            'exito': False,
            'tipo_aviso': 'error',
            'detalles': f'Error al crear sitio: {str(e)}'
        })

Updating Sites

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def editar_sitio(request):
    try:
        sitio_id = request.POST.get('id')
        descripcion = request.POST.get('descripcion')
        comentario = request.POST.get('comentario', '')
        id_frente = request.POST.get('id_frente')
        
        sitio = Sitio.objects.get(id=sitio_id)
        sitio.descripcion = descripcion
        sitio.comentario = comentario
        sitio.id_frente_id = id_frente
        sitio.save()
        
        return JsonResponse({
            'tipo_aviso': 'exito',
            'detalles': 'Sitio actualizada correctamente',
            'exito': True
        })
    except Sitio.DoesNotExist:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': 'Sitio no encontrada',
            'exito': False
        })
    except Exception as e:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': f'Error al actualizar sitio: {str(e)}',
            'exito': False
        })

Getting Site Details

operaciones/views/catalogos.py
@require_http_methods(["GET"])
def obtener_sitio(request):
    try:
        sitio_id = request.GET.get('id')
        sitio = Sitio.objects.get(id=sitio_id)
        return JsonResponse({
            'id': sitio.id,
            'descripcion': sitio.descripcion,
            'comentario': sitio.comentario,
            'id_frente': sitio.id_frente_id,
            'activo': sitio.activo
        })
    except Sitio.DoesNotExist:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': 'Sitio no encontrada'
        }, status=404)

Deleting (Deactivating) Sites

operaciones/views/catalogos.py
@require_http_methods(["POST"])
def eliminar_sitio(request):
    try:
        sitio_id = request.POST.get('sitio_id')
        
        if not sitio_id:
            return JsonResponse({
                'tipo_aviso': 'error',
                'detalles': 'ID de sitio no proporcionado',
                'exito': False
            })

        sitio = Sitio.objects.get(id=sitio_id)
        sitio.activo = False
        sitio.save()

        return JsonResponse({
            'tipo_aviso': 'exito',
            'detalles': 'Sitio desactivada correctamente',
            'exito': True
        })

    except Sitio.DoesNotExist:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': 'Sitio no encontrada',
            'exito': False
        })
        
    except Exception as e:
        return JsonResponse({
            'tipo_aviso': 'error',
            'detalles': f'Error al desactivar sitio: {str(e)}',
            'exito': False
        })

Query Center Integration

The Query Center module uses fronts to filter operational data:
operaciones/views/centro_consulta.py
@login_required
def fn_obtener_frente_afectacion_dos(solicitud):
    """Get fronts with affectation level 2 for OT filtering"""
    try:
        frentes_listado = Frente.objects.filter(nivel_afectacion=2, activo=True)
        return JsonResponse([
            {
                "id": f.id,
                "descripcion": f.descripcion,
                "afectacion": f.nivel_afectacion,
                "comentario": f.comentario,
                "activo": f.activo
            } for f in frentes_listado
        ], safe=False) if frentes_listado.exists() else JsonResponse({
            "tipo_aviso": "error",
            "detalles": "No se encontraron frentes con afectación 2"
        }, status=404)
    except Exception as error_proceso:
        return JsonResponse({
            "tipo_aviso": "error", 
            "detalles": f"Error en el servidor: {str(error_proceso)}"
        }, status=500)

URL Configuration

operaciones/urls.py
# URLs for Fronts
path('catalogos/frentes/', catalogos.lista_frentes, name='lista_frentes'),
path('catalogos/datatable_frentes/', catalogos.datatable_frentes, name='datatable_frentes'),
path('catalogos/frentes/crear/', catalogos.crear_frente, name='crear_frente'),
path('catalogos/frentes/eliminar/', catalogos.eliminar_frente, name='eliminar_frente'),
path('catalogos/frentes/obtener/', catalogos.obtener_frente, name='obtener_frente'),
path('catalogos/frentes/editar/', catalogos.editar_frente, name='editar_frente'),

# URLs for Sites
path('catalogos/sitios/', catalogos.lista_sitios, name='lista_sitios'),
path('catalogos/datatable_sitios/', catalogos.datatable_sitios, name='datatable_sitios'),
path('catalogos/sitios/crear/', catalogos.crear_sitio, name='crear_sitio'),
path('catalogos/sitios/eliminar/', catalogos.eliminar_sitio, name='eliminar_sitio'),
path('catalogos/sitios/obtener/', catalogos.obtener_sitio, name='obtener_sitio'),
path('catalogos/sitios/editar/', catalogos.editar_sitio, name='editar_sitio'),

# Query Center - Frentes API
path('catalogos/frentes/obtener_nivel_afectacion_dos/', centro_consulta.fn_obtener_frente_afectacion_dos, name='obtener_frente_afectacion_dos'),

Site Resolution in Work Orders

Work orders use fronts to determine the correct site field:
operaciones/views/centro_consulta.py (excerpt)
CASE
    WHEN o.id_frente_id = 1  -- Patio
        THEN o.id_patio
    WHEN o.id_frente_id = 2  -- Vessel
        THEN o.id_embarcacion
    WHEN o.id_frente_id = 4  -- Platform
        THEN o.id_plataforma
    ELSE NULL
END AS id_sitio_oficial,

CASE
    WHEN o.id_frente_id = 1
        THEN COALESCE(s_pat.descripcion, 'SIN PATIO')
    WHEN o.id_frente_id = 2
        THEN COALESCE(s_emb.descripcion, 'SIN EMBARCACION')
    WHEN o.id_frente_id = 4
        THEN COALESCE(s_plat.descripcion, 'SIN PLATAFORMA')
    ELSE 'SIN UBICACIÓN'
END AS sitio_oficial
Dynamic Site Mapping: Fronts with ID 1, 2, or 4 map to patio, vessel (embarcación), or platform (plataforma) respectively.

Best Practices

1

Hierarchical Design

Always create fronts before creating sites. Sites require a valid front reference.
2

Consistent Naming

Use consistent naming conventions:
  • Fronts: Broad categories (“PATIO”, “EMBARCACION”, “PLATAFORMA”)
  • Sites: Specific locations (“PATIO A”, “EMBARCACION MAYA 1”)
3

Affectation Levels

Set appropriate nivel_afectacion:
  • Level 1: For site-level operations
  • Level 2: For work order-level operations
4

Soft Delete Only

Never hard-delete fronts or sites. Use activo=False to maintain data integrity.
Foreign Key Constraints: Deactivating a front with active sites may cause validation issues. Always check site references before deactivation.

Catalogs Overview

Return to catalog management overview

Query Center

See how sites are used in queries

Build docs developers (and LLMs) love