/** * ContratoDetailPage - Detalle del contrato con tabs */ import { useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { ArrowLeft, FileText, Building2, Calendar, DollarSign, Pencil, Send, CheckCircle, PlayCircle, XCircle, ListTodo, FilePlus, Clock, Plus, Trash2, } from 'lucide-react'; import { useContract, useContractPartidas, useContractAddendums, useSubmitContract, useApproveContract, useActivateContract, useCompleteContract, useTerminateContract, useDeleteContractPartida, useDeleteContractAddendum, } from '../../../hooks/useContracts'; import type { Contract, ContractPartida, ContractAddendum } from '../../../types/contracts.types'; import { CONTRACT_TYPE_OPTIONS, CONTRACT_STATUS_OPTIONS, ADDENDUM_TYPE_OPTIONS, ADDENDUM_STATUS_OPTIONS, } from '../../../types/contracts.types'; import { StatusBadgeFromOptions, LoadingOverlay, EmptyState, ConfirmDialog, Modal, ModalFooter, TextareaField, } from '../../../components/common'; import { ContractForm } from '../../../components/contracts/ContractForm'; import { AddendaModal } from '../../../components/contracts/AddendaModal'; import { PartidaModal } from '../../../components/contracts/PartidaModal'; type TabType = 'info' | 'partidas' | 'addendas'; export function ContratoDetailPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('info'); const [showEditModal, setShowEditModal] = useState(false); const [showAddendaModal, setShowAddendaModal] = useState(false); const [showPartidaModal, setShowPartidaModal] = useState(false); const [editingAddenda, setEditingAddenda] = useState(null); const [editingPartida, setEditingPartida] = useState(null); const [deletePartidaId, setDeletePartidaId] = useState(null); const [deleteAddendaId, setDeleteAddendaId] = useState(null); const [showTerminateModal, setShowTerminateModal] = useState(false); const [terminateReason, setTerminateReason] = useState(''); const { data: contract, isLoading, error } = useContract(id || ''); const { data: partidas, isLoading: loadingPartidas } = useContractPartidas(id || ''); const { data: addendums, isLoading: loadingAddendums } = useContractAddendums(id || ''); const submitMutation = useSubmitContract(); const approveMutation = useApproveContract(); const activateMutation = useActivateContract(); const completeMutation = useCompleteContract(); const terminateMutation = useTerminateContract(); const deletePartidaMutation = useDeleteContractPartida(); const deleteAddendaMutation = useDeleteContractAddendum(); const formatCurrency = (value: number) => { return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', }).format(value); }; const handleSubmit = async () => { if (id) await submitMutation.mutateAsync(id); }; const handleApprove = async () => { if (id) await approveMutation.mutateAsync(id); }; const handleActivate = async () => { if (id) await activateMutation.mutateAsync(id); }; const handleComplete = async () => { if (id) await completeMutation.mutateAsync(id); }; const handleTerminate = async () => { if (id && terminateReason) { await terminateMutation.mutateAsync({ id, reason: terminateReason }); setShowTerminateModal(false); setTerminateReason(''); } }; const handleDeletePartida = async () => { if (id && deletePartidaId) { await deletePartidaMutation.mutateAsync({ contractId: id, partidaId: deletePartidaId }); setDeletePartidaId(null); } }; const handleDeleteAddenda = async () => { if (id && deleteAddendaId) { await deleteAddendaMutation.mutateAsync({ contractId: id, addendumId: deleteAddendaId }); setDeleteAddendaId(null); } }; if (isLoading) { return ; } if (error || !contract) { return ( ); } const tabs = [ { id: 'info', label: 'Informacion General', icon: FileText }, { id: 'partidas', label: 'Partidas', icon: ListTodo, count: partidas?.length }, { id: 'addendas', label: 'Addendas', icon: FilePlus, count: addendums?.length }, ]; const canSubmit = contract.status === 'draft'; const canApprove = contract.status === 'review'; const canActivate = contract.status === 'approved'; const canComplete = contract.status === 'active'; const canTerminate = ['active', 'approved'].includes(contract.status); return (
{/* Header */}

{contract.contractNumber}

{contract.name}

{canSubmit && ( )} {canApprove && ( )} {canActivate && ( )} {canComplete && ( )} {canTerminate && ( )}
{/* Stats Cards */}

Monto Contrato

{formatCurrency(contract.contractAmount)}

Facturado

{formatCurrency(contract.invoicedAmount)}

Pagado

{formatCurrency(contract.paidAmount)}

Avance

{contract.progressPercentage}%

{/* Tabs */}
{activeTab === 'info' && } {activeTab === 'partidas' && ( { setEditingPartida(null); setShowPartidaModal(true); }} onEdit={(p) => { setEditingPartida(p); setShowPartidaModal(true); }} onDelete={setDeletePartidaId} /> )} {activeTab === 'addendas' && ( { setEditingAddenda(null); setShowAddendaModal(true); }} onEdit={(a) => { setEditingAddenda(a); setShowAddendaModal(true); }} onDelete={setDeleteAddendaId} /> )}
{/* Modals */} {showEditModal && ( setShowEditModal(false)} /> )} {showAddendaModal && ( { setShowAddendaModal(false); setEditingAddenda(null); }} /> )} {showPartidaModal && ( { setShowPartidaModal(false); setEditingPartida(null); }} /> )} {/* Terminate Modal */} {showTerminateModal && ( setShowTerminateModal(false)} title="Terminar Contrato" size="md" footer={ } > setTerminateReason(e.target.value)} placeholder="Describa la razon por la cual se termina el contrato..." rows={4} /> )} {/* Delete Confirmations */} setDeletePartidaId(null)} onConfirm={handleDeletePartida} title="Eliminar Partida" message="Esta seguro de eliminar esta partida del contrato?" confirmLabel="Eliminar" variant="danger" isLoading={deletePartidaMutation.isPending} /> setDeleteAddendaId(null)} onConfirm={handleDeleteAddenda} title="Eliminar Addenda" message="Esta seguro de eliminar esta addenda?" confirmLabel="Eliminar" variant="danger" isLoading={deleteAddendaMutation.isPending} />
); } // ============================================================================ // CONTRACT INFO TAB // ============================================================================ function ContractInfoTab({ contract }: { contract: Contract }) { const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString('es-MX', { year: 'numeric', month: 'long', day: 'numeric', }); }; return (
{/* General Info */}

