Skip to main content

Overview

Monthly Reports (ReporteMensual) serve as the container for all production activities in a specific month for a work order. They aggregate daily operational reports, production volumes, and evidence files for billing and auditing purposes.
Each work order can have only one monthly report per month, enforced by a unique constraint.

ReporteMensual Model

class ReporteMensual(models.Model):
    """
    Representa la 'Carpeta Mensual' de una OT.
    Agrupa todos los reportes diarios y de producción de un mes específico.
    """
    id_ot = models.ForeignKey(OTE, on_delete=models.CASCADE, related_name='reportes_mensuales', blank=True, null=True)
    mes = models.IntegerField(help_text="Mes numérico (1-12)")
    anio = models.IntegerField(help_text="Año (Ej. 2025)")
    archivo = models.URLField(blank=True, null=True, verbose_name="Link Evidencia (Drive)")
    id_estatus = models.ForeignKey(Estatus, on_delete=models.CASCADE, limit_choices_to={'nivel_afectacion': 5}, default=1, verbose_name="Estatus Cierre")
    fecha_creacion = models.DateTimeField(auto_now_add=True)
    fecha_actualizacion = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'reporte_mensual_header'
        unique_together = ['id_ot', 'mes', 'anio']
        verbose_name = "Reporte Mensual"
        verbose_name_plural = "Reportes Mensuales"

    def __str__(self):
        return f"Reporte {self.id_ot.orden_trabajo} - {self.mes}/{self.anio}"

Field Descriptions

id_ot
ForeignKey(OTE)
required
Parent work order for this monthly reportRelationship: One work order can have many monthly reports (one per month)
mes
IntegerField
required
Month number (1-12)Validation: Must be between 1 and 12
anio
IntegerField
required
Year (e.g., 2025)Format: Four-digit year
archivo
URLField
Google Drive link to consolidated evidence folderUsage: Points to monthly photo gallery, reports, and documentation
id_estatus
ForeignKey(Estatus)
default:"1"
Closure status of the monthly reportConstraint: nivel_afectacion=5 (monthly closure statuses)Common Values:
  • Abierto (Open)
  • En Revisión (Under Review)
  • Cerrado (Closed)
  • Facturado (Invoiced)
fecha_creacion
DateTimeField
Timestamp when monthly report was first created
fecha_actualizacion
DateTimeField
Timestamp of last modification

Monthly Report Lifecycle

1

Automatic Creation

Monthly report is automatically created when first daily report or production entry is recorded
reporte_mensual, created = ReporteMensual.objects.get_or_create(
    id_ot_id=id_ot,
    mes=mes,
    anio=anio,
    defaults={'id_estatus_id': 1}  # Default: Abierto
)
2

Data Accumulation

Throughout the month:
  • Daily operational reports are added
  • Production volumes are recorded
  • GPU evidence is uploaded
3

Evidence Upload

At month end, consolidated evidence folder is created in Google Drive and linked
4

Review and Closure

Status changes to “En Revisión”, then “Cerrado” after validation
5

Billing Integration

Closed monthly reports feed into estimation and invoicing processes
The system provides an endpoint to attach Google Drive evidence to monthly reports.
@login_required
@require_http_methods(["POST"])
def guardar_archivo_mensual(request):
    """
    Guarda el enlace de archivo en el ReporteMensual
    """
    try:
        data = json.loads(request.body)
        
        id_ot = data.get('id_ot')
        mes = data.get('mes')
        anio = data.get('anio')
        url = data.get('archivo')
        
        if not all([id_ot, mes, anio]):
            return JsonResponse({
                'exito': False,
                'mensaje': 'Faltan datos obligatorios (OT, Mes o Año).'
            })

        if not url:
            return JsonResponse({
                'exito': False,
                'mensaje': 'La URL del archivo es obligatoria.'
            })

        with transaction.atomic():
            reporte, created = ReporteMensual.objects.get_or_create(
                id_ot_id=id_ot,
                mes=int(mes),
                anio=int(anio),
                defaults={
                    'id_estatus_id': 1
                }
            )

            reporte.archivo = url
            reporte.save()

        return JsonResponse({
            'exito': True,
            'mensaje': 'Evidencia vinculada al Reporte Mensual correctamente.',
            'archivo': reporte.archivo
        })

    except Exception as e:
        return JsonResponse({
            'exito': False,
            'mensaje': f'Error interno al guardar evidencia: {str(e)}'
        }, status=500)
Always use shared Google Drive folders for evidence links. Ensure proper permissions are set so auditors and clients can access them.

Child Relationships

Monthly reports have two primary child relationships:
# Access all daily reports for a monthly report
monthly_report = ReporteMensual.objects.get(id_ot_id=123, mes=3, anio=2025)
daily_reports = monthly_report.dias_estatus.all()

# Count productive days
productive_days = monthly_report.dias_estatus.filter(
    id_estatus__descripcion='Productivo'
).count()