Datos Generales

{contract.description && ( )}
{/* Client/Subcontractor Info */}

{contract.contractType === 'client' ? 'Datos del Cliente' : 'Datos del Subcontratista'}

{contract.contractType === 'client' ? ( <> ) : ( <> )}
{/* Dates */}

Vigencia

{contract.signedAt && ( )}
{/* Payment Terms */}

Condiciones de Pago

{contract.paymentTerms || 'Sin condiciones especificas'}

{/* Audit Info */}

Historial

{contract.submittedAt && ( )} {contract.approvedAt && ( )} {contract.terminatedAt && ( <> )}
{/* Notes */} {contract.notes && (

Notas

{contract.notes}

)}
); } function InfoRow({ label, value }: { label: string; value: string }) { return (
{label} {value}
); } // ============================================================================ // PARTIDAS TAB // ============================================================================ interface PartidasTabProps { partidas: ContractPartida[]; isLoading: boolean; onAdd: () => void; onEdit: (partida: ContractPartida) => void; onDelete: (id: string) => void; } function PartidasTab({ partidas, isLoading, onAdd, onEdit, onDelete }: PartidasTabProps) { const formatCurrency = (value: number) => { return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', }).format(value); }; if (isLoading) { return
Cargando partidas...
; } const total = partidas.reduce((sum, p) => sum + (p.totalAmount || p.quantity * p.unitPrice), 0); return (

Partidas del Contrato

{partidas.length === 0 ? ( } title="Sin partidas" description="Agrega las partidas del contrato." /> ) : (
{partidas.map((partida) => ( ))}
Concepto Cantidad P.U. Total Acciones
{partida.conceptoCode || 'N/A'}
{partida.conceptoDescription || '-'}
{partida.quantity.toLocaleString()} {partida.unit || ''} {formatCurrency(partida.unitPrice)} {formatCurrency(partida.totalAmount || partida.quantity * partida.unitPrice)}
Total: {formatCurrency(total)}
)}
); } // ============================================================================ // ADDENDAS TAB // ============================================================================ interface AddendasTabProps { addendums: ContractAddendum[]; isLoading: boolean; onAdd: () => void; onEdit: (addendum: ContractAddendum) => void; onDelete: (id: string) => void; } function AddendasTab({ addendums, isLoading, onAdd, onEdit, onDelete }: AddendasTabProps) { const formatCurrency = (value: number) => { return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', }).format(value); }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric', }); }; if (isLoading) { return
Cargando addendas...
; } return (

Addendas del Contrato

{addendums.length === 0 ? ( } title="Sin addendas" description="No hay addendas registradas para este contrato." /> ) : (
{addendums.map((addendum) => (
{addendum.addendumNumber}

{addendum.title}

{addendum.description}

Vigencia: {formatDate(addendum.effectiveDate)} {addendum.amountChange !== 0 && ( 0 ? 'text-green-600' : 'text-red-600'}> {addendum.amountChange > 0 ? '+' : ''}{formatCurrency(addendum.amountChange)} )} {addendum.newEndDate && ( Nueva fecha fin: {formatDate(addendum.newEndDate)} )}
))}
)}
); }