Monthly Closure Workflow

Never close a monthly report until all production data has been verified and evidence uploaded. Closed reports may be locked from further editing.

Pre-Closure Checklist

  • All working days have daily operational reports
  • Production volumes entered for all active budget line items
  • GPU evidence uploaded for all C-2/C-3 production
  • No pending validation errors or excess flags unresolved
  • Monthly photo gallery organized in Google Drive
  • Daily reports compiled into PDF or spreadsheet
  • Special incident reports attached
  • Drive folder link tested and accessible
  • Production totals match daily reported volumes
  • No unauthorized production exceeding contracted quantities
  • Time classification (TE vs CMA) correctly assigned
  • Unit prices current and accurate
  • Site supervisor reviewed and approved
  • Operations manager validated data
  • Administrative team confirmed for billing
  • Status changed to “Cerrado”

Querying Monthly Reports

Get or Create Pattern

The system uses Django’s get_or_create pattern extensively:
reporte_mensual, created = ReporteMensual.objects.get_or_create(
    id_ot_id=id_ot,
    mes=mes,
    anio=anio,
    defaults={'id_estatus_id': 1}
)
This pattern ensures a monthly report always exists before adding child records (daily reports or production), preventing orphaned data.

Filtering by Status

# Get all open monthly reports
open_reports = ReporteMensual.objects.filter(
    id_estatus__descripcion='Abierto'
)

# Get closed reports for specific work order
closed_reports = ReporteMensual.objects.filter(
    id_ot__orden_trabajo='OTE-2025-001',
    id_estatus__descripcion='Cerrado'
).order_by('-anio', '-mes')

# Get all reports for current month across all OTs
from datetime import date
today = date.today()
current_month_reports = ReporteMensual.objects.filter(
    mes=today.month,
    anio=today.year
)

Integration with Production Grid

The monthly report archives link is displayed in the production grid header:
archivo_url = ''
dias_bloqueados = []

try:
    reporte = ReporteMensual.objects.filter(id_ot_id=id_ot, mes=mes, anio=anio).first()
    if reporte:
        dias_bloqueados = list(ReporteDiario.objects.filter(
            id_reporte_mensual=reporte,
            id_estatus_id=17,  # Cerrado status
            id_sitio=id_sitio_int
        ).values_list('fecha__day', flat=True))
        
        if reporte.archivo:
            archivo_url = reporte.archivo
except Exception:
    pass
Monthly Report Evidence Link

API Endpoint Reference

Saves or updates the Google Drive evidence link for a monthly report.Request Body:
{
  "id_ot": 123,
  "mes": 3,
  "anio": 2025,
  "archivo": "https://drive.google.com/drive/folders/..."
}
Response:
{
  "exito": true,
  "mensaje": "Evidencia vinculada al Reporte Mensual correctamente.",
  "archivo": "https://drive.google.com/drive/folders/..."
}
Error Response:
{
  "exito": false,
  "mensaje": "La URL del archivo es obligatoria."
}

Database Constraints

Unique Constraint: The combination of [id_ot, mes, anio] must be unique. Attempting to create a duplicate will raise an IntegrityError.
class Meta:
    db_table = 'reporte_mensual_header'
    unique_together = ['id_ot', 'mes', 'anio']

Audit Trail

Monthly reports maintain automatic audit timestamps:

fecha_creacion

Automatically set when record is first createdUsage: Track when month was opened

fecha_actualizacion

Automatically updated on every saveUsage: Track last modification for audit trails

Best Practices

1

Early Creation

Don’t wait until month end - monthly reports are auto-created with first data entry
2

Consistent Evidence Structure

Use consistent Google Drive folder naming:
OTE-2025-001_Marzo_2025/
  ├── Fotos_Diarias/
  ├── Reportes_Operativos/
  ├── GPU_Evidencias/
  └── Incidentes/
3

Regular Status Updates

Update closure status as month progresses:
  • Days 1-25: “Abierto”
  • Days 26-28: “En Revisión”
  • Day 30+: “Cerrado”
4

Lock After Billing

Once invoiced, consider changing status to “Facturado” to prevent accidental modifications

Troubleshooting

Cause: Attempting to create multiple monthly reports for same OT and month.Solution: Always use get_or_create() instead of create().
# ❌ Wrong
ReporteMensual.objects.create(id_ot_id=123, mes=3, anio=2025)

# ✅ Correct
ReporteMensual.objects.get_or_create(
    id_ot_id=123, mes=3, anio=anio,
    defaults={'id_estatus_id': 1}
)
Cause: Production records exist from previous status change or deleted line items.Solution: Filter by id_partida_anexo__importacion_anexo__es_activo=True to exclude deprecated line items.

Daily Reports

Learn about daily operational status tracking

Production Overview

Return to production tracking overview

Build docs developers (and LLMs) love